;; 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)))