prolog: ->/2 if-then-else (in ; and standalone), 9 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
This commit is contained in:
@@ -322,12 +322,71 @@
|
||||
trail
|
||||
cut-box
|
||||
k))
|
||||
((and (pl-compound? g) (= (pl-fun g) "->") (= (len (pl-args g)) 2))
|
||||
(pl-solve-if-then-else!
|
||||
db
|
||||
(first (pl-args g))
|
||||
(nth (pl-args g) 1)
|
||||
(list "atom" "fail")
|
||||
trail
|
||||
cut-box
|
||||
k))
|
||||
((and (pl-compound? g) (= (pl-fun g) "call") (= (len (pl-args g)) 1))
|
||||
(let
|
||||
((call-cb {:cut false}))
|
||||
(pl-solve! db (first (pl-args g)) trail call-cb k)))
|
||||
(true (pl-solve-user! db g trail cut-box k))))))
|
||||
|
||||
(define
|
||||
pl-solve-or!
|
||||
(fn
|
||||
(db a b trail cut-box k)
|
||||
(cond
|
||||
((and (pl-compound? a) (= (pl-fun a) "->") (= (len (pl-args a)) 2))
|
||||
(pl-solve-if-then-else!
|
||||
db
|
||||
(first (pl-args a))
|
||||
(nth (pl-args a) 1)
|
||||
b
|
||||
trail
|
||||
cut-box
|
||||
k))
|
||||
(true
|
||||
(let
|
||||
((mark (pl-trail-mark trail)))
|
||||
(let
|
||||
((r (pl-solve! db a trail cut-box k)))
|
||||
(cond
|
||||
(r true)
|
||||
((dict-get cut-box :cut) false)
|
||||
(true
|
||||
(begin
|
||||
(pl-trail-undo-to! trail mark)
|
||||
(pl-solve! db b trail cut-box k))))))))))
|
||||
|
||||
(define
|
||||
pl-solve-if-then-else!
|
||||
(fn
|
||||
(db cond-goal then-goal else-goal trail cut-box k)
|
||||
(let
|
||||
((mark (pl-trail-mark trail)))
|
||||
(let
|
||||
((local-cb {:cut false}))
|
||||
(let
|
||||
((found {:val false}))
|
||||
(pl-solve!
|
||||
db
|
||||
cond-goal
|
||||
trail
|
||||
local-cb
|
||||
(fn () (begin (dict-set! found :val true) true)))
|
||||
(cond
|
||||
((dict-get found :val) (pl-solve! db then-goal trail cut-box k))
|
||||
(true
|
||||
(begin
|
||||
(pl-trail-undo-to! trail mark)
|
||||
(pl-solve! db else-goal trail cut-box k)))))))))
|
||||
|
||||
(define
|
||||
pl-solve-not-eq!
|
||||
(fn
|
||||
@@ -340,22 +399,6 @@
|
||||
(pl-trail-undo-to! trail mark)
|
||||
(cond (unified false) (true (k))))))))
|
||||
|
||||
(define
|
||||
pl-solve-or!
|
||||
(fn
|
||||
(db a b trail cut-box k)
|
||||
(let
|
||||
((mark (pl-trail-mark trail)))
|
||||
(let
|
||||
((r (pl-solve! db a trail cut-box k)))
|
||||
(cond
|
||||
(r true)
|
||||
((dict-get cut-box :cut) false)
|
||||
(true
|
||||
(begin
|
||||
(pl-trail-undo-to! trail mark)
|
||||
(pl-solve! db b trail cut-box k))))))))
|
||||
|
||||
(define
|
||||
pl-solve-eq!
|
||||
(fn
|
||||
|
||||
@@ -337,4 +337,88 @@
|
||||
(pl-mk-trail))
|
||||
true)
|
||||
|
||||
(define pl-s-db-ite (pl-mk-db))
|
||||
|
||||
(pl-db-load! pl-s-db-ite (pl-parse "p(1). p(2). q(yes). q(no)."))
|
||||
|
||||
(pl-s-test!
|
||||
"if-then-else: cond true → then runs"
|
||||
(pl-solve-once!
|
||||
pl-s-db-ite
|
||||
(pl-s-goal ";(->(true, =(X, ok)), =(X, fallback))" {})
|
||||
(pl-mk-trail))
|
||||
true)
|
||||
|
||||
(define pl-s-env-ite1 {})
|
||||
|
||||
(pl-solve-once!
|
||||
pl-s-db-ite
|
||||
(pl-s-goal ";(->(true, =(X, ok)), =(X, fallback))" pl-s-env-ite1)
|
||||
(pl-mk-trail))
|
||||
|
||||
(pl-s-test!
|
||||
"if-then-else: cond true binds via then"
|
||||
(pl-atom-name (pl-walk-deep (dict-get pl-s-env-ite1 "X")))
|
||||
"ok")
|
||||
|
||||
(pl-s-test!
|
||||
"if-then-else: cond false → else"
|
||||
(pl-solve-once!
|
||||
pl-s-db-ite
|
||||
(pl-s-goal ";(->(fail, =(X, ok)), =(X, fallback))" {})
|
||||
(pl-mk-trail))
|
||||
true)
|
||||
|
||||
(define pl-s-env-ite2 {})
|
||||
|
||||
(pl-solve-once!
|
||||
pl-s-db-ite
|
||||
(pl-s-goal ";(->(fail, =(X, ok)), =(X, fallback))" pl-s-env-ite2)
|
||||
(pl-mk-trail))
|
||||
|
||||
(pl-s-test!
|
||||
"if-then-else: cond false binds via else"
|
||||
(pl-atom-name (pl-walk-deep (dict-get pl-s-env-ite2 "X")))
|
||||
"fallback")
|
||||
|
||||
(pl-s-test!
|
||||
"if-then-else: cond commits to first solution (count = 1)"
|
||||
(pl-solve-count!
|
||||
pl-s-db-ite
|
||||
(pl-s-goal ";(->(p(X), =(Y, found)), =(Y, none))" {})
|
||||
(pl-mk-trail))
|
||||
1)
|
||||
|
||||
(pl-s-test!
|
||||
"if-then-else: then can backtrack"
|
||||
(pl-solve-count!
|
||||
pl-s-db-ite
|
||||
(pl-s-goal ";(->(true, p(X)), =(X, none))" {})
|
||||
(pl-mk-trail))
|
||||
2)
|
||||
|
||||
(pl-s-test!
|
||||
"if-then-else: else can backtrack"
|
||||
(pl-solve-count!
|
||||
pl-s-db-ite
|
||||
(pl-s-goal ";(->(fail, =(X, ignored)), p(X))" {})
|
||||
(pl-mk-trail))
|
||||
2)
|
||||
|
||||
(pl-s-test!
|
||||
"standalone -> with true cond succeeds"
|
||||
(pl-solve-once!
|
||||
pl-s-db-ite
|
||||
(pl-s-goal "->(true, =(X, hi))" {})
|
||||
(pl-mk-trail))
|
||||
true)
|
||||
|
||||
(pl-s-test!
|
||||
"standalone -> with false cond fails"
|
||||
(pl-solve-once!
|
||||
pl-s-db-ite
|
||||
(pl-s-goal "->(fail, =(X, hi))" {})
|
||||
(pl-mk-trail))
|
||||
false)
|
||||
|
||||
(define pl-solve-tests-run! (fn () {:failed pl-s-test-fail :passed pl-s-test-pass :total pl-s-test-count :failures pl-s-test-failures}))
|
||||
|
||||
@@ -53,7 +53,7 @@ Representation choices (finalise in phase 1, document here):
|
||||
- [x] 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 in `tests/clausedb.sx`
|
||||
- [x] 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.
|
||||
- [x] Cut (`!`): cut barrier at current choice-point frame; collapse all up to barrier — two-cut-box scheme: each `pl-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`, `->/2` inside `;`, `call/1`, `write/1`, `nl/0` — done so far: `=/2`, `true/0`, `fail/0`, `!/0`, `,/2`, `\\=/2`, `;/2`, `call/1`. Pending: `->/2` inside `;`, `write/1`, `nl/0`. Note: cut-transparency via `;` not testable yet without operator support — `;(,(a,!), b)` parser-rejects because `,` is body-operator-only.
|
||||
- [ ] Built-ins: `=/2`, `\\=/2`, `true/0`, `fail/0`, `!/0`, `,/2`, `;/2`, `->/2` inside `;`, `call/1`, `write/1`, `nl/0` — done so far: `=/2`, `true/0`, `fail/0`, `!/0`, `,/2`, `\\=/2`, `;/2`, `call/1`, `->/2` (both inside `;` and standalone). Pending: `write/1`, `nl/0`. Note: cut-transparency via `;` not testable yet without operator support — `;(,(a,!), b)` parser-rejects because `,` is body-operator-only.
|
||||
- [ ] Arithmetic `is/2` with `+ - * / mod abs`
|
||||
- [ ] Classic programs in `lib/prolog/tests/programs/`:
|
||||
- [ ] `append.pl` — list append (with backtracking)
|
||||
@@ -88,6 +88,7 @@ Representation choices (finalise in phase 1, document here):
|
||||
|
||||
_Newest first. Agent appends on every commit._
|
||||
|
||||
- 2026-04-25 — `->/2` if-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/1` landed. `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/1` opens a fresh inner cut-box (ISO opacity: cut inside `call(G)` commits G, not caller). 11 new tests in `tests/solve.sx` cover atoms+vars for `\=`, both branches + count for `;`, and `call/1` against atoms / compounds / bound goal vars. Total 121 (+11). Box not yet ticked — `->/2`, `write/1`, `nl/0` still pending.
|
||||
- 2026-04-25 — Cut (`!/0`) landed. `pl-cut?` predicate; solver functions all take a `cut-box`; `pl-solve-user!` creates a fresh inner-cut-box and snapshots `outer-was-cut`; `pl-try-clauses!` abandons alternatives when inner.cut OR (outer.cut transitioned false→true during this call). 6 new cut tests in `tests/solve.sx` covering 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`. New `tests/solve.sx` 18/18 green covers atomic goals, =, conjunction, fact lookup, multi-solution count, recursive ancestor rule, trail-undo verification. Bug fix: `pl-instantiate` had 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).
|
||||
|
||||
Reference in New Issue
Block a user