# testing Ludus langauge constructs (try (os/cd "janet") ([_] nil)) # for REPL (import /scanner :as s) (import /parser :as p) (import /validate :as v) (import /interpreter :as i) (import /errors :as e) (import /base :as b) (use judge) (defn run [source] (def ctx @{}) (def scanned (s/scan source :test)) (when (any? (scanned :errors)) (e/scan-error (scanned :errors)) (error "scanning errors")) (def parsed (p/parse scanned)) (when (any? (parsed :errors)) (e/parse-error (parsed :errors)) (error "parsing errors")) (def valid (v/valid parsed ctx)) (when (any? (valid :errors)) (each err (valid :errors) (e/validation-error err)) (error "validation errors")) (i/interpret (parsed :ast) ctx)) (deftest "returns bare values from single-line scripts" (test (run "true") true) (test (run "false") false) (test (run "nil") :^nil) (test (run "12.34") 12.34) (test (run "-32") -32) (test (run "0") 0) (test (run ":foo") :foo) (test (run ":bar") :bar) (test (run `"a string, a text, a language"`) "a string, a text, a language")) (deftest "returns empty collections from single-line scripts" (test (run "()") []) (test (run "#{}") @{}) (test (run "${}") @{:^type :set}) (test (run "[]") @[])) (deftest "returns populated collections from single-line scripts" (test (run "(1, 2, 3)") [1 2 3]) (test (run "[:a, :b, :c]") @[:a :b :c]) (test (run "${1, 2, 3, 3}") @{1 true 2 true 3 true :^type :set}) (test (run "#{:a 1, :b 2}") @{:a 1 :b 2})) (deftest "returns nested collections from single-line scripts" (test (run "((), (1, 2), [:a, (:b)], #{:foo true, :bar false})") [[] [1 2] @[:a [:b]] @{:bar false :foo true}]) (test (run `#{:foo #{:bar "thing", :baz (1, :foo, nil)}}`) @{:foo @{:bar "thing" :baz [1 :foo :^nil]}})) (deftest "binds names in let bindings with various patterns" (test (run `let foo = :bar; foo`) :bar) (test (run `let 42 = 42`) 42) (test (run `let foo = :bar; let quux = 42; (foo, quux)`) [:bar 42]) (test (run `let (:ok, value) = (:ok, 42); value`) 42) (test (run `let #{:a x, ...} = #{:a 1, :b 2}; x`) 1)) (deftest "executes if/then/else properly" (test (run `if nil then :foo else :bar`) :bar) (test (run `if false then :foo else :bar`) :bar) (test (run `if true then :foo else :bar`) :foo) (test (run `if 42 then :foo else panic! "oops"`) :foo)) (deftest "panics" (test-error (run `panic! "oops"`) {:msg "oops" :node {:data {:data "oops" :token {:input :test :lexeme "\"oops\"" :line 1 :literal "oops" :source "panic! \"oops\"" :start 7 :type :string} :type :string} :token {:input :test :lexeme "panic!" :line 1 :literal :none :source "panic! \"oops\"" :start 0 :type :panic} :type :panic}}) ) (deftest "no match in let panics" (test-error (run "let :foo = :bar") {:msg "no match: let binding" :node {:data @[{:data :foo :token {:input :test :lexeme ":foo" :line 1 :literal :foo :source "let :foo = :bar" :start 4 :type :keyword} :type :keyword} {:data :bar :token {:input :test :lexeme ":bar" :line 1 :literal :bar :source "let :foo = :bar" :start 11 :type :keyword} :type :keyword}] :token {:input :test :lexeme "let" :line 1 :literal :none :source "let :foo = :bar" :start 0 :type :let} :type :let} :value :bar}) ) (deftest "blocks execute code and work" (test (run ` let bar = 12 let foo = { let bar = 42 let baz = :quux :foo } (foo, bar) `) [:foo 12]) (test (run ` let foo = { let bar = 12 { let bar = 15 bar } } `) 15)) (deftest "unbound name panics" (test-error (run `foo`) "validation errors")) (deftest "rebinding name panics" (test-error (run `let foo = 42; let foo = 23`) "validation errors")) (deftest "when forms work as expected" (test (run ` when { false -> :nope nil -> :nope 12 -> :yes } `) :yes) (test-error (run ` when { false -> :nope nil -> :nope } `) {:msg "no match: when form" :node {:data @[[{:data false :token {:input :test :lexeme "false" :line 2 :literal false :source " when {\n false -> :nope\n nil -> :nope\n }\n " :start 12 :type :false} :type :bool} {:data :nope :token {:input :test :lexeme ":nope" :line 2 :literal :nope :source " when {\n false -> :nope\n nil -> :nope\n }\n " :start 21 :type :keyword} :type :keyword}] [{:token {:input :test :lexeme "nil" :line 3 :literal :none :source " when {\n false -> :nope\n nil -> :nope\n }\n " :start 30 :type :nil} :type :nil} {:data :nope :token {:input :test :lexeme ":nope" :line 3 :literal :nope :source " when {\n false -> :nope\n nil -> :nope\n }\n " :start 37 :type :keyword} :type :keyword}]] :token {:input :test :lexeme "when" :line 1 :literal :none :source " when {\n false -> :nope\n nil -> :nope\n }\n " :start 2 :type :when} :type :when}}) ) (deftest "match forms work as expected" (test (run ` match :foo with { :bar -> :nope :baz -> :nope x -> x } `) :foo) (test (run ` let foo = 42 match (:ok, foo) with { (:err, _) -> :nope (:ok, :foo) -> :nope (:ok, _) -> :yes } `) :yes) (test-error (run ` let foo = "foo" match foo with { "bar" -> :nope "baz" -> :nope 12.34 -> :nope } `) {:msg "no match: match form" :node @{:data [{:data "foo" :token {:input :test :lexeme "foo" :line 2 :literal :none :source " let foo = \"foo\"\n match foo with {\n \"bar\" -> :nope\n \"baz\" -> :nope\n 12.34 -> :nope \n }\n " :start 26 :type :word} :type :word} @[[{:data "bar" :token {:input :test :lexeme "\"bar\"" :line 3 :literal "bar" :source " let foo = \"foo\"\n match foo with {\n \"bar\" -> :nope\n \"baz\" -> :nope\n 12.34 -> :nope \n }\n " :start 40 :type :string} :type :string} nil {:data :nope :token {:input :test :lexeme ":nope" :line 3 :literal :nope :source " let foo = \"foo\"\n match foo with {\n \"bar\" -> :nope\n \"baz\" -> :nope\n 12.34 -> :nope \n }\n " :start 49 :type :keyword} :type :keyword}] [{:data "baz" :token {:input :test :lexeme "\"baz\"" :line 4 :literal "baz" :source " let foo = \"foo\"\n match foo with {\n \"bar\" -> :nope\n \"baz\" -> :nope\n 12.34 -> :nope \n }\n " :start 58 :type :string} :type :string} nil {:data :nope :token {:input :test :lexeme ":nope" :line 4 :literal :nope :source " let foo = \"foo\"\n match foo with {\n \"bar\" -> :nope\n \"baz\" -> :nope\n 12.34 -> :nope \n }\n " :start 67 :type :keyword} :type :keyword}] [{:data 12.34 :token {:input :test :lexeme "12.34" :line 5 :literal 12.34 :source " let foo = \"foo\"\n match foo with {\n \"bar\" -> :nope\n \"baz\" -> :nope\n 12.34 -> :nope \n }\n " :start 76 :type :number} :type :number} nil {:data :nope :token {:input :test :lexeme ":nope" :line 5 :literal :nope :source " let foo = \"foo\"\n match foo with {\n \"bar\" -> :nope\n \"baz\" -> :nope\n 12.34 -> :nope \n }\n " :start 85 :type :keyword} :type :keyword}]]] :match @match-fn :token {:input :test :lexeme "match" :line 2 :literal :none :source " let foo = \"foo\"\n match foo with {\n \"bar\" -> :nope\n \"baz\" -> :nope\n 12.34 -> :nope \n }\n " :start 20 :type :match} :type :match} :value "foo"}) ) (deftest "string patterns work as expected" (test (run `let "I {verb} the {noun}" = "I am the walrus"; (verb, noun)`) ["am" "walrus"]) (test (run `let "a {b} c {d}" = "a because I love you c yourself out the door"; (b, d)`) ["because I love you" "yourself out the door"]) ) (deftest "lambdas may be defined and called" (test (run ` let foo = fn () -> :foo foo () `) :foo) (test (run ` let pair = fn (x, y) -> (x, y) pair (:foo, :bar) `) [:foo :bar]) (test-error (run ` let foo = fn () -> :foo foo (:bar) `) {:called @{:^type :fn :body [[{:data @[] :token {:input :test :lexeme "(" :line 1 :source " let foo = fn () -> :foo\n foo (:bar)\n " :start 15 :type :lparen} :type :tuple} nil {:data :foo :token {:input :test :lexeme ":foo" :line 1 :literal :foo :source " let foo = fn () -> :foo\n foo (:bar)\n " :start 21 :type :keyword} :type :keyword}]] :ctx @{} :match @match-fn} :msg "no match: function call" :node {:data "foo" :token {:input :test :lexeme "foo" :line 2 :literal :none :source " let foo = fn () -> :foo\n foo (:bar)\n " :start 28 :type :word} :type :word} :value [:bar]}) )