Add comprehensive spec tests: closures, macros, TCO, defcomp, parser
New test files expose fundamental evaluator issues: - define doesn't create self-referencing closures (13 failures) - let doesn't isolate scope from parent env (2 failures) - set! doesn't walk scope chain for closed-over vars (3 failures) - Component calls return kwargs object instead of evaluating body (10 failures) 485/516 passing (94%). Parser tests: 100% pass. Macro tests: 96% pass. These failures map the exact work needed for tree-walk removal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -256,4 +256,273 @@
|
||||
(deftest "quote shorthand list"
|
||||
(let ((result (first (sx-parse "#'(1 2 3)"))))
|
||||
(assert-equal "quote" (symbol-name (first result)))
|
||||
(assert-equal (list 1 2 3) (nth result 1)))))
|
||||
(assert-equal (list 1 2 3) (nth result 1))))
|
||||
|
||||
(deftest "apostrophe quote expands to (quote ...)"
|
||||
(let ((result (sx-parse "'x")))
|
||||
(assert-length 1 result)
|
||||
(let ((expr (first result)))
|
||||
(assert-type "list" expr)
|
||||
(assert-equal "quote" (symbol-name (first expr)))
|
||||
(assert-equal "x" (symbol-name (nth expr 1))))))
|
||||
|
||||
(deftest "apostrophe quote on list"
|
||||
(let ((result (sx-parse "'(1 2 3)")))
|
||||
(assert-length 1 result)
|
||||
(let ((expr (first result)))
|
||||
(assert-type "list" expr)
|
||||
(assert-equal "quote" (symbol-name (first expr)))
|
||||
(assert-equal (list 1 2 3) (nth expr 1)))))
|
||||
|
||||
(deftest "quasiquote with unquote inside"
|
||||
(let ((result (sx-parse "`(a ,b)")))
|
||||
(assert-length 1 result)
|
||||
(let ((expr (first result)))
|
||||
(assert-type "list" expr)
|
||||
(assert-equal "quasiquote" (symbol-name (first expr)))
|
||||
(let ((inner (nth expr 1)))
|
||||
(assert-type "list" inner)
|
||||
(assert-equal "a" (symbol-name (first inner)))
|
||||
(let ((unquoted (nth inner 1)))
|
||||
(assert-type "list" unquoted)
|
||||
(assert-equal "unquote" (symbol-name (first unquoted)))))))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Number formats
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "parser-numbers"
|
||||
(deftest "integer zero"
|
||||
(assert-equal (list 0) (sx-parse "0")))
|
||||
|
||||
(deftest "large integer"
|
||||
(assert-equal (list 1000000) (sx-parse "1000000")))
|
||||
|
||||
(deftest "negative float"
|
||||
(assert-equal (list -2.718) (sx-parse "-2.718")))
|
||||
|
||||
(deftest "exponent notation"
|
||||
(let ((result (sx-parse "1e10")))
|
||||
(assert-length 1 result)
|
||||
(assert-type "number" (first result))
|
||||
(assert-equal 10000000000 (first result))))
|
||||
|
||||
(deftest "negative exponent"
|
||||
(let ((result (sx-parse "2.5e-1")))
|
||||
(assert-length 1 result)
|
||||
(assert-type "number" (first result))
|
||||
(assert-equal 0.25 (first result))))
|
||||
|
||||
(deftest "uppercase exponent E"
|
||||
(let ((result (sx-parse "1E3")))
|
||||
(assert-length 1 result)
|
||||
(assert-type "number" (first result))
|
||||
(assert-equal 1000 (first result)))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Symbol naming conventions
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "parser-symbols"
|
||||
(deftest "symbol with hyphens"
|
||||
(let ((result (sx-parse "my-var")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "my-var" (symbol-name (first result)))))
|
||||
|
||||
(deftest "symbol with question mark"
|
||||
(let ((result (sx-parse "nil?")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "nil?" (symbol-name (first result)))))
|
||||
|
||||
(deftest "symbol with exclamation"
|
||||
(let ((result (sx-parse "set!")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "set!" (symbol-name (first result)))))
|
||||
|
||||
(deftest "symbol with tilde (component)"
|
||||
(let ((result (sx-parse "~my-comp")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "~my-comp" (symbol-name (first result)))))
|
||||
|
||||
(deftest "symbol with arrow"
|
||||
(let ((result (sx-parse "->")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "->" (symbol-name (first result)))))
|
||||
|
||||
(deftest "symbol with &"
|
||||
(let ((result (sx-parse "&key")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "&key" (symbol-name (first result)))))
|
||||
|
||||
(deftest "symbol with every? style"
|
||||
(let ((result (sx-parse "every?")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "every?" (symbol-name (first result)))))
|
||||
|
||||
(deftest "ellipsis is a symbol"
|
||||
(let ((result (sx-parse "...")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "..." (symbol-name (first result))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Serializer — extended
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "serializer-extended"
|
||||
(deftest "serialize negative number"
|
||||
(assert-equal "-5" (sx-serialize -5)))
|
||||
|
||||
(deftest "serialize float"
|
||||
(assert-equal "3.14" (sx-serialize 3.14)))
|
||||
|
||||
(deftest "serialize string with escaped quote"
|
||||
(let ((s (sx-serialize "say \"hi\"")))
|
||||
(assert-true (string-contains? s "\\\""))))
|
||||
|
||||
(deftest "serialize dict round-trips"
|
||||
;; Parse a dict literal, serialize it, parse again — values survive.
|
||||
(let ((d (first (sx-parse "{:x 1 :y 2}"))))
|
||||
(let ((s (sx-serialize d)))
|
||||
(assert-true (string-contains? s ":x"))
|
||||
(assert-true (string-contains? s ":y"))
|
||||
(let ((d2 (first (sx-parse s))))
|
||||
(assert-equal 1 (get d2 "x"))
|
||||
(assert-equal 2 (get d2 "y"))))))
|
||||
|
||||
(deftest "serialize symbol with hyphens"
|
||||
(assert-equal "my-fn" (sx-serialize (make-symbol "my-fn"))))
|
||||
|
||||
(deftest "serialize keyword with hyphens"
|
||||
(assert-equal ":my-key" (sx-serialize (make-keyword "my-key"))))
|
||||
|
||||
(deftest "serialize deeply nested list"
|
||||
(assert-equal "(1 (2 (3)))"
|
||||
(sx-serialize (list 1 (list 2 (list 3)))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Round-trip — extended
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "parser-roundtrip-extended"
|
||||
(deftest "roundtrip keyword"
|
||||
(let ((parsed (first (sx-parse ":hello"))))
|
||||
(assert-equal ":hello" (sx-serialize parsed))))
|
||||
|
||||
(deftest "roundtrip negative number"
|
||||
(assert-equal "-7" (sx-serialize (first (sx-parse "-7")))))
|
||||
|
||||
(deftest "roundtrip float"
|
||||
(assert-equal "3.14" (sx-serialize (first (sx-parse "3.14")))))
|
||||
|
||||
(deftest "roundtrip string with newline escape"
|
||||
(let ((parsed (first (sx-parse "\"a\\nb\""))))
|
||||
;; Parsed value contains a real newline character.
|
||||
(assert-equal "a\nb" parsed)
|
||||
;; Serialized form must escape it back.
|
||||
(let ((serialized (sx-serialize parsed)))
|
||||
(assert-true (string-contains? serialized "\\n")))))
|
||||
|
||||
(deftest "roundtrip symbol with question mark"
|
||||
(let ((parsed (first (sx-parse "empty?"))))
|
||||
(assert-equal "empty?" (sx-serialize parsed))))
|
||||
|
||||
(deftest "roundtrip component symbol"
|
||||
(let ((parsed (first (sx-parse "~card"))))
|
||||
(assert-equal "~card" (sx-serialize parsed))))
|
||||
|
||||
(deftest "roundtrip keyword arguments in list"
|
||||
(let ((src "(~comp :title \"Hi\" :count 3)"))
|
||||
(assert-equal src
|
||||
(sx-serialize (first (sx-parse src))))))
|
||||
|
||||
(deftest "roundtrip empty list"
|
||||
(assert-equal "()" (sx-serialize (first (sx-parse "()")))))
|
||||
|
||||
(deftest "roundtrip mixed-type list"
|
||||
(let ((src "(1 \"hello\" true nil)"))
|
||||
(assert-equal src
|
||||
(sx-serialize (first (sx-parse src)))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Edge cases
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "parser-edge-cases"
|
||||
(deftest "empty string parses to empty list"
|
||||
(assert-equal (list) (sx-parse "")))
|
||||
|
||||
(deftest "whitespace-only parses to empty list"
|
||||
(assert-equal (list) (sx-parse " \n\t ")))
|
||||
|
||||
(deftest "multiple top-level expressions"
|
||||
(let ((result (sx-parse "1 2 3")))
|
||||
(assert-length 3 result)
|
||||
(assert-equal 1 (nth result 0))
|
||||
(assert-equal 2 (nth result 1))
|
||||
(assert-equal 3 (nth result 2))))
|
||||
|
||||
(deftest "multiple top-level mixed types"
|
||||
(let ((result (sx-parse "42 \"hello\" true nil")))
|
||||
(assert-length 4 result)
|
||||
(assert-equal 42 (nth result 0))
|
||||
(assert-equal "hello" (nth result 1))
|
||||
(assert-equal true (nth result 2))
|
||||
(assert-nil (nth result 3))))
|
||||
|
||||
(deftest "deeply nested list"
|
||||
(let ((result (sx-parse "(((((1)))))")))
|
||||
(assert-length 1 result)
|
||||
(let ((l1 (first result)))
|
||||
(let ((l2 (first l1)))
|
||||
(let ((l3 (first l2)))
|
||||
(let ((l4 (first l3)))
|
||||
(assert-equal (list 1) l4)))))))
|
||||
|
||||
(deftest "long string value"
|
||||
(let ((long-str (join "" (map (fn (x) "abcdefghij") (range 0 10)))))
|
||||
(let ((src (str "\"" long-str "\"")))
|
||||
(assert-equal (list long-str) (sx-parse src)))))
|
||||
|
||||
(deftest "inline comment inside list"
|
||||
(let ((result (sx-parse "(+ 1 ;; comment\n 2)")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal (list (make-symbol "+") 1 2) (first result))))
|
||||
|
||||
(deftest "comment at end of file with no trailing newline"
|
||||
(assert-equal (list 1) (sx-parse "1 ;; trailing comment")))
|
||||
|
||||
(deftest "keyword with numeric suffix"
|
||||
(let ((result (sx-parse ":item-1")))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "item-1" (keyword-name (first result)))))
|
||||
|
||||
(deftest "consecutive keywords parsed as separate atoms"
|
||||
(let ((result (sx-parse ":a :b :c")))
|
||||
(assert-length 3 result)
|
||||
(assert-equal "a" (keyword-name (nth result 0)))
|
||||
(assert-equal "b" (keyword-name (nth result 1)))
|
||||
(assert-equal "c" (keyword-name (nth result 2)))))
|
||||
|
||||
(deftest "symbol immediately after opening paren"
|
||||
(let ((result (first (sx-parse "(foo)"))))
|
||||
(assert-length 1 result)
|
||||
(assert-equal "foo" (symbol-name (first result)))))
|
||||
|
||||
(deftest "parse boolean true is not a symbol"
|
||||
(let ((result (first (sx-parse "true"))))
|
||||
(assert-type "boolean" result)
|
||||
(assert-equal true result)))
|
||||
|
||||
(deftest "parse boolean false is not a symbol"
|
||||
(let ((result (first (sx-parse "false"))))
|
||||
(assert-type "boolean" result)
|
||||
(assert-equal false result)))
|
||||
|
||||
(deftest "parse nil is not a symbol"
|
||||
(let ((result (first (sx-parse "nil"))))
|
||||
(assert-nil result))))
|
||||
|
||||
Reference in New Issue
Block a user