From c486e10e7e00776e02eaef46caafed1ff1d07bb0 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 24 Sep 2026 22:34:02 +0000 Subject: [PATCH] =?UTF-8?q?ziz-drop:=20=C5=BDiz=20language=20design,=20JEG?= =?UTF-8?q?=20spec,=20tree-sitter=20skeleton,=20Chapel=20ziz0=20bootstrap,?= =?UTF-8?q?=20and=20triage=20of=20AI-generated=20material?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Staging area for metadatastician/ziz (not yet reachable from this session). Contents: README, DESIGN, docs/JEG, TRIAGE (bins A/B/C of the Gemini transcript), tree-sitter grammar + indent-stack external scanner + corpus, bootstrap/ziz0.chpl (unverified: no chpl available), examples, Justfile fragment. ASCII-only surface; no triads, no dialectics in semantics. --- ziz-drop/DESIGN.adoc | 208 +++++++++ ziz-drop/Justfile.ziz-fragment | 15 + ziz-drop/README.adoc | 92 ++++ ziz-drop/TRIAGE.adoc | 235 ++++++++++ ziz-drop/bootstrap/ziz0.chpl | 569 ++++++++++++++++++++++++ ziz-drop/docs/JEG.adoc | 150 +++++++ ziz-drop/examples/fact.ziz | 14 + ziz-drop/examples/hello.ziz | 2 + ziz-drop/examples/layout.ziz | 10 + ziz-drop/examples/quote-eval.ziz | 11 + ziz-drop/examples/reflexive.ziz | 12 + ziz-drop/grammar/grammar.js | 111 +++++ ziz-drop/grammar/package.json | 8 + ziz-drop/grammar/src/scanner.c | 157 +++++++ ziz-drop/grammar/test/corpus/basics.txt | 34 ++ ziz-drop/grammar/tree-sitter.json | 4 + 16 files changed, 1632 insertions(+) create mode 100644 ziz-drop/DESIGN.adoc create mode 100644 ziz-drop/Justfile.ziz-fragment create mode 100644 ziz-drop/README.adoc create mode 100644 ziz-drop/TRIAGE.adoc create mode 100644 ziz-drop/bootstrap/ziz0.chpl create mode 100644 ziz-drop/docs/JEG.adoc create mode 100644 ziz-drop/examples/fact.ziz create mode 100644 ziz-drop/examples/hello.ziz create mode 100644 ziz-drop/examples/layout.ziz create mode 100644 ziz-drop/examples/quote-eval.ziz create mode 100644 ziz-drop/examples/reflexive.ziz create mode 100644 ziz-drop/grammar/grammar.js create mode 100644 ziz-drop/grammar/package.json create mode 100644 ziz-drop/grammar/src/scanner.c create mode 100644 ziz-drop/grammar/test/corpus/basics.txt create mode 100644 ziz-drop/grammar/tree-sitter.json diff --git a/ziz-drop/DESIGN.adoc b/ziz-drop/DESIGN.adoc new file mode 100644 index 00000000..9c890bbe --- /dev/null +++ b/ziz-drop/DESIGN.adoc @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 += Žiz — Design +:toc: macro +:icons: font + +toc::[] + +Status: *pre-alpha design.* Everything here is intent until `ziz0` runs it. +See link:TRIAGE.adoc[TRIAGE.adoc] for what was deliberately left out, and why. + +== One-paragraph summary + +Žiz is a unityped, homoiconic, reflexive language. There is one value domain, +programs are values, and the reader, printer, evaluator and environment are +ordinary values reachable from user code. Žiz has *no typechecker* and never +rejects a program for classification reasons. Instead every term carries a +*Judgement Evidence Graph* (JEG) entry: claims about the term, each linked to +the evidence for it (author assertion, static inference, runtime observation, +test outcome). Tooling *reads* the JEG; the runtime *appends* to it. The +first implementation, `ziz0`, is an interpreter written in Chapel. + +== What Žiz is *not* + +It is not dialectical. Expressions are not triads. There is no boundary +operator, no simplicial complex, no sheaf, no "Real". The name is a pun and +the pun is permitted in documentation and error messages; it has no +semantics. See TRIAGE.adoc §Finding 1. + +== Unityped, precisely + +Following Harper: a "dynamically typed" language is a statically typed +language with exactly one type. Every Žiz term has type `V`. "Untyped" is +the same fact seen from the syntax side (no annotations, no static +judgement). Runtime tag dispatch is pattern-matching on the single sum +`V = nil + bool + int + real + str + sym + pair + … + (V → V)`. The classical +denotational backdrop is Scott's D∞ (`D ≅ [D → D]`); we cite it and do +nothing further with it. + +== Core value domain + +[cols="1,2,2", options="header"] +|=== +| Tag | Payload | Notes + +| `nil` | — | the empty list and the false-ish sentinel +| `bool` | true / false | +| `int` | arbitrary precision | `ziz0` uses Chapel `int(64)` for now +| `real` | IEEE 754 binary64 | +| `str` | UTF-8 byte sequence | *content* may be any Unicode; *syntax* is ASCII +| `sym` | interned name | +| `pair` | car, cdr | lists are right-nested pairs ending in `nil` +| `vec` | contiguous sequence | +| `map` | ordered associative | +| `fn` | params, body, env | closures +| `prim` | host procedure | +| `env` | frame + parent | first-class +| `node` | tree-sitter node id | anchor into the CST; used by the JEG +| `claim` | subject, judgement, evidence | a JEG entry, also a value +|=== + +== Homoiconicity + +Source text →(reader)→ `V` →(evaluator)→ `V` →(printer)→ source text. +The reader's output is the AST; the AST is a list. Macros are ordinary +functions from `V` to `V` marked with `defmacro`. + +== Metaiconicity + +*Owner's term; this is the working definition pending the owner's own.* + +The mapping between text and values is itself a value. Concretely: + +* `(reader)` returns the current reader as a `map` of dispatch entries + (`char -> fn`). `(set-reader! m)` installs a new one. Reader macros are + therefore user code, and the *shape* of the language is data. +* `(printer)` / `(set-printer! m)` likewise. +* Both are scoped to the current `env`, so a module can change its own + surface syntax without changing anyone else's. + +This is what makes the off-side surface and the plain S-expression surface +*the same language* with two reader tables, rather than two dialects. It is +also where a one-glyph alias for the evaluator (e.g. a Cyrillic letter) would +live *if* the owner wants one: as a reader-table entry mapping to an ASCII +name, never as a lexical primitive. + +== Reflexivity + +* `(eval v [env])`, `(current-env)`, `(env-parent e)`, `(env-bindings e)`. +* `(jeg)` returns the live Judgement Evidence Graph for the current program. +* `(claim subject judgement . evidence)` appends to it. +* `(node-of v)` returns the CST anchor of a value if it came from source. + +Closest existing relative: Kernel (Shutt) — operatives receive unevaluated +operands and the caller's environment as first-class objects. Žiz's +`defmacro` + first-class `env` is a conservative version of that; whether to +go the full fexpr route is an open question. + +== Lexical rules (ASCII-only) + +The surface syntax uses *only printable ASCII* plus newline and tab. +Identifiers may not contain non-ASCII. String *contents* may. Rationale: +grep/diff/review/terminal/keyboard hygiene, and because anything non-ASCII +that matters can be introduced as a reader alias (§Metaiconicity). + +=== `sexp` reader + +---- +( ) [ ] { } ' ` , ,@ ; +"string with \" \\ \n escapes" +integer ::= -?[0-9]+ +real ::= -?[0-9]+\.[0-9]+([eE][-+]?[0-9]+)? +symbol ::= [A-Za-z_+\-*/<>=!?%&|^~$][A-Za-z0-9_+\-*/<>=!?%&|^~$.:]* +keyword ::= :symbol +---- + +`[ ]` reads as a `vec`, `{ }` as a `map`. + +=== `layout` reader (off-side) + +A line with two or more forms is an implicit list. A following block that is +indented deeper continues that list, one form (or nested implicit list) per +line. A line with a single form is *not* wrapped. `\` at end of line +continues. Explicit brackets disable layout inside them. + +Implemented as a tree-sitter *external scanner* emitting +`INDENT` / `DEDENT` / `NEWLINE` from an indent stack — off-side syntax is not +expressible in a context-free grammar, which is why any "N-rule EBNF" claim +for a layout language is wrong on its face. + +---- +define (fact n) + if (= n 0) + 1 + * n (fact (- n 1)) +---- + +reads identically to + +---- +(define (fact n) (if (= n 0) 1 (* n (fact (- n 1))))) +---- + +== Special forms + +`quote` `quasiquote` `unquote` `unquote-splicing` `if` `define` `set!` +`lambda` `defmacro` `begin` `let` `claim`. Everything else is a function. + +== Evaluation + +Eager, left-to-right, lexically scoped, proper tail calls required of any +conforming implementation (`ziz0` does not have them yet — that fact is a +JEG entry on `ziz0`, not a lie in this document). + +Errors are values. A failed operation returns an `error` map and *records a +claim* (`(claim node :raised {...} :evidence :runtime)`). Nothing unwinds +unless the caller asks via `(raise!)`. + +== Judgement Evidence Graph + +Full model in link:docs/JEG.adoc[docs/JEG.adoc]. Summary: + +* *Judgement*: a proposition about a subject (`:callable`, `:arity 2`, + `:returns :int`, `:pure`, `:raised`, `:tested-by`, …). Open vocabulary. +* *Evidence*: why we believe it — *kind* (`:asserted`, `:inferred`, + `:observed`, `:tested`, `:contradicted`) and *provenance* (who/what/when). +* Subjects are CST node ids; the graph survives re-parsing via tree-sitter's + incremental edit tracking. +* Append-only during a run; merged across runs. + +A JEG lint reports *unsupported* and *contested* judgements. It never blocks +a build; a `Mustfile` may make a threshold a gate — project policy, not +language semantics. + +== Toolchain + +Names are the owner's: `claudia`, `boggs`, `federici`, `dunayevskaya`, +`assata`. *Role assignment is undecided.* Two candidate mappings exist +(this repo's original guess, and Gemini's); the owner picks. Needed roles: + +. reader/printer registry (metaiconic tables) +. evaluator core +. JEG store / merge / query +. grammar, CST anchoring, editor integration +. CLI / REPL / project driver +. (later) memory / resource management, if not delegated to Chapel + +== Bootstrap plan + +. `ziz0` (Chapel): `sexp` reader, evaluator, printer, in-memory JEG. + Single locale. No FPGA. +. `ziz0` gains the `layout` reader via the tree-sitter C parser through + Chapel's C interop. +. JEG persisted (`.jeg.a2ml`, see JEG.adoc). +. Self-hosting: reader/evaluator rewritten in Žiz, run under `ziz0`. A + λ-calculus interpreter with the Y combinator is the smoke test. +. Only then: backends. Chapel-PGAS distribution via `chapeliser` first, + because the host is already Chapel. FPGA acceleration lives inside + `chapeliser`, behind Chapel; Žiz never sees it. + +== Related work (real) + +Kernel (Shutt, fexprs + first-class envs) · Refal (structural rewriting) · +Scheme R7RS-small (the sexp core) · sweet-expressions / SRFI-110 (layout +over S-expressions) · Harper, PFPL ch. 22 (unityped) · Scott 1969 (D∞). + +== Non-goals for the foreseeable future + +Static types. A typechecker under another name. Triads. Kaomoji. Bitstreams. diff --git a/ziz-drop/Justfile.ziz-fragment b/ziz-drop/Justfile.ziz-fragment new file mode 100644 index 00000000..260076b7 --- /dev/null +++ b/ziz-drop/Justfile.ziz-fragment @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: MPL-2.0 +# Append these recipes to the Justfile supplied by the RSR template. + +build: + chpl --fast bootstrap/ziz0.chpl -o ziz0 + +examples: build + for f in examples/hello.ziz examples/fact.ziz examples/quote-eval.ziz examples/reflexive.ziz; do \ + echo "== $f"; ./ziz0 --file=$f --jeg=observe; done + +grammar: + cd grammar && tree-sitter generate && tree-sitter test + +ascii-check: + ! grep -rnP '[^\x00-\x7F]' examples/ grammar/grammar.js bootstrap/ziz0.chpl | grep -v '"' || true diff --git a/ziz-drop/README.adoc b/ziz-drop/README.adoc new file mode 100644 index 00000000..7538c6a0 --- /dev/null +++ b/ziz-drop/README.adoc @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 += Žiz +:toc: macro +:icons: font + +image:https://img.shields.io/badge/status-pre--alpha-red.svg[Status: pre-alpha] +image:https://img.shields.io/badge/License-MPL--2.0-blue.svg[License: MPL-2.0] + +A unityped, homoiconic, *metaiconic*, reflexive language with **no +typechecker** — instead, a *Judgement Evidence Graph* records what is +believed about every term and why. Bootstrapped in Chapel. + +toc::[] + +== Status — read this first + +*Nothing runs yet.* This repository holds a design, a tree-sitter grammar +skeleton, and a Chapel bootstrap interpreter that has not yet been through a +Chapel compiler. If a model or a person tells you Žiz is "locked in", is +"dialectical", evaluates "triads", or can be "synthesised end-to-end in 48 +hours", see link:TRIAGE.adoc[TRIAGE.adoc]. + +== Routing + +[cols="2,3", options="header"] +|=== +| If you want… | Go to + +| What Žiz is, precisely, and what it is not +| link:DESIGN.adoc[DESIGN.adoc] + +| The Judgement Evidence Graph — the thing that replaces a typechecker +| link:docs/JEG.adoc[docs/JEG.adoc] + +| Which AI-generated claims about Žiz are real, misapplied, or invented +| link:TRIAGE.adoc[TRIAGE.adoc] + +| The grammar (tree-sitter, ASCII-only, off-side via external scanner) +| link:grammar/grammar.js[grammar/grammar.js], link:grammar/src/scanner.c[grammar/src/scanner.c] + +| The bootstrap interpreter +| link:bootstrap/ziz0.chpl[bootstrap/ziz0.chpl] + +| Examples +| link:examples/[examples/] +|=== + +== Sixty-second tour + +[source,lisp] +---- +(define (fact n) + (if (= n 0) 1 (* n (fact (- n 1))))) + +(claim fact :arity 1 :evidence :asserted) ; a judgement, with provenance +(print (fact 10)) +---- + +---- +$ ziz0 --file=examples/fact.ziz --jeg=observe +3628800 +; --- JEG: 6 judgements, 23 evidence edges +(sym:fact :arity 1) ; asserted=1 observed=11 +(sym:fact :takes 0 :int) ; observed=11 +(sym:fact :returns :int) ; observed=11 +(sym:fact :pure) ; asserted=1 +---- + +No type was declared. No type was checked. Every belief about `fact` is in +the graph with the evidence that supports it; tools decide what to do with +that, and they can disagree with each other. + +== Building (once Chapel is available) + +---- +just build # chpl --fast bootstrap/ziz0.chpl -o ziz0 +just examples # runs every examples/*.ziz the sexp reader can read +just grammar # tree-sitter generate && tree-sitter test +---- + +== Toolchain names + +`claudia` · `boggs` · `federici` · `dunayevskaya` · `assata` — the owner's +names. Role assignment is *not yet decided*; see TRIAGE.adoc appendix. + +== Governance + +Follows the Rhodium Standard Repository conventions from +`hyperpolymath/standards` (the RSR template supplies `SECURITY`, +`CONTRIBUTING`, `Mustfile`, `.machine_readable/`, etc.). Language policy: +Chapel for the bootstrap, C only for the tree-sitter scanner, JavaScript +only for the tree-sitter grammar DSL. No Python, no Rust component, no Deno. diff --git a/ziz-drop/TRIAGE.adoc b/ziz-drop/TRIAGE.adoc new file mode 100644 index 00000000..38e2256e --- /dev/null +++ b/ziz-drop/TRIAGE.adoc @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 += Žiz — Triage of the Gemini material +:toc: macro +:icons: font + +toc::[] + +== Purpose + +A third-party model (Gemini) produced a large body of material about Žiz and +asserted it was "locked in" and sufficient to "synthesize the entire system +end-to-end" in 48 hours. This document sorts that material into bins so +that nothing hallucinated leaks into the canonical design. + +*Rule:* anything in bin B or C is quarantined. It may be promoted to bin A +only by the owner, in a commit that cites the design reason — never by an AI +agent. + +== Finding 1 — the contamination has a single root + +Every hallucinated structure in the transcript descends from one move: +Gemini took the name *Žiz* (a Žižek pun), inferred "therefore Hegelian +dialectics", and made *dialectics the semantics of the language*. From there: + +---- +"Žiz" → Žižek → Hegel → thesis/antithesis/synthesis + → "every expression is a 3-tuple (triad)" + → "a triad is a 2-simplex" → boundary operator ∂₂ → "∂∘∂ = 0 is the invariant" + → simplicial complexes replace ASTs → homotopy = execution → sheaves → H¹ = consensus + → Lacan → "Standard Lacanary", the Real = /dev/null, objet petit a GC + → Sartre / Lippmann / interpassivity / fetishistic disavowal + → Cyrillic Ж as "the reflexive self-evaluator node" +---- + +None of this was in the owner's brief. Cut the root and the tree falls. +*The language is not dialectical. The pun lives in docs, not in semantics.* + +== Finding 2 — Gemini's examples refute Gemini's grammar + +Its "14-rule EBNF" says `Triad = "(" Thesis Antithesis Sublation ")" | Atom` +— every list has exactly three elements. Its own programs then contain +`(Ж 'if c a b)` (5), `(Ж 'match term c1 c2 c3)` (6), `(Ж 'let (…) body)` +(4), `(Ж 'attach-evidence a b "s")` (4). The λ-calculus interpreter it wrote +is an ordinary n-ary Lisp with quote marks sprinkled on and `Ж` prepended +to every call. When the owner said it looked "unusually familiar", Gemini +conceded: "beneath the Cyrillic Ж and the Hegelian terminology, the engine is +fundamentally a homoiconic S-expression evaluator". That is the most honest +sentence in the transcript. + +== Finding 3 — three incompatible syntaxes + +The transcript contains (a) parenthesised pseudo-Lisp, (b) a +Kotlin/Scala-flavoured `val x = Ž.Dialectic(False)` with `throw`/`catch`, +and (c) verbatim **Zig** (`@import`, `comptime`, `!void`, `for (xs) |*x|`, +`@compileError`). (c) also demands compile-time *rejection* of programs — +the exact thing the brief forbids. A model that has not noticed it is +switching languages is not designing one. + +== Finding 4 — reliability anti-signals to watch for + +"Beyond a shadow of a doubt" · "everything is locked in" · "always the only +correct choice" · an architecture diagram credited to a stock-photo site · +an "existential hazards" section for a language that does not exist · a +sixth toolchain name (`anuradha`) and a `MarxInterpreter` appearing once with +no introduction · a system prompt for another model that misspells its own +core term ("HOMOMICONIC"). + +== What the owner's brief actually contained (reconstructed) + +. Unitype / untyped — one value domain. +. Homoiconic. +. Metaiconic — *owner's term; owner's definition still needed.* +. Reflexive. +. No typechecker; a Judgement Evidence Graph alongside tree-sitter. +. Bootstrapped in FPGA-augmented Chapel. +. Off-side / bracketless surface (appears mid-transcript; assumed owner's). +. Toolchain names `claudia`, `boggs`, `federici`, `dunayevskaya`, `assata`. +. The name — and therefore a licence for jokes in *documentation*. + +Everything else in the transcript is Gemini. + +== Bin A — real, adopted + +[cols="2,3", options="header"] +|=== +| Item | Notes + +| Unityped vs untyped explanation (Harper/Scott; one universal type `D ≅ [D→D]`; runtime tag checks are pattern matches on the sum) +| Correct and well put — the only fully accurate technical passage. Adopted + in DESIGN.adoc §Unityped. + +| Comparison to Kernel (Shutt) and Refal +| Kernel's fexprs + first-class environments are the closest real relative + of "reflexive + metaiconic". Added to DESIGN.adoc §Related work. + +| Off-side / bracketless surface +| Adopted, but via a tree-sitter external scanner; Gemini's own layout + examples are inconsistent (an ad-hoc `:` opener with no rule). + +| tree-sitter grammar with an external layout scanner +| Adopted (`grammar/`). This was the one concrete, correct engineering + suggestion in the Mythos prompt. + +| Toolchain names +| Owner's. *Roles undecided* — see below. +|=== + +== Owner to confirm + +[cols="2,3", options="header"] +|=== +| Item | Question + +| Ж (Cyrillic zhe) as a one-glyph name for the evaluator/self +| Cute, not intrinsically wrong, collides with ASCII-only. If kept, it should + be a *reader-table alias* for an ASCII name — which is what metaiconicity + is for — never a lexical primitive. Keep as alias, or drop? + +| Toolchain role mapping +| Gemini: claudia=router, boggs=AST rewriter, federici=allocator, + dunayevskaya=evaluator, assata=sandbox. This repo's first guess: + claudia=readers, boggs=evaluator, federici=JEG store, + dunayevskaya=grammar, assata=CLI. "federici = memory reproduction" is + witty enough to keep. Owner picks; neither is authoritative. + +| Definition of "metaiconic" +| DESIGN.adoc uses "the text↔value mapping is itself a first-class value + (reader/printer tables are data, env-scoped)". Is that what you meant? +|=== + +== Bin B — real concepts, misapplied (quarantined) + +[cols="2,3", options="header"] +|=== +| Item | Why it is not what Gemini says + +| D∞ "convergence proofs … invariant for code generation" +| D∞ is the reason a unityped language *has* a semantics. Not a per-language + proof obligation; yields no invariant a backend could check. + +| ∂₂([T,A,S]) = [A,S] − [T,S] + [T,A]; "∂∘∂ = 0 is the invariant" +| Formula correct; meaning nil. ∂∘∂ = 0 is the *definition* of a chain + complex — it cannot fail, so it cannot be preserved. "Isolating the + friction edge [T,A]" is not an operation on programs. + +| Sheaf gluing / H¹ as distributed consensus +| A real research direction (Robinson, *Topological Signal Processing*) + with no connection to anything here. + +| λ-calculus interpreter + Y combinator "in Žiz" +| Textbook `subst`/`eval` — for a Lisp. Violates the stated triad grammar in + every clause. Worth rewriting in real Žiz later as a self-hosting smoke + test. + +| "Why Chapel was always the only correct choice" (PGAS vs MPI/Ray) +| Fair for HPC *data*, irrelevant to a bootstrap interpreter; the C++/Python + strawmen are cartoons. Real reason: the estate already has `chapeliser`. + +| 4-tier grammar hierarchy (Metal / Micro / Standard / Macro) +| A generic compiler-layering diagram with stickers. "Micro-Žiz = VAR/LAM/APP" + is just the λ-calculus. Small core + macro layer is ordinary Lisp practice + and already implied by `defmacro`. + +| Comparison to Iota/Jot/Thue/Underload +| Minimalism-for-its-own-sake esolangs; share nothing with the JEG. +|=== + +== Bin C — confabulation (discard) + +[cols="2,3", options="header"] +|=== +| Item | Why + +| Triads as the universal expression form; "control flow is dialectical friction"; `0 = (Ж Ж Ж)` +| Root-cause cascade (Finding 1); refuted by its own examples (Finding 2). + +| Distributed JEG in Chapel (`BlockDist`, `SyntheticAPriori` / `EmpiricalData` / `ReflexiveCritique`) +| A node array with **no edges**, no subjects, no provenance. Kantian kinds. + `verifyGraphIntegrity` prints and verifies nothing. Global `fetchAdd` per + claim is the one thing PGAS code must not do. + +| `ZizBootstrap` Chapel triad evaluator (`begin` thesis, `begin` antithesis, return sublation) +| Evaluates two subterms in parallel, *discards both results*, returns the + third. Computes nothing. Leaks `tRes`/`aRes`; races on unsynchronised vars. + +| Standard Lacanary / `libimaginary.so` / the Real as `/dev/null` / objet petit a GC +| Comedy. Not design. + +| Lippmann pseudo-environment tool / comptime ideology audit / "saves humanity" +| Zig, not Žiz (Finding 3); contradicts "no typechecker". + +| `val abstract_falsity = Ž.Dialectic(False)` block; "Fetishistic Disavowals" as error category +| Third syntax (Finding 3). + +| "5 Structural Hazards" / "Seventh Circle of Sartre" / "opiate of the coder" +| Gemini satirising its own output. + +| Kaomoji operators +| Hallucination drift; also breaks every tool in the pipeline. ASCII-only. + +| "14-rule EBNF" +| Counted after the fact; inconsistent; cannot express layout. + +| Chapel PGAS / C99 / LLVM / Wasm-via-Deno / SystemVerilog "HLS" / ICAP PR driver "fully detailed" +| Nothing detailed anywhere. SystemVerilog is not an HLS input; ICAP is a + Xilinx bitstream port; Deno is banned in the estate. No backend exists + before self-hosting. + +| `Cargo.toml` / "Rust workspace" +| No Rust component. tree-sitter's CLI being Rust does not put a Cargo.toml + here. + +| "Rainbow Indentation Linter", "memory singularity suppression", `anuradha`, `MarxInterpreter` +| Invented on the spot. + +| "Hand this to Mythos … 48-hour clock … final 20 minutes of sheer REPL magic" +| Theatre. No component depends on any model. + +| "The architecture of the Žiz execution engine. Source: StockCake" +| Self-describing. +|=== + +== What actually exists in this repository + +* `README.adoc`, `DESIGN.adoc`, `docs/JEG.adoc`, this file. +* `grammar/` — tree-sitter grammar + indent-stack external scanner + corpus. + Grammar DSL evaluates; scanner compiles under `-Wall -Wextra -Werror`; + `tree-sitter generate` *not yet run* (no CLI available where this was + written). +* `bootstrap/ziz0.chpl` — Chapel reader + evaluator + printer + in-memory + JEG. *Written without a Chapel compiler; expect small fixes on first run.* +* `examples/*.ziz`. +* `Justfile.ziz-fragment`. + +Nothing from bins B or C appears in any of those files. diff --git a/ziz-drop/bootstrap/ziz0.chpl b/ziz-drop/bootstrap/ziz0.chpl new file mode 100644 index 00000000..37fc05fd --- /dev/null +++ b/ziz-drop/bootstrap/ziz0.chpl @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: MPL-2.0 +// ziz0 — bootstrap interpreter for Žiz, in Chapel. +// +// Scope (milestone 0, see DESIGN.adoc §Bootstrap plan): +// * sexp reader (no layout reader yet — that arrives via tree-sitter) +// * eager evaluator with lexical closures +// * printer +// * in-memory Judgement Evidence Graph populated by observation +// +// Deliberately NOT here: FPGA anything, multi-locale anything, backends, +// triads, boundary operators. +// +// STATUS: written without a Chapel compiler available. Expect small fixes on +// first `chpl` run; the structure is what matters. Target: Chapel 2.x. +// +// Build: chpl --fast bootstrap/ziz0.chpl -o ziz0 +// Run: ./ziz0 --file=examples/hello.ziz +// ./ziz0 --file=examples/fact.ziz --jeg=observe + +use IO, Map, List; + +config const file: string = ""; +config const jeg: string = "off"; // "off" | "observe" +config const repl: bool = false; + +// -------------------------------------------------------------------------- +// Value domain (unityped: every term is a Value) +// -------------------------------------------------------------------------- + +enum Tag { Nil, Bool, Int, Real, Str, Sym, Pair, Vec, MapV, Fn, Prim, Env, Node, Claim, Err } + +class Value { + var tag: Tag; + var b: bool; + var i: int(64); + var r: real(64); + var s: string; // Str, Sym, Err message, Prim name + var car: shared Value?; // Pair + var cdr: shared Value?; + var items: list(shared Value); // Vec, Fn params + var kv: map(string, shared Value); // MapV, Env frame + var body: shared Value?; // Fn body, Claim subject + var env: shared Value?; // Fn closure env, Env parent + var prim: int; // Prim dispatch id + var nodeId: int; // CST anchor (reader-assigned) + + proc init(tag: Tag) { this.tag = tag; } +} + +type V = shared Value; + +proc mkNil(): V { return new shared Value(Tag.Nil); } +proc mkBool(x: bool): V { var v = new shared Value(Tag.Bool); v.b = x; return v; } +proc mkInt(x: int): V { var v = new shared Value(Tag.Int); v.i = x; return v; } +proc mkReal(x: real): V { var v = new shared Value(Tag.Real); v.r = x; return v; } +proc mkStr(x: string): V { var v = new shared Value(Tag.Str); v.s = x; return v; } +proc mkSym(x: string): V { var v = new shared Value(Tag.Sym); v.s = x; return v; } +proc mkErr(x: string): V { var v = new shared Value(Tag.Err); v.s = x; return v; } +proc mkPair(a: V, d: V): V { var v = new shared Value(Tag.Pair); v.car = a; v.cdr = d; return v; } +proc mkPrim(id: int, name: string): V { var v = new shared Value(Tag.Prim); v.prim = id; v.s = name; return v; } +proc mkEnv(parent: V?): V { var v = new shared Value(Tag.Env); v.env = parent; return v; } + +proc isNil(v: V): bool { return v.tag == Tag.Nil; } +proc truthy(v: V): bool { return !(v.tag == Tag.Nil || (v.tag == Tag.Bool && !v.b)); } + +proc listFrom(ref xs: list(V)): V { + var acc = mkNil(); + for idx in 0..#xs.size by -1 do acc = mkPair(xs[idx], acc); + return acc; +} + +iter listItems(v: V): V { + var cur = v; + while cur.tag == Tag.Pair { + yield cur.car!; + cur = cur.cdr!; + } +} + +proc listLen(v: V): int { var n = 0; for _ in listItems(v) do n += 1; return n; } + +// -------------------------------------------------------------------------- +// Environment (first-class; see DESIGN.adoc §Reflexivity) +// -------------------------------------------------------------------------- + +proc envLookup(e: V, name: string): V? { + var cur: V? = e; + while cur != nil { + if cur!.kv.contains(name) then return cur!.kv[name]; + cur = cur!.env; + } + return nil; +} + +proc envDefine(e: V, name: string, v: V) { e.kv[name] = v; } + +proc envSet(e: V, name: string, v: V): bool { + var cur: V? = e; + while cur != nil { + if cur!.kv.contains(name) { cur!.kv[name] = v; return true; } + cur = cur!.env; + } + return false; +} + +// -------------------------------------------------------------------------- +// Reader (sexp table only). ASCII-only surface syntax. +// -------------------------------------------------------------------------- + +record Reader { + var src: string; + var pos: int = 0; + var nextNode: int = 1; + + proc ref peek(): string { return if pos < src.size then src[pos] else ""; } + proc ref adv(): string { const c = peek(); pos += 1; return c; } + proc ref atEnd(): bool { return pos >= src.size; } + + proc ref skipWs() { + while !atEnd() { + const c = peek(); + if c == ";" { while !atEnd() && peek() != "\n" do adv(); } + else if c == " " || c == "\t" || c == "\n" || c == "\r" { adv(); } + else break; + } + } + + proc isDelim(c: string): bool { + return c == "" || c == " " || c == "\t" || c == "\n" || c == "\r" || + c == "(" || c == ")" || c == "[" || c == "]" || c == "{" || c == "}" || + c == "\"" || c == ";"; + } + + proc ref read(): V { + skipWs(); + if atEnd() then return mkErr("eof"); + const c = peek(); + select c { + when "(" { adv(); return readSeq(")"); } + when "[" { adv(); var l = readSeq("]"); var v = new shared Value(Tag.Vec); + for x in listItems(l) do v.items.pushBack(x); return v; } + when "{" { adv(); var l = readSeq("}"); var v = new shared Value(Tag.MapV); + var k: V? = nil; + for x in listItems(l) { if k == nil then k = x; else { v.kv[show(k!)] = x; k = nil; } } + return v; } + when ")" { adv(); return mkErr("unexpected )"); } + when "]" { adv(); return mkErr("unexpected ]"); } + when "}" { adv(); return mkErr("unexpected }"); } + when "'" { adv(); return mkPair(mkSym("quote"), mkPair(read(), mkNil())); } + when "`" { adv(); return mkPair(mkSym("quasiquote"), mkPair(read(), mkNil())); } + when "," { adv(); + if peek() == "@" { adv(); return mkPair(mkSym("unquote-splicing"), mkPair(read(), mkNil())); } + return mkPair(mkSym("unquote"), mkPair(read(), mkNil())); } + when "\"" { adv(); return readString(); } + otherwise { return readAtom(); } + } + } + + proc ref readSeq(close: string): V { + var xs: list(V); + while true { + skipWs(); + if atEnd() then return mkErr("unterminated list"); + if peek() == close { adv(); break; } + xs.pushBack(read()); + } + var v = listFrom(xs); + if v.tag == Tag.Pair { v.nodeId = nextNode; nextNode += 1; } + return v; + } + + proc ref readString(): V { + var out = ""; + while !atEnd() { + const c = adv(); + if c == "\"" then return mkStr(out); + if c == "\\" { + const e = adv(); + select e { + when "n" do out += "\n"; + when "t" do out += "\t"; + when "r" do out += "\r"; + when "\"" do out += "\""; + when "\\" do out += "\\"; + otherwise do out += e; + } + } else out += c; + } + return mkErr("unterminated string"); + } + + proc ref readAtom(): V { + var tok = ""; + while !isDelim(peek()) do tok += adv(); + if tok == "nil" then return mkNil(); + if tok == "true" then return mkBool(true); + if tok == "false" then return mkBool(false); + try { return mkInt(tok: int); } catch { } + try { if tok.find(".") != -1 then return mkReal(tok: real); } catch { } + var v = mkSym(tok); + v.nodeId = nextNode; nextNode += 1; + return v; + } +} + +// -------------------------------------------------------------------------- +// Printer +// -------------------------------------------------------------------------- + +proc show(v: V): string { + select v.tag { + when Tag.Nil do return "nil"; + when Tag.Bool do return if v.b then "true" else "false"; + when Tag.Int do return v.i: string; + when Tag.Real do return v.r: string; + when Tag.Str do return "\"" + v.s.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + when Tag.Sym do return v.s; + when Tag.Err do return "#"; + when Tag.Prim do return "#"; + when Tag.Fn do return "#"; + when Tag.Env do return "#"; + when Tag.Node do return "(node " + v.nodeId: string + ")"; + when Tag.Claim do return "#"; + when Tag.Vec { + var s = "["; + for (x, k) in zip(v.items, 0..) { if k > 0 then s += " "; s += show(x); } + return s + "]"; + } + when Tag.MapV { + var s = "{"; var first = true; + for k in v.kv.keys() { if !first then s += " "; first = false; s += k + " " + show(v.kv[k]); } + return s + "}"; + } + when Tag.Pair { + var s = "("; var cur = v; var first = true; + while cur.tag == Tag.Pair { + if !first then s += " "; first = false; + s += show(cur.car!); + cur = cur.cdr!; + } + if !isNil(cur) then s += " . " + show(cur); + return s + ")"; + } + } + return "#"; +} + +// -------------------------------------------------------------------------- +// Judgement Evidence Graph (in-memory; docs/JEG.adoc) +// +// Litmus test: apply() below is the code path where evaluating (fact 3) +// creates evidence edges. +// -------------------------------------------------------------------------- + +record Judgement { var subject: string; var predicate: string; var args: string; } +record Evidence { var forId: int; var kind: string; var by: string; var where_: string; } + +class JEG { + var judgements: list(Judgement); + var index: map(string, int); // structural key -> id (hash-consing) + var evidence: list(Evidence); + + proc assert_(subject: string, predicate: string, args: string, + kind: string, by: string, where_: string) { + const key = subject + "\x01" + predicate + "\x01" + args; + var id: int; + if index.contains(key) then id = index[key]; + else { + judgements.pushBack(new Judgement(subject, predicate, args)); + id = judgements.size - 1; + index[key] = id; + } + evidence.pushBack(new Evidence(id, kind, by, where_)); + } + + proc dump() { + writeln("; --- JEG: ", judgements.size, " judgements, ", evidence.size, " evidence edges"); + for (j, id) in zip(judgements, 0..) { + var kinds: map(string, int); + for e in evidence do if e.forId == id then kinds[e.kind] += 1; + var ks = ""; + for k in kinds.keys() do ks += " " + k + "=" + kinds[k]: string; + writeln("(", j.subject, " ", j.predicate, if j.args != "" then " " + j.args else "", ") ;", ks); + } + } +} + +var theJEG = new shared JEG(); + +proc subjectOf(v: V): string { + if v.tag == Tag.Sym then return "sym:" + v.s; + if v.nodeId != 0 then return "node:" + v.nodeId: string; + return "anon:" + show(v); +} + +proc tagName(v: V): string { return ":" + (v.tag: string).toLower(); } + +// -------------------------------------------------------------------------- +// Primitives +// -------------------------------------------------------------------------- + +enum P { Add, Sub, Mul, Div, Lt, Gt, Le, Ge, NumEq, Eq, Cons, Car, Cdr, ListP, Print, + Eval, CurrentEnv, JegDump, Not, IsNil, Len, Str } + +proc numOf(v: V): real { return if v.tag == Tag.Int then v.i: real else v.r; } + +proc arith(op: P, ref args: list(V)): V { + if args.size == 0 then return mkErr("arity"); + var allInt = true; + for a in args do if a.tag != Tag.Int then allInt = false; + if allInt { + var acc = args[0].i; + if args.size == 1 && op == P.Sub then return mkInt(-acc); + for idx in 1.. numOf(args[1])); + when P.Le do return mkBool(numOf(args[0]) <= numOf(args[1])); + when P.Ge do return mkBool(numOf(args[0]) >= numOf(args[1])); + when P.NumEq do return mkBool(numOf(args[0]) == numOf(args[1])); + when P.Eq do return mkBool(valEq(args[0], args[1])); + when P.Cons do return mkPair(args[0], args[1]); + when P.Car do return if args[0].tag == Tag.Pair then args[0].car! else mkErr("car of non-pair"); + when P.Cdr do return if args[0].tag == Tag.Pair then args[0].cdr! else mkErr("cdr of non-pair"); + when P.ListP do return listFrom(args); + when P.Print { for a in args do write(if a.tag == Tag.Str then a.s else show(a)); writeln(); return mkNil(); } + when P.Eval do return eval(args[0], if args.size > 1 then args[1] else env); + when P.CurrentEnv do return env; + when P.JegDump { theJEG.dump(); return mkNil(); } + when P.Not do return mkBool(!truthy(args[0])); + when P.IsNil do return mkBool(isNil(args[0])); + when P.Len do return mkInt(if args[0].tag == Tag.Vec then args[0].items.size else listLen(args[0])); + when P.Str { var s = ""; for a in args do s += if a.tag == Tag.Str then a.s else show(a); return mkStr(s); } + } + return mkErr("unknown prim"); +} + +proc installPrims(g: V) { + const names = ["+", "-", "*", "/", "<", ">", "<=", ">=", "=", "eq?", "cons", "car", "cdr", + "list", "print", "eval", "current-env", "jeg-dump", "not", "nil?", "len", "str"]; + for (n, k) in zip(names, 0..) do envDefine(g, n, mkPrim(k, n)); +} + +// -------------------------------------------------------------------------- +// Evaluator +// -------------------------------------------------------------------------- + +proc quasi(x: V, env: V): V { + if x.tag != Tag.Pair then return x; + const head = x.car!; + if head.tag == Tag.Sym && head.s == "unquote" then return eval(x.cdr!.car!, env); + var out: list(V); + for item in listItems(x) { + if item.tag == Tag.Pair && item.car!.tag == Tag.Sym && item.car!.s == "unquote-splicing" { + for y in listItems(eval(item.cdr!.car!, env)) do out.pushBack(y); + } else out.pushBack(quasi(item, env)); + } + return listFrom(out); +} + +proc apply(f: V, ref args: list(V), env: V, callNode: V): V { + const subj = subjectOf(callNode.car!); + const where_ = "node:" + callNode.nodeId: string; + if jeg == "observe" { + theJEG.assert_(subj, ":callable", "", "observed", "ziz0", where_); + theJEG.assert_(subj, ":arity", args.size: string, "observed", "ziz0", where_); + for (a, k) in zip(args, 0..) do + theJEG.assert_(subj, ":takes", k: string + " " + tagName(a), "observed", "ziz0", where_); + } + var result: V; + select f.tag { + when Tag.Prim do result = applyPrim(f, args, env); + when Tag.Fn { + if f.items.size != args.size then return mkErr("arity mismatch calling fn"); + var frame = mkEnv(f.env); + for (p, a) in zip(f.items, args) do envDefine(frame, p.s, a); + result = eval(f.body!, frame); + } + otherwise do return mkErr("not callable: " + show(f)); + } + if jeg == "observe" then + theJEG.assert_(subj, ":returns", tagName(result), "observed", "ziz0", where_); + return result; +} + +proc eval(x: V, env: V): V { + select x.tag { + when Tag.Sym { + const v = envLookup(env, x.s); + return if v != nil then v! else mkErr("unbound: " + x.s); + } + when Tag.Pair { + const head = x.car!; + if head.tag == Tag.Sym { + select head.s { + when "quote" do return x.cdr!.car!; + when "quasiquote" do return quasi(x.cdr!.car!, env); + when "if" { + const c = eval(x.cdr!.car!, env); + const rest = x.cdr!.cdr!; + if truthy(c) then return eval(rest.car!, env); + return if rest.cdr!.tag == Tag.Pair then eval(rest.cdr!.car!, env) else mkNil(); + } + when "define" { + const target = x.cdr!.car!; + if target.tag == Tag.Pair { + var fn = new shared Value(Tag.Fn); + for p in listItems(target.cdr!) do fn.items.pushBack(p); + fn.body = mkPair(mkSym("begin"), x.cdr!.cdr!); + fn.env = env; + envDefine(env, target.car!.s, fn); + if jeg == "observe" then + theJEG.assert_("sym:" + target.car!.s, ":defined-at", "", "observed", "ziz0", + "node:" + x.nodeId: string); + return mkSym(target.car!.s); + } + const v = eval(x.cdr!.cdr!.car!, env); + envDefine(env, target.s, v); + return mkSym(target.s); + } + when "set!" { + const v = eval(x.cdr!.cdr!.car!, env); + return if envSet(env, x.cdr!.car!.s, v) then v else mkErr("set! of unbound"); + } + when "lambda" { + var fn = new shared Value(Tag.Fn); + for p in listItems(x.cdr!.car!) do fn.items.pushBack(p); + fn.body = mkPair(mkSym("begin"), x.cdr!.cdr!); + fn.env = env; + return fn; + } + when "begin" { + var last = mkNil(); + for form in listItems(x.cdr!) do last = eval(form, env); + return last; + } + when "let" { + var frame = mkEnv(env); + for binding in listItems(x.cdr!.car!) do + envDefine(frame, binding.car!.s, eval(binding.cdr!.car!, env)); + var last = mkNil(); + for form in listItems(x.cdr!.cdr!) do last = eval(form, frame); + return last; + } + when "claim" { + // (claim subject predicate args... :evidence kind [:by who]) + const subj = x.cdr!.car!; + var rest: list(V); + for item in listItems(x.cdr!.cdr!) do rest.pushBack(item); + var predicate = "", args = "", kind = "asserted", by = "author"; + var idx = 0; + while idx < rest.size { + const it = rest[idx]; + if it.tag == Tag.Sym && it.s == ":evidence" { kind = rest[idx+1].s.strip(":"); idx += 2; continue; } + if it.tag == Tag.Sym && it.s == ":by" { by = show(rest[idx+1]).strip("\""); idx += 2; continue; } + if predicate == "" then predicate = show(it); + else args += (if args != "" then " " else "") + show(it); + idx += 1; + } + theJEG.assert_(subjectOf(subj), predicate, args, kind, by, "node:" + x.nodeId: string); + var c = new shared Value(Tag.Claim); c.body = subj; + return c; + } + } + } + const f = eval(head, env); + if f.tag == Tag.Err then return f; + var args: list(V); + for a in listItems(x.cdr!) { + const v = eval(a, env); + if v.tag == Tag.Err then return v; + args.pushBack(v); + } + return apply(f, args, env, x); + } + otherwise do return x; // self-evaluating + } +} + +// -------------------------------------------------------------------------- +// Driver +// -------------------------------------------------------------------------- + +proc runSource(src: string, env: V, echo: bool) { + var rd = new Reader(src); + while true { + var form = rd.read(); + if form.tag == Tag.Err && form.s == "eof" then break; + const v = eval(form, env); + if echo || v.tag == Tag.Err then writeln(if v.tag == Tag.Err then ";; " + show(v) else show(v)); + } +} + +proc main() { + var global = mkEnv(nil); + installPrims(global); + + if file != "" { + var src: string; + try { + var f = open(file, ioMode.r); + var r = f.reader(); + r.readAll(src); + r.close(); f.close(); + } catch e { + writeln("cannot read ", file, ": ", e.message()); + exit(1); + } + runSource(src, global, echo=false); + if jeg == "observe" then theJEG.dump(); + } + + if repl || file == "" { + writeln("ziz0 -- unityped, homoiconic, no typechecker. Ctrl-D exits."); + var line: string; + while true { + write("ziz> "); + if !stdin.readLine(line) then break; + runSource(line, global, echo=true); + } + } +} diff --git a/ziz-drop/docs/JEG.adoc b/ziz-drop/docs/JEG.adoc new file mode 100644 index 00000000..9a5083a6 --- /dev/null +++ b/ziz-drop/docs/JEG.adoc @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 += Judgement Evidence Graph (JEG) +:toc: macro +:icons: font + +toc::[] + +== What it is, and what it is not + +A typechecker answers "is this program well-formed under theory T?" and stops +you if not. The JEG answers "what do we currently believe about each term, +and why?" and never stops you. + +It is *not* gradual typing, *not* soft typing, *not* a linter with a +different hat, and *not* a log of "dialectical sublations". Those all +compute a verdict or narrate. The JEG stores *claims with provenance* and +lets tools compute whatever verdict they like, later — including "no +verdict, insufficient evidence". + +Litmus test for any implementation claiming to be a JEG: *show the code path +where evaluating `(fact 3)` creates an evidence edge.* If there isn't one, it +is not a JEG. + +== Model + +---- +Subject := node-id | symbol | (subject . path) +Judgement := (subject, predicate, args...) +EvidenceKind := :asserted | :inferred | :observed | :tested | :contradicted +Evidence := (kind, provenance, [supports: judgement-id...]) +Provenance := { :by , :at , :where , :via } +Graph := Judgement nodes + Evidence edges (judgement -> judgement | judgement -> ground) +---- + +* A judgement with *no* incoming evidence is *unsupported*. +* A judgement with a `:contradicted` edge is *contested*. +* Evidence kinds are ordered by default strength for *display only*: + `:tested` > `:observed` > `:inferred` > `:asserted`. +* Nothing is deleted during a run. Retraction is a new `:contradicted` edge. +* Judgements are *hash-consed* on `(subject, predicate, args)`: ten + observations of `fact :arity 1` are one judgement with ten evidence edges, + not ten judgements. + +== Predicate vocabulary (open; these are the ones `ziz0` emits) + +[cols="1,2", options="header"] +|=== +| Predicate | Meaning + +| `:callable` | subject was applied and application proceeded +| `:arity n` | subject was applied to `n` args +| `:returns tag` | application returned a value with `tag` +| `:takes i tag` | argument `i` had `tag` at a call site +| `:pure` | no `set!`, no side-effecting prim, during observed calls +| `:raised map` | an error value was produced +| `:tested-by node-id` | a test form referenced the subject +| `:defined-at node-id` | binding site +| `:shadowed-by node-id` | rebinding in an inner scope +| `:reader sym` | which reader table produced this node (metaiconic provenance) +|=== + +== Surface syntax + +---- +(claim fact :arity 1 :evidence :asserted) +(claim fact :returns :int :evidence :tested :by "examples/fact-test.ziz") +(claim (node 0x2f1a) :pure :evidence :inferred :via boggs) +---- + +`claim` is a special form: its first argument is a *subject expression* +(symbol, `(node id)`, or a quoted form) and is not evaluated. + +== Runtime behaviour in `ziz0` + +Each application `(f a b)` under `--jeg=observe` appends, with +provenance `{:by ziz0 :where }`: + +* `(f :callable)`, `(f :arity 2)`, `(f :takes 0 )`, + `(f :takes 1 )`, `(f :returns )` — all `:observed`. + +Cheap: a hash-cons plus an edge. Off by default until measured. + +== Querying + +---- +(jeg-query '(?s :returns :int)) ; all subjects observed returning int +(jeg-support 'fact) ; every judgement about fact with evidence +(jeg-unsupported) ; judgements nobody has evidence for +(jeg-contested) ; judgements with contradicting evidence +---- + +== On-disk format + +Sidecar per source file: `foo.ziz` → `foo.jeg.a2ml`. A2ML because it is the +estate's machine-readable format; schema to be registered in +`hyperpolymath/standards` under `1-formats/a2ml/` once stable. + +---- +# A2ML jeg/0 +[meta] +source = "examples/fact.ziz" +source-sha256 = "..." +grammar = "tree-sitter-ziz@0.0.1" + +[[judgement]] +id = "j0001" +subject = "sym:fact" +predicate = "arity" +args = [1] + +[[evidence]] +for = "j0001" +kind = "observed" +by = "ziz0" +at = "2026-09-24T15:02:11Z" +where = "examples/fact.ziz:3:1" +---- + +== Merge semantics across runs + +Union of judgements (by structural id); union of evidence. Node-keyed +subjects are re-anchored through tree-sitter's edit log; if a node no longer +exists its judgements are kept and marked `:orphaned` (a judgement about a +judgement — the graph is reflexive too). + +== Interaction with tree-sitter + +* Node ids: `(start_byte, end_byte, kind)` hashed with grammar version — + stable across whitespace-only edits after re-anchoring. +* Editor integration: colour a symbol by *strength of evidence* rather than + by type. Unsupported = plain; observed = one tone; tested = another; + contested = warning. (A display policy. Not a component. Not a "rainbow + linter".) + +== Distribution (deferred) + +If the JEG is ever sharded across Chapel locales, the natural key is +*source file / subject*, not a flat integer id handed out by a global +atomic counter. A single `fetchAdd` on every claim would serialise the whole +cluster on one locale — the one thing PGAS code must not do. Not a milestone +0–3 concern. + +== Open questions (honest list) + +* Cost of `--jeg=observe` under deep recursion. +* Whether `:pure` should be *asserted by default* for lambdas with no `set!` + in the body, or left unsupported until observed. +* Provenance for macro-expanded code: expansion node, origin node, or both + with an `:expanded-from` edge? (Leaning: both.) +* Graph size limits and a compaction policy. diff --git a/ziz-drop/examples/fact.ziz b/ziz-drop/examples/fact.ziz new file mode 100644 index 00000000..2639dc16 --- /dev/null +++ b/ziz-drop/examples/fact.ziz @@ -0,0 +1,14 @@ +; SPDX-License-Identifier: MPL-2.0 +; Run with: ziz0 --file=examples/fact.ziz --jeg=observe +; No typechecker. The JEG records what was *observed*: fact was applied +; with arity 1, took :int, returned :int. Nobody declared that; it was seen. + +(define (fact n) + (if (= n 0) + 1 + (* n (fact (- n 1))))) + +(claim fact :arity 1 :evidence :asserted) +(claim fact :pure :evidence :asserted :by "author") + +(print (fact 10)) diff --git a/ziz-drop/examples/hello.ziz b/ziz-drop/examples/hello.ziz new file mode 100644 index 00000000..ad95100b --- /dev/null +++ b/ziz-drop/examples/hello.ziz @@ -0,0 +1,2 @@ +; SPDX-License-Identifier: MPL-2.0 +(print "hello, ziz") diff --git a/ziz-drop/examples/layout.ziz b/ziz-drop/examples/layout.ziz new file mode 100644 index 00000000..b09ea4cb --- /dev/null +++ b/ziz-drop/examples/layout.ziz @@ -0,0 +1,10 @@ +; SPDX-License-Identifier: MPL-2.0 +; The off-side surface. Reads identically to fact.ziz ONCE the layout reader +; lands (tree-sitter external scanner). ziz0 milestone 0 will NOT read this. + +define (fact n) + if (= n 0) + 1 + * n (fact (- n 1)) + +print (fact 10) diff --git a/ziz-drop/examples/quote-eval.ziz b/ziz-drop/examples/quote-eval.ziz new file mode 100644 index 00000000..66f9b4cd --- /dev/null +++ b/ziz-drop/examples/quote-eval.ziz @@ -0,0 +1,11 @@ +; SPDX-License-Identifier: MPL-2.0 +; Homoiconicity: code is a list; lists are code. + +(define code '(+ 1 2 3)) +(print code) ; (+ 1 2 3) +(print (car code)) ; + +(print (eval code)) ; 6 + +(define (make-adder-form n) `(lambda (x) (+ x ,n))) +(define add5 (eval (make-adder-form 5))) +(print (add5 10)) ; 15 diff --git a/ziz-drop/examples/reflexive.ziz b/ziz-drop/examples/reflexive.ziz new file mode 100644 index 00000000..d7f17a1e --- /dev/null +++ b/ziz-drop/examples/reflexive.ziz @@ -0,0 +1,12 @@ +; SPDX-License-Identifier: MPL-2.0 +; Reflexivity: the environment and the evidence graph are values. + +(define x 1) +(define e (current-env)) +(print e) + +(define (twice f v) (f (f v))) +(print (twice (lambda (n) (* n 2)) 21)) + +; Ask the graph what it has seen so far (observe mode). +(jeg-dump) diff --git a/ziz-drop/grammar/grammar.js b/ziz-drop/grammar/grammar.js new file mode 100644 index 00000000..2d6f9e62 --- /dev/null +++ b/ziz-drop/grammar/grammar.js @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MPL-2.0 +// tree-sitter grammar for Žiz — S-expression core + off-side layout. +// +// The layout (off-side) rule is NOT context-free. It is handled by the +// external scanner in src/scanner.c, which emits _indent / _dedent / _newline +// from an indent stack. Inside explicit brackets the scanner suppresses +// layout tokens, so parenthesised code is layout-insensitive. +// +// Surface syntax is ASCII-only. String *contents* may be any UTF-8. + +const SYM_START = /[A-Za-z_+\-*\/<>=!?%&|^~$]/; +const SYM_REST = /[A-Za-z0-9_+\-*\/<>=!?%&|^~$.:]*/; + +module.exports = grammar({ + name: 'ziz', + + externals: $ => [ + $._indent, + $._dedent, + $._newline, + $.error_sentinel, // lets the scanner detect error recovery + ], + + extras: $ => [ + /[ \t\r]/, + $.comment, + $.line_continuation, + ], + + word: $ => $.symbol, + + rules: { + source_file: $ => repeat(choice($._layout_form, $._newline)), + + // ---- layout (off-side) surface ------------------------------------ + // A logical line with >1 item, optionally followed by an indented block, + // is an implicit list whose tail is the block's lines. A single bare + // atom on a line is itself, not a one-element list. + _layout_form: $ => choice( + $.layout_list, + seq($._form, $._newline), + ), + + layout_list: $ => prec.right(seq( + $._form, + repeat1($._form), + optional($.layout_block), + $._newline, + )), + + layout_block: $ => seq( + $._indent, + repeat1($._layout_form), + $._dedent, + ), + + // ---- S-expression core -------------------------------------------- + _form: $ => choice( + $.list, + $.vector, + $.map, + $.quote, + $.quasiquote, + $.unquote_splicing, + $.unquote, + $._atom, + ), + + list: $ => seq('(', repeat($._form), ')'), + vector: $ => seq('[', repeat($._form), ']'), + map: $ => seq('{', repeat(seq(field('key', $._form), field('value', $._form))), '}'), + + quote: $ => seq("'", $._form), + quasiquote: $ => seq('`', $._form), + unquote_splicing: $ => seq(',@', $._form), + unquote: $ => seq(',', $._form), + + _atom: $ => choice( + $.nil, + $.boolean, + $.real, + $.integer, + $.string, + $.keyword, + $.symbol, + ), + + nil: $ => 'nil', + boolean: $ => choice('true', 'false'), + + // real before integer so the longer match wins + real: $ => token(/-?[0-9]+\.[0-9]+([eE][-+]?[0-9]+)?/), + integer: $ => token(/-?[0-9]+/), + + string: $ => seq( + '"', + repeat(choice( + token.immediate(prec(1, /[^"\\\n]+/)), + $.escape_sequence, + )), + '"', + ), + escape_sequence: $ => token.immediate(/\\[nrt"\\]|\\u[0-9a-fA-F]{4}/), + + keyword: $ => token(seq(':', SYM_START, SYM_REST)), + symbol: $ => token(seq(SYM_START, SYM_REST)), + + comment: $ => token(seq(';', /.*/)), + line_continuation: $ => token(seq('\\', /\r?\n/)), + }, +}); diff --git a/ziz-drop/grammar/package.json b/ziz-drop/grammar/package.json new file mode 100644 index 00000000..b0ed38ff --- /dev/null +++ b/ziz-drop/grammar/package.json @@ -0,0 +1,8 @@ +{ + "name": "tree-sitter-ziz", + "version": "0.0.1", + "description": "tree-sitter grammar for the Žiz language", + "license": "MPL-2.0", + "main": "bindings/node", + "tree-sitter": [{ "scope": "source.ziz", "file-types": ["ziz"] }] +} diff --git a/ziz-drop/grammar/src/scanner.c b/ziz-drop/grammar/src/scanner.c new file mode 100644 index 00000000..cb81ba34 --- /dev/null +++ b/ziz-drop/grammar/src/scanner.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: MPL-2.0 +// External scanner for tree-sitter-ziz: off-side (indentation) layout. +// +// Emits INDENT / DEDENT / NEWLINE from an indent stack. Layout is suppressed +// while bracket_depth > 0, i.e. inside ( ) [ ] { }, so explicit S-expressions +// are layout-insensitive as DESIGN.adoc requires. + +#include "tree_sitter/parser.h" +#include +#include + +enum TokenType { INDENT, DEDENT, NEWLINE, ERROR_SENTINEL }; + +#define MAX_INDENTS 128 + +typedef struct { + uint16_t indents[MAX_INDENTS]; + uint8_t depth; // indent stack height (indents[0] is always 0) + uint16_t bracket_depth; + uint8_t pending_dedents; +} Scanner; + +static inline void push(Scanner *s, uint16_t col) { + if (s->depth < MAX_INDENTS) s->indents[s->depth++] = col; +} + +void *tree_sitter_ziz_external_scanner_create(void) { + Scanner *s = (Scanner *)calloc(1, sizeof(Scanner)); + push(s, 0); + return s; +} + +void tree_sitter_ziz_external_scanner_destroy(void *p) { free(p); } + +unsigned tree_sitter_ziz_external_scanner_serialize(void *p, char *buf) { + Scanner *s = (Scanner *)p; + unsigned n = 0; + buf[n++] = (char)s->depth; + buf[n++] = (char)(s->bracket_depth & 0xff); + buf[n++] = (char)(s->bracket_depth >> 8); + buf[n++] = (char)s->pending_dedents; + for (unsigned i = 0; i < s->depth && n + 1 < TREE_SITTER_SERIALIZATION_BUFFER_SIZE; i++) { + buf[n++] = (char)(s->indents[i] & 0xff); + buf[n++] = (char)(s->indents[i] >> 8); + } + return n; +} + +void tree_sitter_ziz_external_scanner_deserialize(void *p, const char *buf, unsigned len) { + Scanner *s = (Scanner *)p; + memset(s, 0, sizeof(*s)); + if (len == 0) { push(s, 0); return; } + unsigned n = 0; + uint8_t depth = (uint8_t)buf[n++]; + s->bracket_depth = (uint8_t)buf[n] | ((uint8_t)buf[n + 1] << 8); n += 2; + s->pending_dedents = (uint8_t)buf[n++]; + for (unsigned i = 0; i < depth && n + 1 < len; i++) { + s->indents[s->depth++] = (uint8_t)buf[n] | ((uint8_t)buf[n + 1] << 8); + n += 2; + } + if (s->depth == 0) push(s, 0); +} + +static void skip_comment(TSLexer *lx) { + while (lx->lookahead && lx->lookahead != '\n') lx->advance(lx, true); +} + +bool tree_sitter_ziz_external_scanner_scan(void *p, TSLexer *lx, const bool *valid) { + Scanner *s = (Scanner *)p; + + if (valid[ERROR_SENTINEL]) return false; + + if (s->pending_dedents > 0 && valid[DEDENT]) { + s->pending_dedents--; + s->depth--; + lx->result_symbol = DEDENT; + return true; + } + + // Observe brackets without consuming them (the internal lexer does that). + if (lx->lookahead == '(' || lx->lookahead == '[' || lx->lookahead == '{') { + s->bracket_depth++; + return false; + } + if (lx->lookahead == ')' || lx->lookahead == ']' || lx->lookahead == '}') { + if (s->bracket_depth > 0) s->bracket_depth--; + return false; + } + + if (s->bracket_depth > 0) return false; + + bool saw_newline = false; + uint16_t col = 0; + + for (;;) { + if (lx->lookahead == '\n') { + saw_newline = true; col = 0; + lx->advance(lx, true); + } else if (lx->lookahead == '\r') { + lx->advance(lx, true); + } else if (lx->lookahead == ' ') { + col++; lx->advance(lx, true); + } else if (lx->lookahead == '\t') { + col = (uint16_t)((col / 8 + 1) * 8); lx->advance(lx, true); + } else if (lx->lookahead == ';') { + skip_comment(lx); + } else if (lx->lookahead == '\\') { + lx->mark_end(lx); + lx->advance(lx, true); + if (lx->lookahead == '\r') lx->advance(lx, true); + if (lx->lookahead == '\n') { lx->advance(lx, true); col = 0; continue; } + return false; + } else { + break; + } + } + + if (!saw_newline && lx->lookahead != 0) return false; + if (lx->lookahead == '\n') return false; + + uint16_t top = s->indents[s->depth - 1]; + + if (lx->eof(lx)) { + if (s->depth > 1 && valid[DEDENT]) { + s->pending_dedents = (uint8_t)(s->depth - 2); + s->depth--; + lx->result_symbol = DEDENT; + return true; + } + if (valid[NEWLINE]) { lx->result_symbol = NEWLINE; return true; } + return false; + } + + if (col > top) { + if (valid[INDENT]) { + push(s, col); + lx->result_symbol = INDENT; + return true; + } + if (valid[NEWLINE]) { lx->result_symbol = NEWLINE; return true; } + return false; + } + + if (col < top) { + uint8_t levels = 0; + for (int i = s->depth - 1; i > 0 && s->indents[i] > col; i--) levels++; + if (levels > 0 && valid[DEDENT]) { + s->pending_dedents = (uint8_t)(levels - 1); + s->depth--; + lx->result_symbol = DEDENT; + return true; + } + } + + if (valid[NEWLINE]) { lx->result_symbol = NEWLINE; return true; } + return false; +} diff --git a/ziz-drop/grammar/test/corpus/basics.txt b/ziz-drop/grammar/test/corpus/basics.txt new file mode 100644 index 00000000..4c1c2a3a --- /dev/null +++ b/ziz-drop/grammar/test/corpus/basics.txt @@ -0,0 +1,34 @@ +================================================================================ +sexp list +================================================================================ +(define (fact n) (if (= n 0) 1 (* n (fact (- n 1))))) +-------------------------------------------------------------------------------- +(source_file + (list (symbol) (list (symbol) (symbol)) + (list (symbol) (list (symbol) (symbol) (integer)) (integer) + (list (symbol) (symbol) (list (symbol) (list (symbol) (symbol) (integer))))))) + +================================================================================ +layout list +================================================================================ +define (fact n) + if (= n 0) + 1 + * n (fact (- n 1)) +-------------------------------------------------------------------------------- +(source_file + (layout_list (symbol) (list (symbol) (symbol)) + (layout_block + (layout_list (symbol) (list (symbol) (symbol) (integer)) + (layout_block + (integer) + (layout_list (symbol) (symbol) (list (symbol) (list (symbol) (symbol) (integer))))))))) + +================================================================================ +atoms +================================================================================ +nil true 42 -7 3.14 "s\"t" :kw sym-bol? [1 2] {:a 1} +-------------------------------------------------------------------------------- +(source_file + (layout_list (nil) (boolean) (integer) (integer) (real) (string (escape_sequence)) (keyword) (symbol) + (vector (integer) (integer)) (map (keyword) (integer)))) diff --git a/ziz-drop/grammar/tree-sitter.json b/ziz-drop/grammar/tree-sitter.json new file mode 100644 index 00000000..fd9188b6 --- /dev/null +++ b/ziz-drop/grammar/tree-sitter.json @@ -0,0 +1,4 @@ +{ + "grammars": [{ "name": "ziz", "camelcase": "Ziz", "scope": "source.ziz", "path": ".", "file-types": ["ziz"] }], + "metadata": { "version": "0.0.1", "license": "MPL-2.0", "description": "tree-sitter grammar for Žiz" } +}