Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
9.7 KiB
9.7 KiB
Prolog-on-SX: mini-Prolog interpreter on delimited continuations
Horn clauses + unification + cut + arithmetic, implemented as an interpreter (clauses live as SX data, a SX-implemented solver walks them). Backtracking is powered by the delimited-continuations machinery in lib/callcc.sx + spec/evaluator.sx Step 5 — this is the reason Prolog fits SX well.
End-state goal: 200+ tests passing (classic programs + Hirst's ISO conformance subset + Hyperscript integration suite). Long-lived background agent driving the scoreboard up.
Ground rules
- Scope: only touch
lib/prolog/**andplans/prolog-on-sx.md. Do not editspec/,hosts/,shared/,lib/js/**,lib/hyperscript/**,lib/lua/**,lib/stdlib.sx, or anything inlib/root. Prolog primitives go inlib/prolog/runtime.sx. - Shared-file issues go under "Blockers" below with a minimal repro; do not fix from this loop.
- SX files: use
sx-treeMCP tools only. - Architecture: Prolog source → term AST → clause DB. Solver is SX code walking the DB; backtracking via delimited continuations, not a separate trail machine.
- Commits: one feature per commit. Keep
## Progress logupdated and tick boxes.
Architecture sketch
Prolog source text
│
▼
lib/prolog/tokenizer.sx — atoms, vars, numbers, punct, comments
│
▼
lib/prolog/parser.sx — term AST; phase 1: f(a,b) syntax only, no operator table
│
▼
lib/prolog/runtime.sx — clause DB, unify!, trail, solver (DFS + delimited-cont backtracking)
│ built-ins: =/2, \=/2, !/0, is/2, call/1, findall/3, …
▼
solutions / side-effects
Representation choices (finalise in phase 1, document here):
- Term: nested SX list. Compound
(functor arg1 arg2). Atom = symbol. Number = number. Variable ={:var "X" :binding <ref>}with mutable binding slot. - List: cons-cell compound
(. H T)or similar.[1,2,3]sugar desugared at parse. - Clause:
{:head <term> :body <term>}where body is the conjunction goal. - Clause DB: dict
"functor/arity" → list of clauses.
Roadmap
Phase 1 — tokenizer + term parser (no operator table)
- Tokenizer: atoms (lowercase/quoted), variables (uppercase/
_), numbers, strings, punct( ) , . [ ] | ! :-, comments (%,/* */) - Parser: clauses
head :- body.and factshead.; termsatom | Var | number | compound(args) | [list,sugar] - Skip for phase 1: operator table.
X is Y + 1must be writtenis(X, '+'(Y, 1));=written=(X, Y). Operators land in phase 4. - Unit tests in
lib/prolog/tests/parse.sx— 25 pass
Phase 2 — unification + trail
make-var,walk(follow binding chain),prolog-unify!(terms + trail → bool),trail-undo-to!- Occurs-check off by default, exposed as flag
- 30+ unification tests in
lib/prolog/tests/unify.sx: atoms, vars, compounds, lists, cyclic (no-occurs-check), mutual occurs — 47 pass
Phase 3 — clause DB + DFS solver + cut + first classic programs
- Clause DB:
"functor/arity" → list-of-clauses, loader inserts —pl-mk-db/pl-db-add!/pl-db-load!/pl-db-lookup/pl-db-lookup-goal, 14 tests intests/clausedb.sx - Solver: DFS with choice points backed by delimited continuations (
lib/callcc.sx). On goal entry, capture; per matching clause, unify head + recurse body; on failure, undo trail, try next — first cut: trail-based undo + CPS k (no shift/reset yet, per briefing gotcha). Built-ins so far:true/0,fail/0,=/2,,/2. Refactor to delimited conts later. - Cut (
!): cut barrier at current choice-point frame; collapse all up to barrier — two-cut-box scheme: eachpl-solve-user!creates a fresh inner-cut-box (set by!in this predicate's body) AND snapshots the outer-cut-box state on entry. After body fails, abandon clause alternatives if (a) inner was set or (b) outer transitioned false→true during this call. Lets post-cut goals backtrack normally while blocking pre-cut alternatives. 6 cut tests cover bare cut, clause-commit, choice-commit, cut+fail, post-cut backtracking, nested-cut isolation. - Built-ins:
=/2,\\=/2,true/0,fail/0,!/0,,/2,;/2,->/2inside;,call/1,write/1,nl/0— all 11 done.write/1andnl/0use a globalpl-output-bufferstring +pl-output-clear!for testability;pl-format-termwalks deep then renders atoms/nums/strs/compounds/vars (var →_<id>). Note: cut-transparency via;not testable yet without operator support —;(,(a,!), b)parser-rejects because,is body-operator-only; revisit in phase 4. - Arithmetic
is/2with+ - * / mod abs—pl-eval-arithwalks deep, recurses on compounds, dispatches on functor; binary+ - * / mod, binary AND unary-, unaryabs.is/2evaluates RHS, wraps as("num" v), unifies viapl-solve-eq!. 11 tests cover each op + nested + ground LHS match/mismatch + bound-var-on-RHS chain. - Classic programs in
lib/prolog/tests/programs/:append.pl— list append (with backtracking)reverse.pl— naive reversemember.pl— generate all solutions via backtrackingnqueens.pl— 8-queensfamily.pl— facts + rules (parent/ancestor)
lib/prolog/conformance.sh+ runner,scoreboard.json+scoreboard.md- Target: all 5 classic programs passing
Phase 4 — operator table + more built-ins (next run)
- Operator table parsing (prefix/infix/postfix, precedence, assoc)
assert/1,asserta/1,assertz/1,retract/1findall/3,bagof/3,setof/3copy_term/2,functor/3,arg/3,=../2- String/atom predicates
Phase 5 — Hyperscript integration
prolog-queryprimitive callable from SX/Hyperscript- Hyperscript DSL:
when allowed(user, :edit) then … - Integration suite
Phase 6 — ISO conformance
- Vendor Hirst's conformance tests
- Drive scoreboard to 200+
Phase 7 — compiler (later, optional)
- Compile clauses to SX continuations for speed
- Keep interpreter as the reference
Progress log
Newest first. Agent appends on every commit.
- 2026-04-25 —
is/2arithmetic landed.pl-eval-arithrecursively evaluates ground RHS expressions (binary+ - * /,mod; binary+unary-; unaryabs);is/2wraps the value as("num" v)and unifies viapl-solve-eq!, so it works in all three modes — bind unbound LHS, check ground LHS for equality, propagate from earlier var bindings on RHS. 11 tests, total 148 (+11). Without operator support, expressions must be written prefix:is(X, +(2, *(3, 4))). - 2026-04-25 —
write/1+nl/0landed using global string buffer (pl-output-buffer+pl-output-clear!+pl-output-write!).pl-format-termwalks deep + dispatches on atom/num/str/compound/var;pl-format-argsrecursively comma-joins. 7 new tests cover atom/num/compound formatting, conjunction order, var-walk, andnl. Built-ins box (=/2,\\=/2,true/0,fail/0,!/0,,/2,;/2,->/2,call/1,write/1,nl/0) now ticked. Total 137 (+7). - 2026-04-25 —
->/2if-then-else landed (both;(->(C,T), E)and standalone->(C, T)≡(C -> T ; fail)).pl-solve-or!now special-cases->in left arg →pl-solve-if-then-else!. Cond runs in a fresh local cut-box (ISO opacity for cut inside cond). Then-branch can backtrack, else-branch can backtrack, but cond commits to first solution. 9 new tests covering both forms, both branches, binding visibility, cond-commit, then-backtrack, else-backtrack. Total 130 (+9). - 2026-04-25 — Built-ins
\=/2,;/2,call/1landed.pl-solve-not-eq!(try unify, always undo, succeed iff unify failed).pl-solve-or!(try left, on failure check cut and only try right if not cut).call/1opens a fresh inner cut-box (ISO opacity: cut insidecall(G)commits G, not caller). 11 new tests intests/solve.sxcover atoms+vars for\=, both branches + count for;, andcall/1against atoms / compounds / bound goal vars. Total 121 (+11). Box not yet ticked —->/2,write/1,nl/0still pending. - 2026-04-25 — Cut (
!/0) landed.pl-cut?predicate; solver functions all take acut-box;pl-solve-user!creates a fresh inner-cut-box and snapshotsouter-was-cut;pl-try-clauses!abandons alternatives when inner.cut OR (outer.cut transitioned false→true during this call). 6 new cut tests intests/solve.sxcovering bare cut, clause-commit, choice-commit, cut+fail blocks alt clauses, post-cut goal backtracks freely, inner cut isolation. Total 110 (+6). - 2026-04-25 — Phase 3 DFS solver landed (CPS, trail-based backtracking; delimited conts deferred).
pl-solve!+pl-solve-eq!+pl-solve-user!+pl-try-clauses!+pl-solve-once!+pl-solve-count!in runtime.sx. Built-ins:true/0,fail/0,=/2,,/2. Newtests/solve.sx18/18 green covers atomic goals, =, conjunction, fact lookup, multi-solution count, recursive ancestor rule, trail-undo verification. Bug fix:pl-instantiatehad no("clause" h b)case → vars in rule head/body were never instantiated, so rule resolution silently failed against runtime-var goals. Added clause case to recurse with shared var-env. Total 104 (+18). - 2026-04-24 — Phase 3 clause DB landed:
pl-mk-db+pl-head-key/pl-clause-key/pl-goal-key+pl-db-add!/pl-db-load!/pl-db-lookup/pl-db-lookup-goalin runtime.sx. Newtests/clausedb.sx14/14 green. Total 86 (+14). Loader preserves declaration order (append!). - 2026-04-24 — Verified phase 1+2 already implemented on loops/prolog:
pl-parse-tests-run!25/25,pl-unify-tests-run!47/47 (72 total). Ticked phase 1+2 boxes. - (awaiting phase 1)
Blockers
Shared-file issues that need someone else to fix. Minimal repro only.
- (none yet)