8000 Emitter abstraction layer: decouple language semantics from Python backend · Issue #147 · Mnehmos/mnehmos.nls.lang · GitHub
[go: up one dir, main page]

Skip to content

Emitter abstraction layer: decouple language semantics from Python backend #147

Closed

Description

@Mnehmos

Problem

NLS language semantics are currently entangled with Python emitter quirks. The 'language' is effectively a Python macro system, not an independent language with its own semantics.

Acceptance Criteria

Why This Blocks v1.0

A production language platform must be separable from its implementation backend. This is the architectural gate for multi-target and long-term language stability.


Typed-control roadmap and current status

Updated 2026-09-10 after checking NLS 3110b312950233ff0f8e28cfddc2f5e9b90443cd and Osprey 8792de9f6d159922141f9d191e4bc5d256fbb971.

Completed foundations

These are closed implementation milestones. The original audit comment and issue reproducers retain their original commit baseline; they are not claims that the same failures were re-tested on current master. A representable/lowered IR is still distinct from a module that has passed every type, control, effect, and failure check.

Remaining implementation sequence

Work Issues Boundary
Graph and checked value/control semantics #192, #195, #196 Valid step identities, resolved operations/types, dominance, branches, and loop-carried values
Observable effects and typed failures #197, #198 Separate required operations, resource footprints, and escaping failures
Backend conformance #202 Begin the Python/TypeScript harness now; migrate to each checked pass as it lands
Safe eligibility and protocol state #199, #200 Preserve alias/control/failure constraints and consumed resource states
Typed injectable handlers #207 Synchronous direct-return lexical handlers first
Affine resumption #208 At most one successful answer per operation request; no general continuation runtime
Retry/timeout policies #201 Ordinary bounded policies can proceed on the core passes; handler integration follows #207/#208
Optional Osprey target study #209 Versioned compatibility probes and a go/no-go decision; does not block Python/TypeScript or require a production backend

#195–#202 now include Osprey-informed implementation refinements, bounded acceptance cases, and pinned source references. #207–#209 contain their own implementation hints, dependencies, acceptance criteria, and scope limits. Existing Rust target work stays in #24.

Architectural invariants from the comparison

  1. Frontend differences end at canonical IR. Existing NLS parser/localization paths must feed the same checker and emitters. This does not require another surface syntax.
  2. Resolved operations carry computation contracts. Check argument/results, latent callback requirements, control regions, resource effects, and failures before calling a module semantically checked.
  3. Handling does not imply purity. Supplying a handler removes its operation requirement; the implementation's writes, aliases, clocks, unknown effects, and escaping failures remain relevant.
  4. Three obligations stay distinct. Resource-state consumption (feat(typestate): check explicit resource state transitions and consumed-state reuse #200), request-continuation consumption (feat(control): enforce affine single-shot handler resumption #208), and external replay/idempotency (feat(control): add checked Retry and Timeout policies with explicit idempotency obligations #201) cannot substitute for one another.
  5. Capability checks precede executable output. A backend must reject unsupported semantic forms before emitting partial programs or launching a toolchain.
  6. Keep the work staged. Direct handlers do not need resumption; basic retry does not need handlers; the common backend harness does not wait for every future control combinator.

Primary implementation references

These references were inspected, not executed during this comparison. At the pinned commit, Osprey's checker rejects unsupported many and declaration-level abort operations. Its resource/continuation guarantees should not be inferred from website summaries or generalized into externally exactly-once execution.

Activity

  1. added this to the v1.0 milestone on Mar 17, 2026
  2. added
    coreCore language semantics and design
    emitterCode generation targets
    on Mar 17, 2026
  3. Mnehmos commented on Sep 10, 2026

    @Mnehmos
    OwnerAuthor

    Typed control/dataflow/effect audit and implementation series

    Audited current master at 5630d74ec9cd912d4e2f325349f1943cbc15bcc6, including compiler paths, graph helpers, the core semantic document, tests, and all 104 existing issue records plus 85 PR records available before this series. This comment uses #147 as the existing architecture umbrella rather than opening another duplicate IR epic.

    Audit conclusion

    The proposed direction is supported by the source: NLS has useful value-dependency and state-label metadata, but no checked representation that defines operation types, control regions, effects, and escaping failures together. The next step is a minimal checked IR beneath the existing surface, with deterministic lowering and explicit operation resolution.

    Several qualifications matter:

    • parallel_groups() currently serves graph analysis/output. Emitters still traverse source order. This is an unsound claim of parallel eligibility, not evidence that the current runtime already races bank requests.
    • Core NLS Semantics (Target-Neutral IR) #88 delivered a semantic document and a Python-oriented semantic test suite. It did not deliver the checked compiler IR envisioned by this issue. Treat the new work as the first real checked IR boundary.
    • Immutable bindings and structural types are specified, but not fully enforced by a semantic type checker. Mutable reference-bearing lists/records also need alias/effect analysis.
    • Typed failures can be added while retaining the existing exception model. Result types are not a prerequisite.
    • Disjoint value dependencies and even disjoint effect labels do not by themselves justify reordering: failure, short-circuit, branch, alias, and resource lifetime behavior must be preserved.
    • Typestate labels alone cannot prevent double consumption; consumed tokens/aliases need explicit rules. A compiler also cannot infer exactly-once external execution from a timeout or a claimed idempotency key.

    Confirmed correctness gaps

    The issue bodies contain pinned source references and reproductions. Current probes established that:

    1. Descriptive output-producing steps become None; type-only RETURNS can substitute an invented zero.
    2. A valid [reject]() call without an output binding becomes a Python comment, erasing its error behavior.
    3. Unknown calls and incompatible typed call arguments pass shared validation. CLI verify also returns ok: true for the unknown-call and conditional-output examples.
    4. Conditional results are not checked for availability; TypeScript block scope breaks even the true branch. Rebinding emits duplicate const declarations.
    5. Duplicate LOGIC numbers can collapse the dependency graph to an empty result while the generated program still executes both steps.
    6. Python ANLU-call parsing truncates nested arguments. TypeScript rewrites keywords inside a string literal, and its equality/truthiness/division semantics differ from the documented core.
    7. A LOGIC-only edit can be reported unchanged, pass lock verification, and cause the public rebuild API to reuse the old body. The reproduced edited program should return 2 but the cached result is 1.

    Implementation series

    Every new issue includes scope, implementation hints, acceptance criteria, priorities, and integration dependencies. Existing broader work (#20, #24, #86, #143, #144, #148, #149, #150) remains referenced rather than duplicated.

    Issue Work Priority Integration dependencies
    #190 Reject unresolved executable prose/placeholders High Can start now
    #191 Preserve discarded operation calls High Can start now
    #192 Validate graph identity and control/data distinctions Medium Can start now
    #193 Fix semantic hashes and stale cache reuse High Can start now
    #194 Add minimal target-neutral IR and expression lowering High Can start now
    #195 Resolve signatures and check value types High #194
    #196 Check branch joins, binding mutation, and loop-carried values High #194, #195
    #197 Infer conservative effects and resource identities Medium #194, #195
    #198 Check failure sets and portable exception payloads High #194, #195
    #199 Prove parallel eligibility conservatively Medium #192, #196, #197, #198
    #200 Check resource states and consumed-token reuse Medium #195, #196, #197, #198
    #201 Specify/check retry and timeout obligations Medium #196, #197, #198
    #202 Migrate emitters and execute cross-target conformance tests High #194, #195, #196, #197, #198

    Recommended sequence

    1. Land semantic-loss and stale-cache regressions/fixes (bug(semantics): reject executable prose and implicit placeholder returns #190, bug(emitter): preserve ANLU calls when their return values are discarded #191, bug(lockfile): include LOGIC and contracts in semantic hashes and cache invalidation #193), correct graph integrity/claims (bug(graph): validate step identities and distinguish dataflow from control flow #192), and begin the TypeScript test runner from feat(backends): consume checked IR and enforce executable Python/TypeScript semantic conformance #202.
    2. Establish the small IR (feat(ir): introduce a minimal target-neutral expression and control IR with source spans #194), signature/type validation (feat(semantics): resolve operation signatures and type-check calls, bindings, and returns #195), and explicit control/binding checks (bug(control): check branch outputs, binding mutation, and loop-carried values before emission #196). Move supported emitter paths through these shared checks incrementally.
    3. Add conservative effects and failures (feat(effects): infer conservative operation effects with resource identities and foreign-call contracts #197, feat(errors): add checked failure sets and a portable typed exception contract #198), then analyze parallel eligibility (feat(dataflow): require effect, alias, control, and failure checks for parallel eligibility #199). Retain sequential execution until explicit concurrency semantics are supported.
    4. Introduce opt-in protocol state checking (feat(typestate): check explicit resource state transitions and consumed-state reuse #200) and checked retry/timeout policies (feat(control): add checked Retry and Timeout policies with explicit idempotency obligations #201). Extend the conformance gate as each capability becomes supported.

    Do not front-load Race, compensation/sagas, a universal ontology, general ownership, or the Rust backend into the first IR milestone. Prefer a small checkable subset plus explicit unsupported-feature diagnostics over silent target fallbacks. Compilation remains deterministic and has no LLM participation.

    Validation performed

    Selected existing suites for dataflow, FSM, LOGIC emission, TypeScript emission, parser parity, semantic behavior, lockfiles, and diff: 116 passed, 22 skipped. The skips were Tree-sitter parity cases because that optional backend was unavailable.

    Additional isolated Python/Node probes reproduced the gaps above. Strict TypeScript compilation confirmed TS2304 for branch scope and TS2451 for rebinding; the corrupted string example passed strict compilation, demonstrating why executable semantic checks are also required.

    This was an audit and issue creation task. No compiler source changes or fixes were committed in this session.

  4. added 2 commits that reference this issue on Sep 12, 2026
  5. Mnehmos commented on Sep 12, 2026

    @Mnehmos
    OwnerAuthor

    Criteria audit (2026-09-13)

    Acceptance criterion State Evidence
    Defined IR between parser/resolver and emitters ✅ #194/#203; nlsc/ir.py, nlsc/lowering.py shared by both parser backends
    Python emitter consumes IR, not raw AST ✅ #241: checked ANLU bodies render from IROperation statement/expression nodes (guards, bindings, branches, augmented assigns, result); foreign content falls back to the legacy path with a scaffold diagnostic
    TS and Rust emitters also consume same IR ◐ TypeScript ✅ #242 (same statement-level rendering). Rust: tracked in #24, deliberately deferred — its prerequisites (remaining checked semantics #197/#200/#207 and a RustRunner conformance harness) are named in docs/targets.md
    Language spec can be validated against IR independently of any backend ✅ #243: nlsc ir --check runs the shared semantic gate plus check_module_eligibility, reports eligible, exits non-zero on ESEM errors or EIR003 blockers
    Emitter plugins can be authored without touching core parser/resolver ✅ #245: TargetEmitter registry (nlsc/targets.py); CLI/watch/capability matrix/lockfile identity all read it; plugins register in-process or via the nlsc.targets entry-point group

    The only unmet clause is the Rust half of criterion 3, which depends on #24 and is explicitly staged there with a design-intent mapping and prerequisites in docs/targets.md. Keeping this issue open solely for that clause; everything else has landed.

  6. Mnehmos commented on Sep 12, 2026

    @Mnehmos
    OwnerAuthor

    Correction to the audit above: this issue was auto-closed by PR #245's body ("Closes #147's second unchecked criterion"), which is why it now shows CLOSED — not by an explicit decision in the audit.

    That outcome is defensible: four of the five criteria are fully delivered (IR, Python IR consumption, IR-only spec validation, plugin registry) and the fifth — the Rust half of "TS and Rust emitters also consume same IR" — is entirely tracked in #24, which stays open with its design-intent mapping and prerequisites in docs/targets.md. Keeping this umbrella open would duplicate that work item in two places.

    If you prefer the umbrella open until Rust lands, just reopen; nothing else is outstanding here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    coreCore language semantics and designemitterCode generation targetspriority-highImportant, address soon

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0