372 lines
12 KiB
Plaintext
372 lines
12 KiB
Plaintext
|
# 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]})
|
||
|
)
|