;; Smalltalk parser tests.
;;
;; Reuses helpers (st-test, st-deep=?) from tokenize.sx. Counters reset
;; here so this file's summary covers parse tests only.

(set! st-test-pass 0)
(set! st-test-fail 0)
(set! st-test-fails (list))

;; ── 1. Atoms ──
(st-test "int" (st-parse-expr "42") {:type "lit-int" :value 42})
(st-test "float" (st-parse-expr "3.14") {:type "lit-float" :value 3.14})
(st-test "string" (st-parse-expr "'hi'") {:type "lit-string" :value "hi"})
(st-test "char" (st-parse-expr "$x") {:type "lit-char" :value "x"})
(st-test "symbol" (st-parse-expr "#foo") {:type "lit-symbol" :value "foo"})
(st-test "binary symbol" (st-parse-expr "#+") {:type "lit-symbol" :value "+"})
(st-test "keyword symbol" (st-parse-expr "#at:put:") {:type "lit-symbol" :value "at:put:"})
(st-test "nil" (st-parse-expr "nil") {:type "lit-nil"})
(st-test "true" (st-parse-expr "true") {:type "lit-true"})
(st-test "false" (st-parse-expr "false") {:type "lit-false"})
(st-test "self" (st-parse-expr "self") {:type "self"})
(st-test "super" (st-parse-expr "super") {:type "super"})
(st-test "ident" (st-parse-expr "x") {:type "ident" :name "x"})
(st-test "negative int" (st-parse-expr "-3") {:type "lit-int" :value -3})

;; ── 2. Literal arrays ──
(st-test
  "literal array of ints"
  (st-parse-expr "#(1 2 3)")
  {:type "lit-array"
   :elements (list
               {:type "lit-int" :value 1}
               {:type "lit-int" :value 2}
               {:type "lit-int" :value 3})})

(st-test
  "literal array mixed"
  (st-parse-expr "#(1 #foo 'x' true)")
  {:type "lit-array"
   :elements (list
               {:type "lit-int" :value 1}
               {:type "lit-symbol" :value "foo"}
               {:type "lit-string" :value "x"}
               {:type "lit-true"})})

(st-test
  "literal array bare ident is symbol"
  (st-parse-expr "#(foo bar)")
  {:type "lit-array"
   :elements (list
               {:type "lit-symbol" :value "foo"}
               {:type "lit-symbol" :value "bar"})})

(st-test
  "nested literal array"
  (st-parse-expr "#(1 (2 3) 4)")
  {:type "lit-array"
   :elements (list
               {:type "lit-int" :value 1}
               {:type "lit-array"
                :elements (list
                            {:type "lit-int" :value 2}
                            {:type "lit-int" :value 3})}
               {:type "lit-int" :value 4})})

(st-test
  "byte array"
  (st-parse-expr "#[1 2 3]")
  {:type "lit-byte-array" :elements (list 1 2 3)})

;; ── 3. Unary messages ──
(st-test
  "unary single"
  (st-parse-expr "x foo")
  {:type "send"
   :receiver {:type "ident" :name "x"}
   :selector "foo"
   :args (list)})

(st-test
  "unary chain"
  (st-parse-expr "x foo bar baz")
  {:type "send"
   :receiver {:type "send"
              :receiver {:type "send"
                         :receiver {:type "ident" :name "x"}
                         :selector "foo"
                         :args (list)}
              :selector "bar"
              :args (list)}
   :selector "baz"
   :args (list)})

(st-test
  "unary on literal"
  (st-parse-expr "42 printNl")
  {:type "send"
   :receiver {:type "lit-int" :value 42}
   :selector "printNl"
   :args (list)})

;; ── 4. Binary messages ──
(st-test
  "binary single"
  (st-parse-expr "1 + 2")
  {:type "send"
   :receiver {:type "lit-int" :value 1}
   :selector "+"
   :args (list {:type "lit-int" :value 2})})

(st-test
  "binary left-assoc"
  (st-parse-expr "1 + 2 + 3")
  {:type "send"
   :receiver {:type "send"
              :receiver {:type "lit-int" :value 1}
              :selector "+"
              :args (list {:type "lit-int" :value 2})}
   :selector "+"
   :args (list {:type "lit-int" :value 3})})

(st-test
  "binary same precedence l-to-r"
  (st-parse-expr "1 + 2 * 3")
  {:type "send"
   :receiver {:type "send"
              :receiver {:type "lit-int" :value 1}
              :selector "+"
              :args (list {:type "lit-int" :value 2})}
   :selector "*"
   :args (list {:type "lit-int" :value 3})})

;; ── 5. Precedence: unary binds tighter than binary ──
(st-test
  "unary tighter than binary"
  (st-parse-expr "3 + 4 factorial")
  {:type "send"
   :receiver {:type "lit-int" :value 3}
   :selector "+"
   :args (list
           {:type "send"
            :receiver {:type "lit-int" :value 4}
            :selector "factorial"
            :args (list)})})

;; ── 6. Keyword messages ──
(st-test
  "keyword single"
  (st-parse-expr "x at: 1")
  {:type "send"
   :receiver {:type "ident" :name "x"}
   :selector "at:"
   :args (list {:type "lit-int" :value 1})})

(st-test
  "keyword chain"
  (st-parse-expr "x at: 1 put: 'a'")
  {:type "send"
   :receiver {:type "ident" :name "x"}
   :selector "at:put:"
   :args (list {:type "lit-int" :value 1} {:type "lit-string" :value "a"})})

;; ── 7. Precedence: binary tighter than keyword ──
(st-test
  "binary tighter than keyword"
  (st-parse-expr "x at: 1 + 2")
  {:type "send"
   :receiver {:type "ident" :name "x"}
   :selector "at:"
   :args (list
           {:type "send"
            :receiver {:type "lit-int" :value 1}
            :selector "+"
            :args (list {:type "lit-int" :value 2})})})

(st-test
  "keyword absorbs trailing unary"
  (st-parse-expr "a foo: b bar")
  {:type "send"
   :receiver {:type "ident" :name "a"}
   :selector "foo:"
   :args (list
           {:type "send"
            :receiver {:type "ident" :name "b"}
            :selector "bar"
            :args (list)})})

;; ── 8. Parens override precedence ──
(st-test
  "paren forces grouping"
  (st-parse-expr "(1 + 2) * 3")
  {:type "send"
   :receiver {:type "send"
              :receiver {:type "lit-int" :value 1}
              :selector "+"
              :args (list {:type "lit-int" :value 2})}
   :selector "*"
   :args (list {:type "lit-int" :value 3})})

;; ── 9. Cascade ──
(st-test
  "simple cascade"
  (st-parse-expr "x m1; m2")
  {:type "cascade"
   :receiver {:type "ident" :name "x"}
   :messages (list
               {:selector "m1" :args (list)}
               {:selector "m2" :args (list)})})

(st-test
  "cascade with binary and keyword"
  (st-parse-expr "Stream new nl; tab; print: 1")
  {:type "cascade"
   :receiver {:type "send"
              :receiver {:type "ident" :name "Stream"}
              :selector "new"
              :args (list)}
   :messages (list
               {:selector "nl" :args (list)}
               {:selector "tab" :args (list)}
               {:selector "print:" :args (list {:type "lit-int" :value 1})})})

;; ── 10. Blocks ──
(st-test
  "empty block"
  (st-parse-expr "[]")
  {:type "block" :params (list) :temps (list) :body (list)})

(st-test
  "block one expr"
  (st-parse-expr "[1 + 2]")
  {:type "block"
   :params (list)
   :temps (list)
   :body (list
           {:type "send"
            :receiver {:type "lit-int" :value 1}
            :selector "+"
            :args (list {:type "lit-int" :value 2})})})

(st-test
  "block with params"
  (st-parse-expr "[:a :b | a + b]")
  {:type "block"
   :params (list "a" "b")
   :temps (list)
   :body (list
           {:type "send"
            :receiver {:type "ident" :name "a"}
            :selector "+"
            :args (list {:type "ident" :name "b"})})})

(st-test
  "block with temps"
  (st-parse-expr "[| t | t := 1. t]")
  {:type "block"
   :params (list)
   :temps (list "t")
   :body (list
           {:type "assign" :name "t" :expr {:type "lit-int" :value 1}}
           {:type "ident" :name "t"})})

(st-test
  "block with params and temps"
  (st-parse-expr "[:x | | t | t := x + 1. t]")
  {:type "block"
   :params (list "x")
   :temps (list "t")
   :body (list
           {:type "assign"
            :name "t"
            :expr {:type "send"
                   :receiver {:type "ident" :name "x"}
                   :selector "+"
                   :args (list {:type "lit-int" :value 1})}}
           {:type "ident" :name "t"})})

;; ── 11. Assignment / return / statements ──
(st-test
  "assignment"
  (st-parse-expr "x := 1")
  {:type "assign" :name "x" :expr {:type "lit-int" :value 1}})

(st-test
  "return"
  (st-parse-expr "1")
  {:type "lit-int" :value 1})

(st-test
  "return statement at top level"
  (st-parse "^ 1")
  {:type "seq" :temps (list)
   :exprs (list {:type "return" :expr {:type "lit-int" :value 1}})})

(st-test
  "two statements"
  (st-parse "x := 1. y := 2")
  {:type "seq" :temps (list)
   :exprs (list
            {:type "assign" :name "x" :expr {:type "lit-int" :value 1}}
            {:type "assign" :name "y" :expr {:type "lit-int" :value 2}})})

(st-test
  "trailing dot allowed"
  (st-parse "1. 2.")
  {:type "seq" :temps (list)
   :exprs (list {:type "lit-int" :value 1} {:type "lit-int" :value 2})})

;; ── 12. Method headers ──
(st-test
  "unary method"
  (st-parse-method "factorial ^ self * (self - 1) factorial")
  {:type "method"
   :selector "factorial"
   :params (list)
   :temps (list)
   :pragmas (list)
   :body (list
           {:type "return"
            :expr {:type "send"
                   :receiver {:type "self"}
                   :selector "*"
                   :args (list
                           {:type "send"
                            :receiver {:type "send"
                                       :receiver {:type "self"}
                                       :selector "-"
                                       :args (list {:type "lit-int" :value 1})}
                            :selector "factorial"
                            :args (list)})}})})

(st-test
  "binary method"
  (st-parse-method "+ other ^ 'plus'")
  {:type "method"
   :selector "+"
   :params (list "other")
   :temps (list)
   :pragmas (list)
   :body (list {:type "return" :expr {:type "lit-string" :value "plus"}})})

(st-test
  "keyword method"
  (st-parse-method "at: i put: v ^ v")
  {:type "method"
   :selector "at:put:"
   :params (list "i" "v")
   :temps (list)
   :pragmas (list)
   :body (list {:type "return" :expr {:type "ident" :name "v"}})})

(st-test
  "method with temps"
  (st-parse-method "twice: x | t | t := x + x. ^ t")
  {:type "method"
   :selector "twice:"
   :params (list "x")
   :temps (list "t")
   :pragmas (list)
   :body (list
           {:type "assign"
            :name "t"
            :expr {:type "send"
                   :receiver {:type "ident" :name "x"}
                   :selector "+"
                   :args (list {:type "ident" :name "x"})}}
           {:type "return" :expr {:type "ident" :name "t"}})})

(list st-test-pass st-test-fail)
