# 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]})
)
