Skip to content

feat(dsl): C ABI and Python DSL checking, closing p7-s5 (#43) - #105

Merged
JArmandoAnaya merged 4 commits into
mainfrom
feat/p7-s5-dsl-check-bindings
Aug 2, 2026
Merged

feat(dsl): C ABI and Python DSL checking, closing p7-s5 (#43)#105
JArmandoAnaya merged 4 commits into
mainfrom
feat/p7-s5-dsl-check-bindings

Conversation

@JArmandoAnaya

Copy link
Copy Markdown
Contributor

The last slice of p7-s5: scena::dsl::check_* reaches all three of Scena's
surfaces, so the DSL checker is not a C++-and-CLI-only feature.

C ABI

scn_check_dsl_file / scn_check_dsl_string produce an opaque scn_dsl_check
carrying the diagnostics, the type count and the file count. Checking needs no
engine — it is a frontend service — which is why the result is its own handle
rather than an engine's diagnostic list, and why its borrowed strings stay valid
until scn_dsl_check_destroy instead of until the next mutating call.

Two decisions worth naming:

  • A failing check still produces a handle. That is exactly the case whose
    diagnostics you want. Only a NULL argument returns without one, which is also
    how a caller tells a null argument apart from an unreadable path — both answer
    SCN_ERROR_INVALID_ARGUMENT, per the Status model's host-misuse split.
  • A zero-initialized scn_dsl_check_options turns the standard library off.
    A zero struct has to mean zero for an appendable ABI struct, so NULL options
    is what means "the C++ defaults, library included". Both are documented on the
    declarations.

capi/ links scena::frontend-dsl PRIVATE, like the XML frontend; nothing in
capi.h names a C++ type.

No ABI version bump. The header's rule is that adding symbols moves the
minor, but nothing is published yet and every prior symbol addition (p5-s4,
p5-s5, p6-s1, p6-s2) also landed at 10000. Bumping only for this one would
misrepresent the others; the released version is the maintainer's call.

Python

scena.check_dsl_file / check_dsl_string return a DslCheckstatus,
diagnostics, type_count, file_count, falsy unless Ok. The XML loaders
return (status, scenario) because the scenario is the payload and the findings
are secondary; a check has no payload, so a named object reads better than a
four-tuple.

python/examples/check_dsl.py runs in CI: a clean source, a defective one, and
an import resolved through a search path.

Parity audit

The audit only ever looked at scena::Engine methods, so the frontend entry
points could lag a binding unnoticed — which is exactly what this PR would have
risked. It now audits the namespace-scope entry points of scena::xml and
scena::dsl alongside the methods, same exclusions-carry-reasons rule:
xml::validate_* (loading validates on the way through) and dsl::load_* (the
lower half of check_*; its result is an AST no binding can carry).

test_parity.py's doctored-header test copies the frontend headers into its
sandbox too, so it still fails for the right reason.

Tests

  • capi_test.cpp +6: coverage, counts, search paths, the standard-library
    toggle, an unreadable path, and the null-argument sweep.
  • c_consumer.c: the same surface exercised from pure C11.
  • test_dsl_check.py +10.
  • 1310 gtest (+6), 194 pytest (+10).

Docs

scena-check.md gains the C and Python worked examples; c-api.md and
python.md link to them; the DSL coverage matrix records all four checking
surfaces as a table of its own.

Closes #43

Checking a DSL source needs no engine — it is a frontend service — so the
result is its own opaque handle rather than an engine's diagnostic list.
A failing check still produces one, because that is the case whose
diagnostics a caller wants; only a NULL argument returns without it.

A zero-initialized scn_dsl_check_options turns the standard library off,
which is what a zero struct has to mean for an appendable ABI struct;
NULL options therefore means the C++ defaults, library included.

capi/ now links scena::frontend-dsl, PRIVATE like the XML frontend: no
frontend type reaches capi.h, which stays C-clean.

Refs #43
The XML loaders return (status, scenario) because the scenario is the
payload and the findings are secondary. A check has no payload — the
findings are the result, alongside how far the checker got — so it
returns one named object instead of a four-tuple.

DslCheck carries status, diagnostics, type_count and file_count, and is
falsy unless the status is Ok.

python/examples/check_dsl.py runs in CI: a clean source, a defective one,
and an import resolved through a search path.

Refs #43
…ing docs

The parity audit only ever looked at scena::Engine methods, so the load
and check entry points — the functions a host calls to get a scenario in
— could lag a binding unnoticed. It now audits scena::xml and scena::dsl
namespace-scope entry points alongside the methods, with the same
exclusions-carry-reasons rule; xml::validate_* and dsl::load_* are
excluded and say why.

Also: the coverage matrix records all four checking surfaces, and
scena-check.md gains the C and Python worked examples that c-api.md and
python.md link to.

Refs #43
scena-frontend-dsl is a static library, and this PR is the first thing to
link it into a shared object (scena-capi and the Python extension). GNU ld
rejects that outright without -fPIC. The XML frontend and the core already
carry POSITION_INDEPENDENT_CODE for exactly this reason; macOS never
noticed because its toolchain compiles PIC by default.

Refs #43
@JArmandoAnaya
JArmandoAnaya merged commit 84f993f into main Aug 2, 2026
23 of 24 checks passed
@JArmandoAnaya
JArmandoAnaya deleted the feat/p7-s5-dsl-check-bindings branch August 2, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[p7-s5] Standard library checking & scena-check

1 participant