Files
rose-ash/lib/prolog/query.sx
giles 25a4ce4a05
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
prolog-query SX API: pl-load + pl-query-all + pl-query-one + pl-query (+16 tests)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 09:58:56 +00:00

115 lines
3.2 KiB
Plaintext

;; lib/prolog/query.sx — high-level Prolog query API for SX/Hyperscript callers.
;;
;; Requires tokenizer.sx, parser.sx, runtime.sx to be loaded first.
;;
;; Public API:
;; (pl-load source-str) → db
;; (pl-query-all db query-str) → list of solution dicts {var-name → term-string}
;; (pl-query-one db query-str) → first solution dict or nil
;; (pl-query source-str query-str) → list of solution dicts (convenience)
;; Collect variable name strings from a parse-time AST (pre-instantiation).
;; Returns list of unique strings, excluding anonymous "_".
(define
pl-query-extract-vars
(fn
(ast)
(let
((seen {}))
(let
((collect!
(fn
(t)
(cond
((not (list? t)) nil)
((empty? t) nil)
((= (first t) "var")
(if
(not (= (nth t 1) "_"))
(dict-set! seen (nth t 1) true)
nil))
((= (first t) "compound")
(for-each collect! (nth t 2)))
(true nil)))))
(collect! ast)
(keys seen)))))
;; Build a solution dict from a var-env after a successful solve.
;; Maps each variable name string to its formatted term value.
(define
pl-query-solution-dict
(fn
(var-names var-env)
(let
((d {}))
(for-each
(fn (name) (dict-set! d name (pl-format-term (dict-get var-env name))))
var-names)
d)))
;; Parse source-str and load clauses into a fresh DB.
;; Returns the DB for reuse across multiple queries.
(define
pl-load
(fn
(source-str)
(let
((db (pl-mk-db)))
(if
(and (string? source-str) (not (= source-str "")))
(pl-db-load! db (pl-parse source-str))
nil)
db)))
;; Run query-str against db, returning a list of solution dicts.
;; Each dict maps variable name strings to their formatted term values.
;; Returns an empty list if no solutions.
(define
pl-query-all
(fn
(db query-str)
(let
((parsed (pl-parse (str "q_ :- " query-str "."))))
(let
((body-ast (nth (first parsed) 2)))
(let
((var-names (pl-query-extract-vars body-ast))
(var-env {}))
(let
((goal (pl-instantiate body-ast var-env))
(trail (pl-mk-trail))
(solutions (list)))
(let
((mark (pl-trail-mark trail)))
(pl-solve!
db
goal
trail
{:cut false}
(fn
()
(begin
(append!
solutions
(pl-query-solution-dict var-names var-env))
false)))
(pl-trail-undo-to! trail mark)
solutions)))))))
;; Return the first solution dict, or nil if no solutions.
(define
pl-query-one
(fn
(db query-str)
(let
((all (pl-query-all db query-str)))
(if (empty? all) nil (first all)))))
;; Convenience: parse source-str, then run query-str against it.
;; Returns a list of solution dicts. Creates a fresh DB each call.
(define
pl-query
(fn
(source-str query-str)
(pl-query-all (pl-load source-str) query-str)))