Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/execution-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ descriptions, and commit messages*
- [x] **Unit 6: macros.** Landed 2026-08-16 (PR 52) as `defmacro` with `qq` and `unquote`, classic Chialisp semantics stated in `docs/lang/macros.md`. Decisions by Evan, 2026-08-16: macros only, includes split to their own unit below (skeleton change under ground rule 6). `if` and `list` stay compiler forms, so macros cannot shadow built-ins. Expansion is depth-capped and cost-budgeted, a recorded deviation from Chialisp's unbounded expansion. No `function` or `com` reflection form, the laziness expressiveness gap recorded in the language doc. No refactor rode along, the shared emission-primitives cleanup stays a candidate for a standalone PR. Expansion runs as a source pre-pass before reachability, macro bodies compile at declaration against earlier macros only, and read-back diverges from clvm_tools by one hop only (decision by Evan, 2026-08-16, steelmanned both ways, then narrowed twice as four review rounds showed every wider evidence scheme unsound on post-reader bytes): names the caller writes in a call's own arguments error when unresolved, REPL def spellings are barred resolution-side, and capture plus stale template spellings stay as Chialisp has them, documented sharp edges. The three new reserved words are a deliberate source and symbol-file compatibility break for earlier programs that used them as names, pinned by a loader test. Compiled representatives pinned in `vectors/vm/macro-programs.json`. Kept after the 2026-08-16 public-record review (decision by Evan): the fixed special forms and capped expansion avoid the macro-built-language failure mode on that record, and re-evaluation stays open at unit 7. Reversed by unit 6c the same day.
- [x] **Unit 6b: includes and the compile-time forms.** Split from unit 6 (decision by Evan, 2026-08-16): an include mechanism needs its own recorded decision against the self-containment rule, a program compiling identically pasted into the REPL. Scope expanded and resequenced ahead of unit 7 (decision by Evan, 2026-08-16, on a two-corpus census of tibetswap and chia-gaming): source-level include of shared constants and functions, computed compile-time constants, and inline functions. Every production puzzle file in both corpora imports shared definitions, and tibetswap defines nine of every ten functions inline. The census corrected the earlier deferral's premise: chia-gaming never abandoned source-level import, its compiled-sibling hash plumbing lives in its build layer outside the language, and that import stays deferred with `bitlisp-compile -T` covering the need manually in v0. `let` stays out, two uses across both corpora, both in tests. `assign` stays held on unit 7 evidence with a pre-registered trigger: benchmark-puzzle helpers that exist only to name intermediate values. Landed 2026-08-16 as `include`, computed `defconstant`, and `defun-inline`, stated in `docs/lang/language.md`. Decisions by Evan, 2026-08-16, via the approved unit plan: the self-containment rule is amended to the form plus its include files resolved through the same explicit search path everywhere, a repeat include dedupes by resolved file and a cycle errors where the classic reference dies on the collision or recurses without bound, `defconstant` evaluates its value on the reference VM under the default budget (a pinned break with unit 4's verbatim semantics, the modern defconst behavior under the classic keyword), and `defun-inline` keeps call-by-name laziness while closing classic's probe-verified sharp edges: arity checked, quoted content untouched, shadowing impossible, expansion depth-capped. Compiled representatives joined `compiled-programs.json`.
- [x] **Unit 6c: the macro reversal.** Landed 2026-08-16 as the removal of `defmacro`, `qq`, and `unquote` and the addition of `assert`, `and`, and `or` as fixed compiler forms with classic utility_macros semantics, reversing unit 6 (decision by Evan, 2026-08-16, both sides steelmanned under ground rule 3). The evidence: three usage surveys (Chia's canonical 91-puzzle corpus, the corpora vendored in references/, and chia-gaming) found no novel macro in any deployed puzzle, short-circuit assert, and, and or the entire production vocabulary, and chia-gaming's production referee choosing built-in destructuring over the structural macro sitting unbuilt beside it. The reserved-word set change breaks compatibility in both directions, pinned by the loader test. Compiled representatives joined `vectors/vm/compiled-programs.json`, and `macro-programs.json` left with the feature.
- [ ] **Unit 6d: the forms two puzzles asked for** (added 2026-08-22, decision by Evan, skeleton change under ground rule 6): `let` with several bindings, compiled as the naming helpers are written by hand, `list*` consing its arguments onto a tail, the list library completed, and a shared puzzle test harness. Evidence: the eight authoring observations in `docs/puzzles/singleton.md`. Declared widths for curried values stay on the typed v1 ledger. Sequenced after the identity-assert rework and before the payment pool, so the remaining puzzles are written with the forms rather than rewritten.
- [x] **Unit 6d: the forms two puzzles asked for** (added 2026-08-22, decision by Evan, skeleton change under ground rule 6): `let` with several bindings, compiled as the naming helpers are written by hand, `list*` consing its arguments onto a tail, the list library completed, and a shared puzzle test harness. Evidence: the eight authoring observations in `docs/puzzles/singleton.md`. Declared widths for curried values stay on the typed v1 ledger. Sequenced after the identity-assert rework and before the payment pool, so the remaining puzzles are written with the forms rather than rewritten. Landed 2026-08-23: `let` compiles as the one-shot naming helper with parallel bindings matching the modern Chialisp dialect, `list*` is a recorded tooling divergence, `append`, `reverse`, `sum`, and `last` complete `puzzles/lib/list.blib`, and `python/tests/puzzle_support.py` owns the shared harness. Compiled representatives joined `compiled-programs.json`, and both puzzles' bytes did not move.
- [ ] **Unit 7: the four benchmark puzzles, written in the language, as the acceptance artifact:** vault, payment pool, async offer, singleton wrapper. This phase makes them exist and run, Phase 4 measures them. One PR per puzzle where size warrants. Style constraint (decision by Evan, 2026-08-16, simplified by unit 6c, widened by unit 6b's resequencing): the puzzles are plain functions, the fixed forms, and the unit 6b surfaces, there being no macro system, and the authoring experience feeds the typed v1 gate's ledger note on whether unit 6c's cut ever binds. Vault first, resequenced ahead of the pool (decision by Evan, 2026-08-16): core semantics match BIP-345, one PR, plus the keyless leader/follower consolidation path over the message ledger with its theft vectors, the evaluation doc's section 3.2 benchmark. Recovery posture is a curried per-instance choice, keyless or keyed (decision by Evan, 2026-08-16). The vault's dispatch helpers exist only to name a reconstructed root once, the pre-registered assign trigger firing, evidence for the unit 7 ledger note. Singleton wrapper second, landed 2026-08-22 (PR 59), resequenced ahead of the pool and the offer: the Chia shape does not port, because a txid commits input outpoints but not input scripts and no VM operator performs the taproot tweak, so the lineage scriptPubKey is constant per launch and the state rides in a tagged OP_RETURN of the creating transaction, read back through two transaction preimages. The constant scriptPubKey is the built shape, both sides stated in `docs/puzzles/singleton.md` (decision by Evan, 2026-08-22, on the census below), and the taproot-tweak operator condition-record decision 3 declined is flagged for the Phase 4 gate with this puzzle as the case that answers decision 3's objection. The helpers that name a txid and an inner hash once are the assign trigger firing again. Value question answered 2026-08-22 in that doc (census of the vendored Chia corpora plus the Bitcoin constructions): Ark, Lightning, vaults, and pools get identity from the root outpoint and need no singleton, the lineage machinery is what the fungible asset token needs, and the remaining lineage effort belongs there. The token is a capability-and-risk study, not a goal: BitLisp does not want fungible asset tokens on Bitcoin, it wants to know what the vocabulary can build and what that exposes (decision by Evan, 2026-08-22). The name BAT1 applies if a standard is ever written. Eight authoring observations across the vault and the singleton are recorded there for the ledger. The fungible asset token joins the unit as a capability-and-risk study (decision by Evan, 2026-08-22): not a goal, built on the singleton's lineage machinery to know what the vocabulary can build and what it exposes, BAT1 only if a standard is ever written. Remaining order: the identity-assert rework of both landed puzzles, unit 6d, then the payment pool, the async offer, and the token study.

**Standing constraints:**
Expand Down
2 changes: 2 additions & 0 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ A term enters this table in the same PR that introduces it.
| curry, uncurry | compare filling the placeholder keys of a script template before deriving its address | curry, uncurry (clvm_tools, chia_rs) | fixing values into a program to make a new committed program and reading them back out, the shape and the strict uncurry contract in docs/lang/curry.md, landed 2026-08-16 |
| include | no script equivalent, script has no authoring language | include (clvm_tools stage 2), import (clvm_tools_rs) | the declaration-file splice form, one declaration list per file resolved through the -I search path, files deduplicated and cycles rejected where classic errors or recurses (docs/lang/language.md deviations), landed 2026-08-16 |
| defun-inline | no script equivalent, script has no authoring language | defun-inline (Chialisp) | the inline function declaration, call-by-name splicing at each call site with arity checked and quoted content untouched where classic substitutes blindly (docs/lang/language.md deviations), landed 2026-08-16 |
| let | no script equivalent, script has no authoring language | let (the modern Chialisp dialect) | parallel bindings for one body expression, compiled as a one-shot apply in place of the hand-written naming helper (docs/lang/language.md), landed 2026-08-23 |
| list* | no script equivalent, script has no authoring language | none, a recorded deviation | list ending in its final argument as the tail instead of nil, for condition lists extending an inherited tail (docs/lang/language.md deviations), landed 2026-08-23 |
| mod hash | compare the script inside a P2WSH or tapleaf commitment, identified by its hash | MOD_HASH, the curried-in hash of a puzzle's own uncurried source | the tree hash of an uncurried program, curried into instances so a program can recompute its own curried identity (docs/puzzles/vault.md), glossed 2026-08-16 with the vault |
| vault | BIP-345 vault, OP_VAULT plus OP_VAULT_RECOVER | the clawback and custody puzzles are the nearest relatives | the first benchmark puzzle, delayed sends with an always-available recovery path (docs/puzzles/vault.md), landed 2026-08-16 |
| trigger spend | the OP_VAULT trigger transaction | no direct equivalent | the authorized vault spend committing a withdrawal target chosen at spend time and creating the triggered coin |
Expand Down
18 changes: 11 additions & 7 deletions docs/lang/curry.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,17 @@ artifact. The uncurry direction is deliberately stricter, in line
with preferring to reject valid-looking input, and a test pins the
divergence against the wheel.

One structural collision is worth knowing. A compiled program that
declares functions has exactly this shape, the main expression
applied over the quoted function tree, so uncurry reports it as a
curry of one value, and that value is the function tree. Uncurry
proves shape, not history: it cannot tell a program somebody
curried from a program that merely compiles to the same tree, and
a zero exit status is never evidence that currying happened.
Two structural collisions are worth knowing. A compiled program
that declares functions has exactly this shape, the main
expression applied over the quoted function tree, so uncurry
reports it as a curry of one value, and that value is the
function tree. A top-level `let` binding only literals, in a
program with no function tree, emits the shape as well, the
quoted body applied over the quoted values, which uncurry reports
as curried values. Uncurry proves shape, not history: it cannot
tell a program somebody curried from a program that merely
compiles to the same tree, and a zero exit status is never
evidence that currying happened.

## Tree hash as identity

Expand Down
71 changes: 65 additions & 6 deletions docs/lang/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ name is exactly a token the raw reader rejects as an unknown symbol,
so strings, hex, operator names, and decimals keep their raw
spelling rules, and the language occupies only text that previously
errored. The reserved words are `program`, `defun`, `defun-inline`,
`defconstant`, `include`, `if`, `list`, `assert`, `and`, and `or`.
`defconstant`, `include`, `if`, `let`, `list`, `list*`, `assert`,
`and`, and `or`.

## The program form

Expand Down Expand Up @@ -200,6 +201,20 @@ Builds a proper list of its evaluated arguments, folding into `c`
calls: `(list A B)` compiles as `(c A (c B ()))`, and `(list)` is
nil. Condition output is written with it.

## list*

```
(list* <expr>* <tail>)
```

Builds a list of its evaluated arguments consed onto the last one,
which becomes the tail: `(list* A B T)` compiles as
`(c A (c B T))`. Where `list` always ends in nil, `list*` is how a
condition list extends a tail the program inherited. With a single
operand there is nothing to cons and the tail compiles bare.
`(list*)` is rejected at compile time: "list* takes items and a
final tail".

## assert

```
Expand Down Expand Up @@ -248,6 +263,41 @@ evaluate, and nil when every operand is falsy. `(or)` is nil.
`(and X)` and `(or X)` compile to the same tree, both meaning X as
a boolean.

## let

```
(let ((<name> <expr>)*) <body>)
```

Binds names to evaluated expressions for one body expression.
Bindings are parallel: every expression evaluates in the enclosing
scope, and the bound names are visible only in the body, so a
binding referencing a sibling is an unknown name. Sequential
naming is nested `let`, each layer seeing the ones above. A
binding names one value, with no destructuring shape. Bound names
follow the parameter rules: duplicates in one binding list are
rejected, a name may not be a reserved word or a condition
constant, and inside the body a bound name shadows parameters,
functions, and constants, exactly as a parameter does. Every name
the `let` does not bind stays visible in the body.

`let` compiles as the naming helpers it replaces are written by
hand, a function taking the bound names as parameters and called
once, except that the body is applied in place instead of entering
the function tree:

```
(a (q . <body>) (c 2 (c <expr> ... 3)))
```

The body's environment is the enclosing one with the bound values
consed in front of the arguments, so the function tree stays at
path 2, calls and recursion inside the body work unchanged, and
the cost is one apply plus one cons per binding, the price of the
hand-written helper call. In a program with no function tree the
rebuild drops the tree cons. `(let () <body>)` binds nothing and
its body compiles bare.

## Expressions and quoting

An expression is an atom, a name, or a form.
Expand Down Expand Up @@ -288,7 +338,9 @@ as a proper list:

Every function therefore sees the same environment shape, its own
parameters rooted at path 3, which is why recursion and mutual
recursion fall out of the layout. The whole program is emitted as
recursion fall out of the layout. A `let` rebuilds the layout the
same way, its bound values consed in front of the arguments, so a
body under `let` still sees the tree at path 2. The whole program is emitted as

```
(a (q . <main>) (c (q . <function tree>) 1))
Expand Down Expand Up @@ -345,7 +397,9 @@ and shows the source name and the live arguments by parameter name
instead of raw bytecode. A function whose compiled body is a single
atom stays out of the table, because an atom's hash cannot be told
apart from ordinary data. Inline functions stay out too: spliced
code has no one compiled body to hash. `main_params` records the
code has no one compiled body to hash. A `let` body stays out as
well: it is part of the expression that contains it, not a
function of its own. `main_params` records the
program's own parameter names for the reader.

## Deviations from Chialisp
Expand All @@ -368,9 +422,14 @@ nothing forces a change. The deliberate differences:
compile time rather than at run time.
- `if`, `list`, `assert`, `and`, and `or` are compiler forms, not
macros, with the semantics Chialisp's stage-2 and utility_macros
macros give the same spellings. Nothing can shadow them: where
Chialisp's newest macro silently wins, the one-namespace rule
makes redefinition an error.
macros give the same spellings, and `let` is a compiler form
with the modern dialect's parallel binding semantics. Nothing
can shadow any of them: where Chialisp's newest macro silently
wins, the one-namespace rule makes redefinition an error.
- `list*` is an addition with no Chialisp counterpart: no dialect
has a form that builds a list onto a tail, so condition lists
extending an inherited tail are consed by hand there. The
expansion is one `c` per item, purely syntactic.
- There is no macro system. Chialisp's `defmacro`, `qq`, and
`unquote` are omitted, a removal decided on production evidence:
across Chia's deployed puzzle corpus and its largest application
Expand Down
22 changes: 22 additions & 0 deletions puzzles/lib/list.blib
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,26 @@
; The element at INDEX, raising past the end of the list.
(defun nth (ITEMS INDEX)
(if INDEX (nth (r ITEMS) (- INDEX 1)) (f ITEMS)))

; LEFT's items in order, then RIGHT as the tail as given, so an
; improper tail survives.
(defun append (LEFT RIGHT)
(if LEFT (c (f LEFT) (append (r LEFT) RIGHT)) RIGHT))

; The accumulator helper owns its generic name under the
; one-namespace rule, as every name in this file does.
(defun reverse-onto (ITEMS ONTO)
(if ITEMS (reverse-onto (r ITEMS) (c (f ITEMS) ONTO)) ONTO))

(defun reverse (ITEMS)
(reverse-onto ITEMS ()))

; The integer sum of the items, 0 for the empty list.
(defun sum (ITEMS)
(if ITEMS (+ (f ITEMS) (sum (r ITEMS))) 0))

; The final item, raising on the empty list as nth raises past
; the end.
(defun last (ITEMS)
(if (r ITEMS) (last (r ITEMS)) (f ITEMS)))
)
Loading