diff --git a/lib/datalog/db.sx b/lib/datalog/db.sx index 7930fdda..798fcfa7 100644 --- a/lib/datalog/db.sx +++ b/lib/datalog/db.sx @@ -182,6 +182,20 @@ ((facts (get db :facts))) (if (has-key? facts rel-key) (get facts rel-key) (list))))) +;; Reserved relation names: built-in / aggregate / negation / arrow. +;; Rules and facts may not have these as their head's relation, since +;; the saturator treats them specially or they are not relation names +;; at all. +(define + dl-reserved-rel-names + (list "not" "count" "sum" "min" "max" "findall" "is" + "<" "<=" ">" ">=" "=" "!=" "+" "-" "*" "/" ":-" "?-")) + +(define + dl-reserved-rel? + (fn + (name) (dl-member-string? name dl-reserved-rel-names))) + (define dl-add-fact! (fn @@ -189,6 +203,9 @@ (cond ((not (and (list? lit) (> (len lit) 0))) (error (str "dl-add-fact!: expected literal list, got " lit))) + ((dl-reserved-rel? (dl-rel-name lit)) + (error (str "dl-add-fact!: '" (dl-rel-name lit) + "' is a reserved name (built-in / aggregate / negation)"))) ((not (dl-ground? lit (dl-empty-subst))) (error (str "dl-add-fact!: expected ground literal, got " lit))) (else @@ -301,6 +318,9 @@ (error (str "dl-add-rule!: expected rule dict, got " rule))) ((not (has-key? rule :head)) (error (str "dl-add-rule!: rule missing :head, got " rule))) + ((dl-reserved-rel? (dl-rel-name (get rule :head))) + (error (str "dl-add-rule!: '" (dl-rel-name (get rule :head)) + "' is a reserved name (built-in / aggregate / negation)"))) (else (let ((rule (dl-rename-anon-rule rule))) (let diff --git a/lib/datalog/scoreboard.json b/lib/datalog/scoreboard.json index 182ca67b..b67356f8 100644 --- a/lib/datalog/scoreboard.json +++ b/lib/datalog/scoreboard.json @@ -1,13 +1,13 @@ { "lang": "datalog", - "total_passed": 242, + "total_passed": 246, "total_failed": 0, - "total": 242, + "total": 246, "suites": [ {"name":"tokenize","passed":26,"failed":0,"total":26}, {"name":"parse","passed":20,"failed":0,"total":20}, {"name":"unify","passed":28,"failed":0,"total":28}, - {"name":"eval","passed":32,"failed":0,"total":32}, + {"name":"eval","passed":36,"failed":0,"total":36}, {"name":"builtins","passed":23,"failed":0,"total":23}, {"name":"semi_naive","passed":8,"failed":0,"total":8}, {"name":"negation","passed":10,"failed":0,"total":10}, @@ -16,5 +16,5 @@ {"name":"magic","passed":34,"failed":0,"total":34}, {"name":"demo","passed":21,"failed":0,"total":21} ], - "generated": "2026-05-09T13:11:25+00:00" + "generated": "2026-05-10T20:51:33+00:00" } diff --git a/lib/datalog/scoreboard.md b/lib/datalog/scoreboard.md index f82a3b30..7c5196ef 100644 --- a/lib/datalog/scoreboard.md +++ b/lib/datalog/scoreboard.md @@ -1,13 +1,13 @@ # datalog scoreboard -**242 / 242 passing** (0 failure(s)). +**246 / 246 passing** (0 failure(s)). | Suite | Passed | Total | Status | |-------|--------|-------|--------| | tokenize | 26 | 26 | ok | | parse | 20 | 20 | ok | | unify | 28 | 28 | ok | -| eval | 32 | 32 | ok | +| eval | 36 | 36 | ok | | builtins | 23 | 23 | ok | | semi_naive | 8 | 8 | ok | | negation | 10 | 10 | ok | diff --git a/lib/datalog/tests/eval.sx b/lib/datalog/tests/eval.sx index 822e58fb..75541282 100644 --- a/lib/datalog/tests/eval.sx +++ b/lib/datalog/tests/eval.sx @@ -165,6 +165,28 @@ ((db (dl-program "edge(1, 2). edge(2, 3). edge(3, 1).\n reach(X, Y) :- edge(X, Y).\n reach(X, Z) :- edge(X, Y), reach(Y, Z)."))) (do (dl-saturate! db) (len (dl-relation db "reach")))) 9) + ;; Reserved relation names rejected as rule/fact heads. + (dl-et-test! + "reserved name `not` as head rejected" + (dl-et-throws? (fn () (dl-program "not(X) :- p(X)."))) + true) + + (dl-et-test! + "reserved name `count` as head rejected" + (dl-et-throws? + (fn () (dl-program "count(N, X, p(X)) :- p(X)."))) + true) + + (dl-et-test! + "reserved name `<` as head rejected" + (dl-et-throws? (fn () (dl-program "<(X, 5) :- p(X)."))) + true) + + (dl-et-test! + "reserved name `is` as head rejected" + (dl-et-throws? (fn () (dl-program "is(N, +(1, 2)) :- p(N)."))) + true) + (dl-et-test! "unsafe head var" (dl-et-throws? (fn () (dl-program "p(X, Y) :- q(X).")))