diff --git a/deps.edn b/deps.edn
index d085200f..0812f2b8 100644
--- a/deps.edn
+++ b/deps.edn
@@ -120,7 +120,9 @@
meta-merge/meta-merge {:mvn/version "1.0.0"}
com.exoscale/lingo {:mvn/version "1.0.0-alpha14"}
io.github.swirrl/dogstatsd {:mvn/version "0.1.39"}
- org.clojure/algo.monads {:mvn/version "0.1.6"}}
+ org.clojure/algo.monads {:mvn/version "0.1.6"}
+ io.lambdaforge/datalog-parser {:mvn/version "0.1.9"}
+ clj-stacktrace/clj-stacktrace {:mvn/version "0.2.8"}}
:classpath-overrides {org.clojure/clojure nil
org.clojure/spec.alpha nil}}
:clj-nvd
diff --git a/doc/companies.md b/doc/companies.md
index bdad6686..ea76a3eb 100644
--- a/doc/companies.md
+++ b/doc/companies.md
@@ -38,6 +38,10 @@ Sponsoring via [Cognitect](https://www.cognitect.com/).
+### [180seg](https://www.180s.com.br)
+
+
+
### [Dr. Evidence](https://www.drevidence.com/)
diff --git a/doc/libraries.csv b/doc/libraries.csv
index e84bf70f..eb9a9417 100644
--- a/doc/libraries.csv
+++ b/doc/libraries.csv
@@ -10,6 +10,7 @@ camel-snake-kebab/camel-snake-kebab,https://github.com/clj-commons/camel-snake-k
circleci/bond,https://github.com/circleci/bond
clj-commons/clj-yaml,https://github.com/clj-commons/clj-yaml
clj-commons/multigrep,https://github.com/clj-commons/multigrep
+clj-stacktrace/clj-stacktrace,https://github.com/mmcgrana/clj-stacktrace
clojure-csv/clojure-csv,https://github.com/davidsantiago/clojure-csv
clojure-term-colors/clojure-term-colors,https://github.com/trhura/clojure-term-colors
com.exoscale/lingo,https://github.com/exoscale/lingo
@@ -43,6 +44,7 @@ io.github.cognitect-labs/test-runner,https://github.com/cognitect-labs/test-runn
io.github.swirrl/dogstatsd,https://github.com/swirrl/dogstatsd
io.github.technomancy/limit-break,https://github.com/technomancy/limit-break
io.helins/binf,https://github.com/helins/binf.cljc
+io.lambdaforge/datalog-parser,https://github.com/lambdaforge/datalog-parser
io.replikativ/hasch,https://github.com/replikativ/hasch
java-http-clj/java-http-clj,http://www.github.com/schmee/java-http-clj
lambdaisland/regal,https://github.com/lambdaisland/regal
diff --git a/script/add-libtest.clj b/script/add-libtest.clj
index 98215340..9eeea5f2 100755
--- a/script/add-libtest.clj
+++ b/script/add-libtest.clj
@@ -78,7 +78,8 @@
(let [lib-dir (if branch
(gl/procure git-url lib-name branch)
(or (gl/procure git-url lib-name "master")
- (gl/procure git-url lib-name "main")))
+ (gl/procure git-url lib-name "main")
+ (throw (ex-info "Unable to clone git-url" {}))))
_ (println "Git clone is at" lib-dir)
lib-root-dir (if directory (fs/file lib-dir directory) lib-dir)
test-dirs (if test-directories
diff --git a/test-resources/lib_tests/bb-tested-libs.edn b/test-resources/lib_tests/bb-tested-libs.edn
index bc8984d2..a6310591 100644
--- a/test-resources/lib_tests/bb-tested-libs.edn
+++ b/test-resources/lib_tests/bb-tested-libs.edn
@@ -103,4 +103,6 @@
meta-merge/meta-merge {:git-url "https://github.com/weavejester/meta-merge", :test-namespaces (meta-merge.core-test), :git-sha "c968c38baccd4219fe0ba592d89af37ea8e426bf"}
com.exoscale/lingo {:git-url "https://github.com/exoscale/lingo", :test-namespaces (exoscale.lingo.test.core-test), :git-sha "30b5084fab28d24c99ec683e21535366910d9f2f" :skip-windows true}
io.github.swirrl/dogstatsd {:git-url "https://github.com/swirrl/dogstatsd", :test-namespaces (swirrl.dogstatsd-test), :git-sha "e110caae452cd1185e65e389a359b69502076d61"}
- org.clojure/algo.monads {:git-url "https://github.com/clojure/algo.monads", :test-namespaces (clojure.algo.test-monads), :git-sha "3a985b0b099110b1654d568fecf597bc9c8d1ff5"}}
+ org.clojure/algo.monads {:git-url "https://github.com/clojure/algo.monads", :test-namespaces (clojure.algo.test-monads), :git-sha "3a985b0b099110b1654d568fecf597bc9c8d1ff5"}
+ io.lambdaforge/datalog-parser {:git-url "https://github.com/lambdaforge/datalog-parser", :test-namespaces (datalog.parser.pull-test datalog.parser.test.util datalog.parser.impl-test datalog.parser-test datalog.unparser-test), :git-sha "02d193f397afc3f93da704e7c6c850b194f0e797"}
+ clj-stacktrace/clj-stacktrace {:git-url "https://github.com/mmcgrana/clj-stacktrace", :test-namespaces (clj-stacktrace.repl-test clj-stacktrace.core-test), :git-sha "94dc2dd748710e79800e94b713e167e5dc525717"}}
diff --git a/test-resources/lib_tests/clj_stacktrace/core_test.clj b/test-resources/lib_tests/clj_stacktrace/core_test.clj
new file mode 100644
index 00000000..b93a31a5
--- /dev/null
+++ b/test-resources/lib_tests/clj_stacktrace/core_test.clj
@@ -0,0 +1,83 @@
+(ns clj-stacktrace.core-test
+ (:use clojure.test)
+ (:use clj-stacktrace.core)
+ (:use clj-stacktrace.utils))
+
+(def cases
+ [["foo.bar$biz__123" "invoke" "bar.clj" 456
+ {:clojure true :ns "foo.bar" :fn "biz"
+ :file "bar.clj" :line 456 :anon-fn false}]
+
+ ["foo.bar$biz_bat__123" "invoke" "bar.clj" 456
+ {:clojure true :ns "foo.bar" :fn "biz-bat"
+ :file "bar.clj" :line 456 :anon-fn false}]
+
+ ["foo.bar$biz_bat_QMARK___448" "invoke" "bar.clj" 456
+ {:clojure true :ns "foo.bar" :fn "biz-bat?"
+ :file "bar.clj" :line 456 :anon-fn false}]
+
+ ["foo.bar$biz_bat_QMARK___448$fn__456" "invoke" "bar.clj" 456
+ {:clojure true :ns "foo.bar" :fn "biz-bat?"
+ :file "bar.clj" :line 456 :anon-fn true}]
+
+ ["foo.bar$repl$fn__5629.invoke" "invoke" "bar.clj" 456
+ {:clojure true :ns "foo.bar" :fn "repl"
+ :file "bar.clj" :line 456 :anon-fn true}]
+
+ ["foo.bar$repl$read_eval_print__5624" "invoke" "bar.clj" 456
+ {:clojure true :ns "foo.bar" :fn "repl"
+ :file "bar.clj" :line 456 :anon-fn true}]
+
+ ["foo.bar$biz__123$fn__456" "invoke" "bar.clj" 789
+ {:clojure true :ns "foo.bar" :fn "biz"
+ :file "bar.clj" :line 789 :anon-fn true}]
+
+ ["foo.bar_bat$biz__123" "invoke" "bar.clj" 456
+ {:clojure true :ns "foo.bar-bat" :fn "biz"
+ :file "bar.clj" :line 456 :anon-fn false}]
+
+ ["user$eval__345" "invoke" nil -1
+ {:clojure true :ns "user" :fn "eval"
+ :file nil :line nil :anon-fn false}]
+
+ ["lamina.core.observable.ConstantObservable" "message" "observable.clj" 198
+ {:clojure true :ns "lamina.core.observable"
+ :fn "lamina.core.observable.ConstantObservable"
+ :file "observable.clj" :line 198 :anon-fn false}]
+
+ ["clojure.lang.Var" "invoke" "Var.java" 123
+ {:java true :class "clojure.lang.Var" :method "invoke"
+ :file "Var.java" :line 123}]
+
+ ["clojure.proxy.space.SomeClass" "someMethod" "SomeClass.java" 123
+ {:java true :class "clojure.proxy.space.SomeClass" :method "someMethod"
+ :file "SomeClass.java" :line 123}]
+
+ ["some.space.SomeClass" "someMethod" "SomeClass.java" 123
+ {:java true :class "some.space.SomeClass" :method "someMethod"
+ :file "SomeClass.java" :line 123}]
+
+ ["some.space.SomeClass$SomeInner" "someMethod" "SomeClass.java" 123
+ {:java true :class "some.space.SomeClass$SomeInner" :method "someMethod"
+ :file "SomeClass.java" :line 123}]
+
+ ["some.space.SomeClass" "someMethod" nil -1
+ {:java true :class "some.space.SomeClass" :method "someMethod"
+ :file nil :line nil}]])
+
+(deftest test-parse-trace-elem
+ (doseq [[class method file line parsed] cases
+ :let [elem (StackTraceElement. class method file line)]]
+ (is (= parsed (parse-trace-elem elem)))))
+
+(deftest test-trim-redundant
+ (let [trim-fn (resolve 'clj-stacktrace.core/trim-redundant)]
+ (is (= '(d c) (trim-fn '(d c b a) '(f e b a))))
+ (is (= '(c) (trim-fn '(c b a) '(f e b a))))
+ (is (= '(d c) (trim-fn '(d c b a) '(e b a))))))
+
+(deftest test-parse-exception
+ (try
+ (eval '(/))
+ (catch Exception e
+ (is (parse-exception e)))))
diff --git a/test-resources/lib_tests/clj_stacktrace/repl_test.clj b/test-resources/lib_tests/clj_stacktrace/repl_test.clj
new file mode 100644
index 00000000..83d6a46f
--- /dev/null
+++ b/test-resources/lib_tests/clj_stacktrace/repl_test.clj
@@ -0,0 +1,31 @@
+(ns clj-stacktrace.repl-test
+ (:use clojure.test)
+ (:use clj-stacktrace.utils)
+ (:use clj-stacktrace.repl))
+
+(defmacro with-cascading-exception
+ "Execute body in the context of a variable bound to an exception instance
+ that includes a caused-by cascade."
+ [binding-sym & body]
+ `(try (first (lazy-seq (cons (/) nil)))
+ (catch Exception e#
+ (let [~binding-sym e#]
+ ~@body))))
+
+(deftest test-pst
+ (with-cascading-exception e
+ (is (with-out-str (pst e)))
+ (binding [*e e]
+ (is (with-out-str (pst))))))
+
+(deftest test-pst-str
+ (with-cascading-exception e
+ (is (pst-str e))
+ (binding [*e e]
+ (is (pst-str)))))
+
+(deftest test-pst+
+ (with-cascading-exception e
+ (is (with-out-str (pst+ e)))
+ (binding [*e e]
+ (is (with-out-str (pst+))))))
diff --git a/test-resources/lib_tests/datalog/parser/impl_test.cljc b/test-resources/lib_tests/datalog/parser/impl_test.cljc
new file mode 100644
index 00000000..10e1bef9
--- /dev/null
+++ b/test-resources/lib_tests/datalog/parser/impl_test.cljc
@@ -0,0 +1,492 @@
+(ns datalog.parser.impl-test
+ (:require #?(:cljs [cljs.test :refer-macros [is are deftest testing]]
+ :clj [clojure.test :refer [is are deftest testing]])
+ [datalog.parser.impl :as dp]
+ [datalog.parser.type :as t]
+ [datalog.parser.test.util])
+ (:import [clojure.lang ExceptionInfo]))
+
+(deftest bindings
+ (are [form res] (= (dp/parse-binding form) res)
+ '?x
+ (t/->BindScalar (t/->Variable '?x))
+
+ '_
+ (t/->BindIgnore)
+
+ '[?x ...]
+ (t/->BindColl (t/->BindScalar (t/->Variable '?x)))
+
+ '[?x]
+ (t/->BindTuple [(t/->BindScalar (t/->Variable '?x))])
+
+ '[?x ?y]
+ (t/->BindTuple [(t/->BindScalar (t/->Variable '?x)) (t/->BindScalar (t/->Variable '?y))])
+
+ '[_ ?y]
+ (t/->BindTuple [(t/->BindIgnore) (t/->BindScalar (t/->Variable '?y))])
+
+ '[[_ [?x ...]] ...]
+ (t/->BindColl
+ (t/->BindTuple [(t/->BindIgnore)
+ (t/->BindColl
+ (t/->BindScalar (t/->Variable '?x)))]))
+
+ '[[?a ?b ?c]]
+ (t/->BindColl
+ (t/->BindTuple [(t/->BindScalar (t/->Variable '?a))
+ (t/->BindScalar (t/->Variable '?b))
+ (t/->BindScalar (t/->Variable '?c))])))
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse binding"
+ (dp/parse-binding :key))))
+
+(deftest in
+ (are [form res] (= (dp/parse-in form) res)
+ '[?x]
+ [(t/->BindScalar (t/->Variable '?x))]
+
+ '[$ $1 % _ ?x]
+ [(t/->BindScalar (t/->SrcVar '$))
+ (t/->BindScalar (t/->SrcVar '$1))
+ (t/->BindScalar (t/->RulesVar))
+ (t/->BindIgnore)
+ (t/->BindScalar (t/->Variable '?x))]
+
+ '[$ [[_ [?x ...]] ...]]
+ [(t/->BindScalar (t/->SrcVar '$))
+ (t/->BindColl
+ (t/->BindTuple [(t/->BindIgnore)
+ (t/->BindColl
+ (t/->BindScalar (t/->Variable '?x)))]))])
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse binding"
+ (dp/parse-in ['?x :key]))))
+
+(deftest with
+ (is (= (dp/parse-with '[?x ?y])
+ [(t/->Variable '?x) (t/->Variable '?y)]))
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse :with clause"
+ (dp/parse-with '[?x _]))))
+
+(deftest test-parse-find
+ (is (= (dp/parse-find '[?a ?b])
+ (t/->FindRel [(t/->Variable '?a) (t/->Variable '?b)])))
+ (is (= (dp/parse-find '[[?a ...]])
+ (t/->FindColl (t/->Variable '?a))))
+ (is (= (dp/parse-find '[?a .])
+ (t/->FindScalar (t/->Variable '?a))))
+ (is (= (dp/parse-find '[[?a ?b]])
+ (t/->FindTuple [(t/->Variable '?a) (t/->Variable '?b)]))))
+
+(deftest test-parse-aggregate
+ (is (= (dp/parse-find '[?a (count ?b)])
+ (t/->FindRel [(t/->Variable '?a) (t/->Aggregate (t/->PlainSymbol 'count) [(t/->Variable '?b)])])))
+ (is (= (dp/parse-find '[[(count ?a) ...]])
+ (t/->FindColl (t/->Aggregate (t/->PlainSymbol 'count) [(t/->Variable '?a)]))))
+ (is (= (dp/parse-find '[(count ?a) .])
+ (t/->FindScalar (t/->Aggregate (t/->PlainSymbol 'count) [(t/->Variable '?a)]))))
+ (is (= (dp/parse-find '[[(count ?a) ?b]])
+ (t/->FindTuple [(t/->Aggregate (t/->PlainSymbol 'count) [(t/->Variable '?a)]) (t/->Variable '?b)]))))
+
+(deftest test-parse-custom-aggregates
+ (is (= (dp/parse-find '[(aggregate ?f ?a)])
+ (t/->FindRel [(t/->Aggregate (t/->Variable '?f) [(t/->Variable '?a)])])))
+ (is (= (dp/parse-find '[?a (aggregate ?f ?b)])
+ (t/->FindRel [(t/->Variable '?a) (t/->Aggregate (t/->Variable '?f) [(t/->Variable '?b)])])))
+ (is (= (dp/parse-find '[[(aggregate ?f ?a) ...]])
+ (t/->FindColl (t/->Aggregate (t/->Variable '?f) [(t/->Variable '?a)]))))
+ (is (= (dp/parse-find '[(aggregate ?f ?a) .])
+ (t/->FindScalar (t/->Aggregate (t/->Variable '?f) [(t/->Variable '?a)]))))
+ (is (= (dp/parse-find '[[(aggregate ?f ?a) ?b]])
+ (t/->FindTuple [(t/->Aggregate (t/->Variable '?f) [(t/->Variable '?a)]) (t/->Variable '?b)]))))
+
+(deftest test-parse-find-elements
+ (is (= (dp/parse-find '[(count ?b 1 $x) .])
+ (t/->FindScalar (t/->Aggregate (t/->PlainSymbol 'count)
+ [(t/->Variable '?b)
+ (t/->Constant 1)
+ (t/->SrcVar '$x)])))))
+
+(deftest clauses
+ (are [form res] (= (set (dp/parse-rules form)) res)
+ '[[(rule ?x)
+ [?x :name _]]]
+ #{(t/->Rule
+ (t/->PlainSymbol 'rule)
+ [(t/->RuleBranch
+ (t/->RuleVars nil [(t/->Variable '?x)])
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?x) (t/->Constant :name) (t/->Placeholder)])])])})
+ (is (thrown-with-msg? ExceptionInfo #"Reference to the unknown variable"
+ (dp/parse-rules '[[(rule ?x) [?x :name ?y]]]))))
+
+(deftest rule-vars
+ (are [form res] (= (set (dp/parse-rules form)) res)
+ '[[(rule [?x] ?y)
+ [_]]]
+ #{(t/->Rule
+ (t/->PlainSymbol 'rule)
+ [(t/->RuleBranch
+ (t/->RuleVars [(t/->Variable '?x)] [(t/->Variable '?y)])
+ [(t/->Pattern (t/->DefaultSrc) [(t/->Placeholder)])])])}
+
+ '[[(rule [?x ?y] ?a ?b)
+ [_]]]
+ #{(t/->Rule
+ (t/->PlainSymbol 'rule)
+
+ [(t/->RuleBranch
+ (t/->RuleVars [(t/->Variable '?x) (t/->Variable '?y)]
+ [(t/->Variable '?a) (t/->Variable '?b)])
+ [(t/->Pattern (t/->DefaultSrc) [(t/->Placeholder)])])])}
+
+ '[[(rule [?x])
+ [_]]]
+ #{(t/->Rule
+ (t/->PlainSymbol 'rule)
+ [(t/->RuleBranch
+ (t/->RuleVars [(t/->Variable '?x)] nil)
+ [(t/->Pattern (t/->DefaultSrc) [(t/->Placeholder)])])])})
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse rule-vars"
+ (dp/parse-rules '[[(rule) [_]]])))
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse rule-vars"
+ (dp/parse-rules '[[(rule []) [_]]])))
+
+ (is (thrown-with-msg? ExceptionInfo #"Rule variables should be distinct"
+ (dp/parse-rules '[[(rule ?x ?y ?x) [_]]])))
+
+ (is (thrown-with-msg? ExceptionInfo #"Rule variables should be distinct"
+ (dp/parse-rules '[[(rule [?x ?y] ?z ?x) [_]]]))))
+
+(deftest branches
+ (are [form res] (= (set (dp/parse-rules form)) res)
+ '[[(rule ?x)
+ [:a]
+ [:b]]
+ [(rule ?x)
+ [:c]]]
+ #{(t/->Rule
+ (t/->PlainSymbol 'rule)
+ [(t/->RuleBranch
+ (t/->RuleVars nil [(t/->Variable '?x)])
+ [(t/->Pattern (t/->DefaultSrc) [(t/->Constant :a)])
+ (t/->Pattern (t/->DefaultSrc) [(t/->Constant :b)])])
+ (t/->RuleBranch
+ (t/->RuleVars nil [(t/->Variable '?x)])
+ [(t/->Pattern (t/->DefaultSrc) [(t/->Constant :c)])])])}
+
+ '[[(rule ?x)
+ [:a]
+ [:b]]
+ [(other ?x)
+ [:c]]]
+ #{(t/->Rule
+ (t/->PlainSymbol 'rule)
+ [(t/->RuleBranch
+ (t/->RuleVars nil [(t/->Variable '?x)])
+ [(t/->Pattern (t/->DefaultSrc) [(t/->Constant :a)])
+ (t/->Pattern (t/->DefaultSrc) [(t/->Constant :b)])])])
+ (t/->Rule
+ (t/->PlainSymbol 'other)
+ [(t/->RuleBranch
+ (t/->RuleVars nil [(t/->Variable '?x)])
+ [(t/->Pattern (t/->DefaultSrc) [(t/->Constant :c)])])])})
+
+ (is (thrown-with-msg? ExceptionInfo #"Rule branch should have clauses"
+ (dp/parse-rules '[[(rule ?x)]])))
+
+ (is (thrown-with-msg? ExceptionInfo #"Arity mismatch"
+ (dp/parse-rules '[[(rule ?x) [_]]
+ [(rule ?x ?y) [_]]])))
+
+ (is (thrown-with-msg? ExceptionInfo #"Arity mismatch"
+ (dp/parse-rules '[[(rule ?x) [_]]
+ [(rule [?x]) [_]]]))))
+
+(deftest pattern
+ (are [clause pattern] (= (dp/parse-clause clause) pattern)
+ '[?e ?a ?v]
+ (t/->Pattern (t/->DefaultSrc) [(t/->Variable '?e) (t/->Variable '?a) (t/->Variable '?v)])
+
+ '[_ ?a _ _]
+ (t/->Pattern (t/->DefaultSrc) [(t/->Placeholder) (t/->Variable '?a) (t/->Placeholder) (t/->Placeholder)])
+
+ '[$x _ ?a _ _]
+ (t/->Pattern (t/->SrcVar '$x) [(t/->Placeholder) (t/->Variable '?a) (t/->Placeholder) (t/->Placeholder)])
+
+ '[$x _ :name ?v]
+ (t/->Pattern (t/->SrcVar '$x) [(t/->Placeholder) (t/->Constant :name) (t/->Variable '?v)])
+
+ '[$x _ sym ?v]
+ (t/->Pattern (t/->SrcVar '$x) [(t/->Placeholder) (t/->Constant 'sym) (t/->Variable '?v)])
+
+ '[$x _ $src-sym ?v]
+ (t/->Pattern (t/->SrcVar '$x) [(t/->Placeholder) (t/->Constant '$src-sym) (t/->Variable '?v)]))
+
+ (is (thrown-with-msg? ExceptionInfo #"Pattern could not be empty"
+ (dp/parse-clause '[]))))
+
+(deftest test-pred
+ (are [clause res] (= (dp/parse-clause clause) res)
+ '[(pred ?a 1)]
+ (t/->Predicate (t/->PlainSymbol 'pred) [(t/->Variable '?a) (t/->Constant 1)])
+
+ '[(pred)]
+ (t/->Predicate (t/->PlainSymbol 'pred) [])
+
+ '[(?custom-pred ?a)]
+ (t/->Predicate (t/->Variable '?custom-pred) [(t/->Variable '?a)])))
+
+(deftest test-fn
+ (are [clause res] (= (dp/parse-clause clause) res)
+ '[(fn ?a 1) ?x]
+ (t/->Function (t/->PlainSymbol 'fn) [(t/->Variable '?a) (t/->Constant 1)] (t/->BindScalar (t/->Variable '?x)))
+
+ '[(fn) ?x]
+ (t/->Function (t/->PlainSymbol 'fn) [] (t/->BindScalar (t/->Variable '?x)))
+
+ '[(?custom-fn) ?x]
+ (t/->Function (t/->Variable '?custom-fn) [] (t/->BindScalar (t/->Variable '?x)))
+
+ '[(?custom-fn ?arg) ?x]
+ (t/->Function (t/->Variable '?custom-fn) [(t/->Variable '?arg)] (t/->BindScalar (t/->Variable '?x)))))
+
+(deftest rule-expr
+ (are [clause res] (= (dp/parse-clause clause) res)
+ '(friends ?x ?y)
+ (t/->RuleExpr (t/->DefaultSrc) (t/->PlainSymbol 'friends) [(t/->Variable '?x) (t/->Variable '?y)])
+
+ '(friends "Ivan" _)
+ (t/->RuleExpr (t/->DefaultSrc) (t/->PlainSymbol 'friends) [(t/->Constant "Ivan") (t/->Placeholder)])
+
+ '($1 friends ?x ?y)
+ (t/->RuleExpr (t/->SrcVar '$1) (t/->PlainSymbol 'friends) [(t/->Variable '?x) (t/->Variable '?y)])
+
+ '(friends something)
+ (t/->RuleExpr (t/->DefaultSrc) (t/->PlainSymbol 'friends) [(t/->Constant 'something)]))
+
+ (is (thrown-with-msg? ExceptionInfo #"rule-expr requires at least one argument"
+ (dp/parse-clause '(friends)))))
+
+(deftest not-clause
+ (are [clause res] (= (dp/parse-clause clause) res)
+ '(not [?e :follows ?x])
+ (t/->Not
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Variable '?x)]
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])])
+
+ '(not
+ [?e :follows ?x]
+ [?x _ ?y])
+ (t/->Not
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Variable '?x) (t/->Variable '?y)]
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])
+ (t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?x) (t/->Placeholder) (t/->Variable '?y)])])
+
+ '($1 not [?x])
+ (t/->Not
+ (t/->SrcVar '$1)
+ [(t/->Variable '?x)]
+ [(t/->Pattern (t/->DefaultSrc) [(t/->Variable '?x)])])
+
+ '(not-join [?e ?y]
+ [?e :follows ?x]
+ [?x _ ?y])
+ (t/->Not
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Variable '?y)]
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])
+ (t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?x) (t/->Placeholder) (t/->Variable '?y)])])
+
+ '($1 not-join [?e] [?e :follows ?x])
+ (t/->Not
+ (t/->SrcVar '$1)
+ [(t/->Variable '?e)]
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])]))
+
+ (is (thrown-with-msg? ExceptionInfo #"Join variable not declared inside clauses: \[\?x\]"
+ (dp/parse-clause '(not-join [?x] [?y]))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Join variables should not be empty"
+ (dp/parse-clause '(not-join [] [?y]))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Join variables should not be empty"
+ (dp/parse-clause '(not [_]))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse 'not-join' clause"
+ (dp/parse-clause '(not-join [?x]))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse 'not' clause"
+ (dp/parse-clause '(not))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Join variable not declared inside clauses: \[\?y\]"
+ (dp/parse-clause '(not-join [?y]
+ (not-join [?x]
+ [?x :follows ?y]))))))
+
+(deftest or-clause
+ (are [clause res] (= (dp/parse-clause clause) res)
+ '(or [?e :follows ?x])
+ (t/->Or
+ (t/->DefaultSrc)
+ (t/->RuleVars nil [(t/->Variable '?e) (t/->Variable '?x)])
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])])
+
+ '(or
+ [?e :follows ?x]
+ [?e :friend ?x])
+ (t/->Or
+ (t/->DefaultSrc)
+ (t/->RuleVars nil [(t/->Variable '?e) (t/->Variable '?x)])
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])
+ (t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :friend) (t/->Variable '?x)])])
+
+ '(or
+ [?e :follows ?x]
+ (and
+ [?e :friend ?x]
+ [?x :friend ?e]))
+ (t/->Or
+ (t/->DefaultSrc)
+ (t/->RuleVars nil [(t/->Variable '?e) (t/->Variable '?x)])
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])
+ (t/->And
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :friend) (t/->Variable '?x)])
+ (t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?x) (t/->Constant :friend) (t/->Variable '?e)])])])
+
+ '($1 or [?x])
+ (t/->Or
+ (t/->SrcVar '$1)
+ (t/->RuleVars nil [(t/->Variable '?x)])
+ [(t/->Pattern (t/->DefaultSrc) [(t/->Variable '?x)])])
+
+ '(or-join [?e]
+ [?e :follows ?x]
+ [?e :friend ?y])
+ (t/->Or
+ (t/->DefaultSrc)
+ (t/->RuleVars nil [(t/->Variable '?e)])
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])
+ (t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :friend) (t/->Variable '?y)])])
+
+ '(or-join [[?e]]
+ (and [?e :follows ?x]
+ [?e :friend ?y]))
+ (t/->Or
+ (t/->DefaultSrc)
+ (t/->RuleVars [(t/->Variable '?e)] nil)
+ [(t/->And
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])
+ (t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :friend) (t/->Variable '?y)])])])
+
+ '($1 or-join [[?e] ?x]
+ [?e :follows ?x])
+ (t/->Or
+ (t/->SrcVar '$1)
+ (t/->RuleVars [(t/->Variable '?e)] [(t/->Variable '?x)])
+ [(t/->Pattern
+ (t/->DefaultSrc)
+ [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])]))
+
+ ;; These tests reflect the or-join semantics of Datomic Datalog, https://docs.datomic.com/on-prem/query.html
+ ;; TODO use record constructors instead of wordy literals as for rest in this buffer
+ (is (= (dp/parse-clause '(or-join [?x] [?y]))
+ '#datalog.parser.type.Or{:source #datalog.parser.type.DefaultSrc{},
+ :rule-vars #datalog.parser.type.RuleVars{:required nil,
+ :free [#datalog.parser.type.Variable{:symbol ?x}]},
+ :clauses [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{},
+ :pattern [#datalog.parser.type.Variable{:symbol ?y}]}]}))
+ (is (= (dp/parse-clause '(or-join [?x ?y] [?x ?y] [?y]))
+ '#datalog.parser.type.Or{:source #datalog.parser.type.DefaultSrc{},
+ :rule-vars #datalog.parser.type.RuleVars{:required nil,
+ :free [#datalog.parser.type.Variable{:symbol ?x}
+ #datalog.parser.type.Variable{:symbol ?y}]},
+ :clauses [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{},
+ :pattern [#datalog.parser.type.Variable{:symbol ?x}
+ #datalog.parser.type.Variable{:symbol ?y}]}
+ #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{},
+ :pattern [#datalog.parser.type.Variable{:symbol ?y}]}]}))
+
+ (is (= (dp/parse-clause '(or-join [?y]
+ (or-join [?x]
+ [?x :follows ?y])))
+ '#datalog.parser.type.Or{:source #datalog.parser.type.DefaultSrc{},
+ :rule-vars #datalog.parser.type.RuleVars{:required nil,
+ :free [#datalog.parser.type.Variable{:symbol ?y}]},
+ :clauses [#datalog.parser.type.Or{:source #datalog.parser.type.DefaultSrc{},
+ :rule-vars #datalog.parser.type.RuleVars{:required nil,
+ :free [#datalog.parser.type.Variable{:symbol ?x}]},
+ :clauses [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{},
+ :pattern [#datalog.parser.type.Variable{:symbol ?x}
+ #datalog.parser.type.Constant{:value :follows} #datalog.parser.type.Variable{:symbol ?y}]}]}]}))
+
+
+ (is (thrown-with-msg? ExceptionInfo #"Join variable not declared inside clauses: \[\?y\]"
+ (dp/parse-clause '(or [?x] [?x ?y]))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Join variable not declared inside clauses: \[\?y\]"
+ (dp/parse-clause '(or [?x] [?y]))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse rule-vars"
+ (dp/parse-clause '(or-join [] [?y]))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Join variables should not be empty"
+ (dp/parse-clause '(or [_]))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse 'or-join' clause"
+ (dp/parse-clause '(or-join [?x]))))
+
+ (is (thrown-with-msg? ExceptionInfo #"Cannot parse 'or' clause"
+ (dp/parse-clause '(or)))))
+
+
+(deftest test-parse-return-maps
+ (testing "failed parsing"
+ (is (thrown-with-msg? ExceptionInfo #"Only one of these three options is allowed: :keys :strs :syms"
+ (dp/parse-return-maps {:keys '("keys" "strs" "syms") :syms '("keys" "strs" "syms")}))))
+ (testing "parsing correct options"
+ (is (= #datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key "keys"} #datalog.parser.type.MappingKey{:mapping-key "strs"} #datalog.parser.type.MappingKey{:mapping-key "syms"})}
+ (dp/parse-return-maps {:keys '("keys" "strs" "syms")})))
+ (is (= #datalog.parser.type.ReturnMaps{:mapping-type :strs, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key "keys"} #datalog.parser.type.MappingKey{:mapping-key "strs"} #datalog.parser.type.MappingKey{:mapping-key "syms"})}
+ (dp/parse-return-maps {:strs '("keys" "strs" "syms")})))
+ (is (= #datalog.parser.type.ReturnMaps{:mapping-type :syms, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key "keys"} #datalog.parser.type.MappingKey{:mapping-key "strs"} #datalog.parser.type.MappingKey{:mapping-key "syms"})}
+ (dp/parse-return-maps {:syms '("keys" "strs" "syms")})))))
diff --git a/test-resources/lib_tests/datalog/parser/pull_test.cljc b/test-resources/lib_tests/datalog/parser/pull_test.cljc
new file mode 100644
index 00000000..1a13ad84
--- /dev/null
+++ b/test-resources/lib_tests/datalog/parser/pull_test.cljc
@@ -0,0 +1,42 @@
+(ns datalog.parser.pull-test
+ (:require [datalog.parser.pull :as dpp]
+ #?(:cljs [cljs.test :as t :refer-macros [is are deftest testing]]
+ :clj [clojure.test :as t :refer [is are deftest testing]])))
+
+#?(:cljs
+ (def Throwable js/Error))
+
+(deftest test-parse-pattern
+ (are [pattern expected] (= expected (dpp/parse-pull pattern))
+ '[:db/id :foo/bar]
+ (dpp/->PullSpec false {:db/id {:attr :db/id}
+ :foo/bar {:attr :foo/bar}})
+
+ '[(limit :foo 1)]
+ (dpp/->PullSpec false {:foo {:attr :foo :limit 1}})
+
+ '[* (default :foo "bar")]
+ (dpp/->PullSpec true {:foo {:attr :foo :default "bar"}})
+
+ '[{:foo ...}]
+ (dpp/->PullSpec false {:foo {:attr :foo :recursion nil}})
+
+ '[{(limit :foo 2) [:bar :me]}]
+ (dpp/->PullSpec
+ false
+ {:foo {:attr :foo
+ :limit 2
+ :subpattern (dpp/->PullSpec
+ false
+ {:bar {:attr :bar}
+ :me {:attr :me}})}})))
+
+(deftest test-parse-bad-limit
+ (is
+ (thrown? Throwable (dpp/parse-pull '[(limit :foo :bar)]))))
+
+(deftest test-parse-bad-default
+ (is
+ (thrown? Throwable (dpp/parse-pull '[(default 1 :bar)]))))
+
+#_(t/test-ns 'datahike.test.pull-parser)
diff --git a/test-resources/lib_tests/datalog/parser/test/util.cljc b/test-resources/lib_tests/datalog/parser/test/util.cljc
new file mode 100644
index 00000000..747ec8b8
--- /dev/null
+++ b/test-resources/lib_tests/datalog/parser/test/util.cljc
@@ -0,0 +1,16 @@
+(ns datalog.parser.test.util
+ (:require [#?(:clj clojure.test :cljs cljs.test) :as test]))
+
+#?(:clj
+ (defmethod test/assert-expr 'thrown-msg? [msg form]
+ (let [[_ match & body] form]
+ `(try ~@body
+ (test/do-report {:type :fail, :message ~msg, :expected '~form, :actual nil})
+ (catch Throwable e#
+ (let [m# (.getMessage e#)]
+ (test/do-report
+ {:type (if (= ~match m#) :pass :fail)
+ :message ~msg
+ :expected '~form
+ :actual e#}))
+ e#)))))
diff --git a/test-resources/lib_tests/datalog/parser_test.cljc b/test-resources/lib_tests/datalog/parser_test.cljc
new file mode 100644
index 00000000..95764275
--- /dev/null
+++ b/test-resources/lib_tests/datalog/parser_test.cljc
@@ -0,0 +1,96 @@
+(ns datalog.parser-test
+ (:require #?(:cljs [cljs.test :refer-macros [are deftest]]
+ :clj [clojure.test :refer [are deftest]])
+ [datalog.parser :as parser]
+ [datalog.parser.test.util]))
+
+(deftest validation
+ (are [q result] (= result (parser/parse q))
+ '[:find ?e
+ :in $ ?fname ?lname
+ :keys foo
+ :where [?e :user/firstName ?fname]
+ [?e :user/lastName ?lname]]
+ '#datalog.parser.type.Query{:qfind #datalog.parser.type.FindRel{:elements [#datalog.parser.type.Variable{:symbol ?e}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}}
+
+ '[:find ?e
+ :in $ ?fname ?lname
+ :strs foo
+ :where [?e :user/firstName ?fname]
+ [?e :user/lastName ?lname]]
+ '#datalog.parser.type.Query{:qfind #datalog.parser.type.FindRel{:elements [#datalog.parser.type.Variable{:symbol ?e}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :strs, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}}
+
+ '[:find ?e
+ :in $ ?fname ?lname
+ :syms foo
+ :where [?e :user/firstName ?fname]
+ [?e :user/lastName ?lname]]
+ '#datalog.parser.type.Query{:qfind #datalog.parser.type.FindRel{:elements [#datalog.parser.type.Variable{:symbol ?e}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :syms, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}}
+
+ '{:find [?e]
+ :in [$ ?fname ?lname]
+ :keys [foo]
+ :where [[?e :user/firstName ?fname]
+ [?e :user/lastName ?lname]]}
+ '#datalog.parser.type.Query{:qfind #datalog.parser.type.FindRel{:elements [#datalog.parser.type.Variable{:symbol ?e}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}}
+
+ '{:find [[?e ?fname]]
+ :keys [foo]
+ :in [$ ?fname ?lname]
+ :where [[?e :user/firstName ?fname]
+ [?e :user/lastName ?lname]]}
+#datalog.parser.type.Query{:qfind #datalog.parser.type.FindTuple{:elements [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Variable{:symbol ?fname}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}}
+ ))
+
+(deftest validation-fails
+ (are [q msg] (thrown-msg? msg (parser/parse q))
+ '[:find ?e :where [?x]]
+ "Query for unknown vars: [?e]"
+
+ '[:find ?e :with ?f :where [?e]]
+ "Query for unknown vars: [?f]"
+
+ '[:find ?e ?x ?t :in ?x :where [?e]]
+ "Query for unknown vars: [?t]"
+
+ '[:find ?x ?e :with ?y ?e :where [?x ?e ?y]]
+ ":find and :with should not use same variables: [?e]"
+
+ '[:find ?e :in $ $ ?x :where [?e]]
+ "Vars used in :in should be distinct"
+
+ '[:find ?e :in ?x $ ?x :where [?e]]
+ "Vars used in :in should be distinct"
+
+ '[:find ?e :in $ % ?x % :where [?e]]
+ "Vars used in :in should be distinct"
+
+ '[:find ?n :with ?e ?f ?e :where [?e ?f ?n]]
+ "Vars used in :with should be distinct"
+
+ '[:find ?x :where [$1 ?x]]
+ "Where uses unknown source vars: [$1]"
+
+ '[:find ?x :in $1 :where [$2 ?x]]
+ "Where uses unknown source vars: [$2]"
+
+ '[:find ?e :where (rule ?e)]
+ "Missing rules var '%' in :in"
+
+ '[:find ?e :where [?e] :limit [42]]
+ "Cannot parse :limit, expected java.lang.Long"
+
+ '[:find ?e :where [?e] :offset [666]]
+ "Cannot parse :offset, expected java.lang.Long"
+
+ '[:find ?e :keys foo bar :where [?e] :offset 666]
+ "Count of :keys/:strs/:syms must match count of :find"
+
+ '[:find ?e ?f :keys foo :where [?e ?f] :offset 666]
+ "Count of :keys/:strs/:syms must match count of :find"
+
+ '[:find [?e ?f] :keys foo bar :where [?e ?f] :offset 666]
+ "Count of :keys/:strs/:syms must match count of :find"
+
+ '[:find ?e :strs '(foo bar) :keys '("foo" "bar") :where [?e] :offset 666]
+ "Only one of these three options is allowed: :keys :strs :syms"))
diff --git a/test-resources/lib_tests/datalog/unparser_test.clj b/test-resources/lib_tests/datalog/unparser_test.clj
new file mode 100644
index 00000000..8dc959ef
--- /dev/null
+++ b/test-resources/lib_tests/datalog/unparser_test.clj
@@ -0,0 +1,25 @@
+(ns datalog.unparser-test
+ (:require [datalog.unparser :refer [unparse]]
+ [datalog.parser :refer [parse]]
+ [clojure.test :refer [deftest testing is] :as test])
+ (:use [datalog.unparser]))
+
+(let [q '[:find (sum ?balance-before) ?balance-before
+ :in $before $after $txn $txs
+ :where
+ [(= ?balance-before 42)]]]
+ (deftest unparse-roundtrip-test
+ (testing "Datahike query unparsing."
+ (is (= q (unparse (parse q)))))))
+
+
+
+(comment ;; TODO
+ (let [q '[:find ?foo ?baz
+ :in $before $after
+ :where
+ [(= ?balance-before 42)]
+ (not [?foo :bar ?baz])]]
+ (deftest unparse-roundtrip-test
+ (testing "Datahike query unparsing."
+ (is (= q (unparse (parse q))))))))
diff --git a/test/babashka/java_security_test.clj b/test/babashka/java_security_test.clj
index d619a4b2..f7c02ead 100644
--- a/test/babashka/java_security_test.clj
+++ b/test/babashka/java_security_test.clj
@@ -11,10 +11,12 @@
(clojure.walk/postwalk-replace {::algo algo}
'(defn signature [^String s]
(let [algorithm (java.security.MessageDigest/getInstance ::algo)
- digest (.digest algorithm (.getBytes s))]
- (format "%032x" (java.math.BigInteger. 1 digest))))))
+ digest (.digest algorithm (.getBytes s))
+ size (get {"SHA-256" 64} ::algo 32)]
+ (format (str "%0" size "x") (java.math.BigInteger. 1 digest))))))
(deftest java-security-test
(is (= "49f68a5c8493ec2c0bf489821c21fc3b" (bb (list 'do (signature "MD5") '(signature "hi")))))
- (is (= "c22b5f9178342609428d6f51b2c5af4c0bde6a42" (bb (list 'do (signature "SHA-1") '(signature "hi")))))
- (is (= "8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4" (bb (list 'do (signature "SHA-256") '(signature "hi"))))))
+ (is (= "c22b5f9178342609428d6f51b2c5af4c0bde6a42" (bb (list 'do (signature "SHA-1") '(signature "hi")))))
+ (is (= "8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4" (bb (list 'do (signature "SHA-256") '(signature "hi")))))
+ (is (= "035afb1672de25549287fa4f6c108c1269c2a1d2390bf069520a95d1fec25e85" (bb (list 'do (signature "SHA-256") '(signature "654321f5fab07590a9e77e19ac4ccf53c8ab05f232b197432b62f2ec0677651bfc4c04"))))))