datalog: anonymous _ vars are unique per occurrence (Phase 5d, 156/156)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
(p X _), (p _ Y) — the two _ are now different variables, matching standard Datalog semantics. Previously both _ symbols were the same SX symbol, so unification across them gave wrong answers. Fix in db.sx: dl-rename-anon-term + dl-rename-anon-lit walk a term or literal and replace each '_' symbol with a fresh _anon<N>. dl-make-anon-renamer returns a counter-based name generator scoped per call. dl-rename-anon-rule applies it to head and body of a rule. dl-add-rule! invokes the renamer before safety check. eval.sx: dl-query renames anon vars in the goal before search and filters '_' out of the projection so user-facing results aren't polluted with internal _anon<N> bindings. The previous "underscore in head ok" test now correctly rejects (p X _) :- q(X) as unsafe (the head's fresh anon var has no body binder). New "underscore in body only" test confirms the safe case. Two regression tests for rule-level and goal-level independence.
This commit is contained in:
@@ -189,6 +189,44 @@
|
||||
" do not appear in any body literal"))
|
||||
(else nil))))))))
|
||||
|
||||
(define
|
||||
dl-rename-anon-term
|
||||
(fn
|
||||
(term next-name)
|
||||
(cond
|
||||
((and (symbol? term) (= (symbol->string term) "_"))
|
||||
(next-name))
|
||||
((list? term)
|
||||
(map (fn (x) (dl-rename-anon-term x next-name)) term))
|
||||
(else term))))
|
||||
|
||||
(define
|
||||
dl-rename-anon-lit
|
||||
(fn
|
||||
(lit next-name)
|
||||
(cond
|
||||
((and (dict? lit) (has-key? lit :neg))
|
||||
{:neg (dl-rename-anon-term (get lit :neg) next-name)})
|
||||
((list? lit) (dl-rename-anon-term lit next-name))
|
||||
(else lit))))
|
||||
|
||||
(define
|
||||
dl-make-anon-renamer
|
||||
(fn
|
||||
()
|
||||
(let ((counter 0))
|
||||
(fn () (do (set! counter (+ counter 1))
|
||||
(string->symbol (str "_anon" counter)))))))
|
||||
|
||||
(define
|
||||
dl-rename-anon-rule
|
||||
(fn
|
||||
(rule)
|
||||
(let ((next-name (dl-make-anon-renamer)))
|
||||
{:head (dl-rename-anon-term (get rule :head) next-name)
|
||||
:body (map (fn (lit) (dl-rename-anon-lit lit next-name))
|
||||
(get rule :body))})))
|
||||
|
||||
(define
|
||||
dl-add-rule!
|
||||
(fn
|
||||
@@ -199,14 +237,15 @@
|
||||
((not (has-key? rule :head))
|
||||
(error (str "dl-add-rule!: rule missing :head, got " rule)))
|
||||
(else
|
||||
(let
|
||||
((err (dl-rule-check-safety rule)))
|
||||
(cond
|
||||
((not (nil? err)) (error (str "dl-add-rule!: " err)))
|
||||
(else
|
||||
(let
|
||||
((rules (get db :rules)))
|
||||
(do (append! rules rule) true)))))))))
|
||||
(let ((rule (dl-rename-anon-rule rule)))
|
||||
(let
|
||||
((err (dl-rule-check-safety rule)))
|
||||
(cond
|
||||
((not (nil? err)) (error (str "dl-add-rule!: " err)))
|
||||
(else
|
||||
(let
|
||||
((rules (get db :rules)))
|
||||
(do (append! rules rule) true))))))))))
|
||||
|
||||
(define
|
||||
dl-add-clause!
|
||||
|
||||
Reference in New Issue
Block a user