Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
115 lines
3.2 KiB
Plaintext
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)))
|