diff --git a/.gitignore b/.gitignore index c4ea3ce..ab9afee 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ target/repl-port .repl-buffer .repl-buffer.janet .env +janet/jpm_tree diff --git a/janet/judge b/janet/judge new file mode 120000 index 0000000..1ececd9 --- /dev/null +++ b/janet/judge @@ -0,0 +1 @@ +./jpm_tree/bin/judge \ No newline at end of file diff --git a/janet/judgy.fish b/janet/judgy.fish new file mode 100755 index 0000000..4f7b069 --- /dev/null +++ b/janet/judgy.fish @@ -0,0 +1,9 @@ +#!/opt/homebrew/bin/fish + +set FILE $argv[1] +set TESTFILE (string join "" $FILE ".tested") +judge $FILE +if test -e $TESTFILE + cp $TESTFILE $FILE + rm $TESTFILE +end diff --git a/janet/language.test.janet b/janet/language.test.janet new file mode 100644 index 0000000..b375ac2 --- /dev/null +++ b/janet/language.test.janet @@ -0,0 +1,371 @@ +# 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]}) +) diff --git a/janet/prelude.test.janet b/janet/prelude.test.janet new file mode 100644 index 0000000..6be9933 --- /dev/null +++ b/janet/prelude.test.janet @@ -0,0 +1,34 @@ +# testing the prelude +(try (os/cd "janet") ([_] nil)) +(import /scanner :as s) +(import /parser :as p) +(import /validate :as v) +(import /interpreter :as i) +(import /errors :as e) +(import /base :as b) +(import /load-prelude :as pre) +(use judge) + +(defn run [source] + (when (= :error pre/pkg) (error "could not load prelude")) + (def ctx @{:^parent pre/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 "debug add_msg" + (test (run ` + let msgs = [1, :foo, nil] + let msg = do msgs > map (string, _) + msg + `) + @["1" ":foo" ":^nil"]) + # (test (run `print! ("foo", "bar")`) :ok) +) diff --git a/janet/project.janet b/janet/project.janet new file mode 100644 index 0000000..a43aa16 --- /dev/null +++ b/janet/project.janet @@ -0,0 +1,5 @@ +(declare-project + :dependencies [ + {:url "https://github.com/ianthehenry/judge.git" + :tag "v2.8.1"} + ]) diff --git a/janet/test.janet b/janet/test.janet deleted file mode 100644 index 4057c58..0000000 --- a/janet/test.janet +++ /dev/null @@ -1,7 +0,0 @@ -(def myodd? nil) - -(defn myeven? [x] (if (= 0 x) true (myodd? (dec x)))) - -(defn myodd? [x] (if (= 0 x) false (myeven? (dec x)))) - -(myeven? 2) diff --git a/janet/watchy.fish b/janet/watchy.fish new file mode 100755 index 0000000..f9b1e76 --- /dev/null +++ b/janet/watchy.fish @@ -0,0 +1,5 @@ +#!/opt/homebrew/bin/fish + +set FILE $argv[1] + +fd $FILE | entr ./judgy.fish /_