Skip to content

feat(core): LambdaDelta part 2 — hygienic macros + multimethods + prelude#43

Merged
hyperpolymath merged 1 commit into
mainfrom
claude/project-scope-planning-5zoaze
Jul 9, 2026
Merged

feat(core): LambdaDelta part 2 — hygienic macros + multimethods + prelude#43
hyperpolymath merged 1 commit into
mainfrom
claude/project-scope-planning-5zoaze

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

What this is

The language core, completed (spec §3) — the second half of "1 then 2": full macro hygiene, procedural defmacro, multimethods, and a sugar prelude. Builds on the L0 kernel (#35) and notebook host (#36); the kernel/host seam is untouched, so the notebook host inherits all of this for free.

You chose B — full set-of-scopes hygiene now, in one pass — and that's what this is: not the weaker Clojure auto-gensym model.

Full hygiene (and it's genuinely full)

Kohlbecker-style marks, encoded in symbol names. Each macro expansion gets a fresh mark; quasiquote tags the symbols a template introduces with it. Both hygiene properties are implemented and tested:

Property Test Result
Introduced bindings never capture (defmacro m [e] \(let [x 2] ~e)) (let [x 1] (m x))` 1, not 2
Free identifiers are referentially transparent (defmacro inc [n] \(+ ~n 1)) (let [+ -] (inc 5))` 6, not 4

So the prelude's and/or/if-let use plain v/tmp with no foo# — "macro-introduced bindings never capture… without the author asking" (spec §3), delivered literally.

Mechanics: marked free identifiers fall back to the definition (global) env; special forms and macros dispatch on the base name (so a marked let/if/user-macro in a template still works); Display strips marks so output stays readable.

What's in this PR

  • defmacro — procedural transformer over unevaluated forms; gensym, macroexpand, macroexpand-1.
  • Multimethods (spec §3, open dispatch — not class-OO): defmulti name dispatch-fn + defmethod name dispatch-val [args] … with :default. Dispatch on any function of the args — e.g. a note's :type:
    (defmulti describe :type)
    (defmethod describe "task" [n] (str "task:" (:title n)))
    (defmethod describe :default [n] "other")
  • Prelude (prelude.rs) — hygienic sugar written in λδ itself, loaded at Interp::new(): when unless cond and or -> ->> if-let when-let.

Files

value.rs (mark primitives + MultiMethod + mark-stripping display) · mod.rs (macro/multimethod registries, mark counters, prelude load) · eval.rs (hygiene-aware symbol resolution, base-name dispatch, expand_macro, defmacro/defmulti/defmethod, quasiquote marking) · builtins.rs (gensym/macroexpand) · prelude.rs (new) · tests.rs (11 new tests).

Deliberately deferred

let/fn destructuring; an explicit unhygienic escape hatch (if ever justified); case/match (pattern matching). Each layers on without touching the seam.

Verification

  • cargo test81 lib tests green (11 new: user macros, both hygiene properties, multimethods, all prelude sugar, gensym, macroexpand) + host + golden + invariants + doc-tests.
  • cargo clippy --all-targets -- -D warnings — clean (default and --features wasm).
  • cargo fmt --check — clean. Production kernel stays panic-free (Hypatia-clean).

Note: estate governance workflow_audit findings are pre-existing estate policy, out of scope; rust-ci is the gate here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PAEqVP7Y3bZDhtAaPUTbps


Generated by Claude Code

…lude

Completes the language core (spec §3): full hygiene, procedural macros,
multimethods, and a sugar prelude — the "1 then 2" second half.

Full hygiene (Kohlbecker-style marks, encoded in symbol names):
- Each macro expansion gets a fresh mark; quasiquote tags the symbols a
  template *introduces* with it. Two guarantees, both tested:
  * introduced bindings never capture — `(defmacro m [e] `(let [x 2] ~e))`
    then `(let [x 1] (m x))` → 1, not 2;
  * free identifiers are referentially transparent — a macro's `+` means the
    global `+` even when the caller locally rebinds `+` to `-`.
- So `and`/`or`/`if-let` use plain `v`/`tmp` with no `foo#` — "never capture,
  without the author asking" (spec §3), not the weaker Clojure model.
- Marked free identifiers fall back to the definition (global) env; special
  forms and macros dispatch on the base name; Display strips marks.

Macros & reflection:
- `defmacro` (procedural: transformer over unevaluated forms), `gensym`,
  `macroexpand` / `macroexpand-1`.

Multimethods (spec §3 — open dispatch, not class-OO):
- `defmulti name dispatch-fn` + `defmethod name dispatch-val [args] …` with
  `:default`. Dispatch on any fn of the args — e.g. a note's `:type`.

Prelude (`prelude.rs`) — hygienic sugar written in λδ itself and loaded at
`Interp::new()`: `when unless cond and or -> ->> if-let when-let`.

The kernel/host seam is untouched; the notebook host inherits all of this for
free. Verification: `cargo test` 81 lib tests green (11 new: macros, both
hygiene properties, multimethods, all prelude sugar, gensym, macroexpand);
clippy `-D warnings` clean (default + wasm); fmt clean; production kernel
stays panic-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAEqVP7Y3bZDhtAaPUTbps
@sonarqubecloud

sonarqubecloud Bot commented Jul 9, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 62 issues detected

Severity Count
🔴 Critical 1
🟠 High 30
🟡 Medium 31

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Issue in scorecard.yml",
    "type": "missing_workflow",
    "file": "scorecard.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Issue in boj-build.yml",
    "type": "missing_timeout_minutes",
    "file": "boj-build.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in casket-pages.yml",
    "type": "missing_timeout_minutes",
    "file": "casket-pages.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in codeql.yml",
    "type": "missing_timeout_minutes",
    "file": "codeql.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath
hyperpolymath marked this pull request as ready for review July 9, 2026 21:42
@hyperpolymath
hyperpolymath merged commit caad852 into main Jul 9, 2026
27 checks passed
@hyperpolymath
hyperpolymath deleted the claude/project-scope-planning-5zoaze branch July 9, 2026 21:42
hyperpolymath added a commit that referenced this pull request Jul 16, 2026
Verified against the code rather than TOPOLOGY.md, which is stale:

  claim                     TOPOLOGY/wiki       reality
  λδ substrate              "planned"           built (#35, #36, #43 merged)
  core size                 ~623 LOC, 12 tests  5,344 LOC, 90 tests green

λδ is the largest subsystem in the core (~4,050 LOC, ~76%): reader, value
model, evaluator + Budget, hygienic macros, multimethods, prelude, and the
notebook host seam. Calling it "planned" materially misrepresents the project
to every audience the wiki serves.

- Home.md: substrate row planned -> built; status paragraph now cites measured
  figures and flags that TOPOLOGY.md predates the three LambdaDelta merges.
- Developer.md: add the λδ substrate as a first-class stack layer; Track A
  marked substantially landed, with the outstanding work named (.ld packages,
  surfacing L2+ through the disclosure ladder).

Toolchain verified end-to-end while checking this: `just build-wasm` (with
wasm-bindgen-cli 0.2.126, matching Cargo.lock), `just build`, and `just test`
all green — 90 Rust + 10 UI tests, including "TEA update delegates to the
wasm core".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Jul 17, 2026
…fix the Justfile (#46)

## Why

`TOPOLOGY.md` predated the three LambdaDelta merges (#35, #36, #43) and
never mentioned λδ. Because it is *the* status source, every downstream
doc inherited its stale figures — including the wiki landed in #45. This
re-derives the countable facts from the tree and corrects the cascade,
then fixes the Justfile gaps found along the way.

## TOPOLOGY was understating the project on six axes

| Claim | Reality |
|---|---|
| λδ absent entirely | **Built, not planned** — reader, value model,
evaluator + Budget sandbox, hygienic macros, multimethods, prelude, host
seam. ~3,400 LOC excl. tests, **~64% of the core**, its largest
subsystem. Now its own dashboard row |
| "12 passing unit tests" | **90** (81 unit + 3 exchange + 2 golden + 2
property + 2 doc) |
| WASM bridge "40%, in progress" | **Wired and exercised** — `Main.res`
loads the wasm at boot, `WasmStore` binds 21 of 31 exports, ui-ci runs a
TEA↔WASM contract test against the real bundle |
| CI "10%, landing in a parallel workstream" | `rust-ci.yml` +
`ui-ci.yml` **load-bearing since #22**, SHA-pinned, green on main |
| Web/PWA "no service worker yet (planned)" | Service worker +
webmanifest + icon **ship and are registered** (`Main.res:88`, #27) |
| UI "no drag-and-drop; GraphView placeholder" | Note drag
**implemented** (`View.res:397`); GraphView renders a circular layout |

**Overall ~35% → ~65%.** The critical path has **moved off the WASM
bridge onto the UI surface**: 10 exports are unbound in ReScript,
including both λδ entry points (`lambdadeltaEval`, `evalLambdadelta`) —
the substrate is reachable from JS but nothing calls it.

Also corrects **a mixed-basis error I introduced in #45**: λδ was
reported as "~76% of the core" by dividing 4,049 LOC (*with* tests) by
5,344 (*without*). Like-for-like it is 3,407/5,344 = **~64%**.

## Justfile

- **`just test` could not work on a clean checkout** — the UI tests
import generated `*.res.js` and the wasm bindings, so type-check failed
with 4 `TS2307` errors. Now `test → build → build-wasm`. Warm no-op
costs ~0.45s, so it is not a tax on the inner loop; added `test-rust`
for the Rust-only loop.
- **`just check` was weaker than CI** (no `--all-targets`, no
`--features wasm`, ran from `core/`) — it could pass locally while CI
failed. Now mirrors `rust-ci.yml` exactly.
- **`just doctor` ignored the wasm toolchain** — the one thing that
actually blocks the browser build. Now checks the wasm32 target and
compares the wasm-bindgen CLI against `Cargo.lock`, printing the exact
fix command on drift.
- **`crg-grade` and `crg-badge` were dead on arrival** — Make-style
`$$(...)` under just/sh expanded `$$` to the shell PID, so `(` was a
syntax error. They failed 100% of the time. Rewritten as bash shebang
recipes.

## Verification

- `just check` — pass (now compiles the `wasm` feature, which the old
one never did)
- `just test` from a **fully cleaned tree** (no `web/wasm`, no
`*.res.js`) — rebuilds and passes **90 Rust + 10 UI**
- `just doctor`, `crg-grade` (→ `D`), `crg-badge` — all run; doctor's
FAIL path verified by hiding wasm-bindgen from `PATH`
- `asciidoctor` renders README/ROADMAP/QUICKSTART-DEV clean; `just
wiki-sync dry` still previews correctly

## Deliberately not done — needs your call

- **`READINESS.md` grade D is probably stale.** All three *Path to C*
criteria now appear met (product CI on every PR ✅, UI tests in CI ✅,
WASM bridge built + smoke-tested ✅). The grade is assigned by audit, so
I corrected the facts and flagged a re-audit rather than self-promoting.
`just crg-badge` publishes that letter, so the badge is currently
understating the project.
- **`must-spdx-headers` is a broken gate** (pre-existing, untouched
here). It runs `find . | head -20`, so it checks an arbitrary,
readdir-order-dependent fifth of the tree *and* walks vendored
`node_modules/`. It currently fails on `@rescript/react`'s own `.res`
files — **every tracked source file does have SPDX**. Worse,
`contractile.just` has drifted from its source:
`contractiles/must/Mustfile.a2ml` contains no `spdx-headers` rule at
all, and its `no-hardcoded-paths` rule (severity: **critical**) is
missing from the generated file. The `contractile` tool isn't installed
to regenerate, and this is your standards ecosystem — so I left it
alone.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants