All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m13s
Reader macros in parser.sx spec, Python parser.py, and hand-written sx.js: - #; datum comment: read and discard next expression - #|...| raw string: no escape processing - #' quote shorthand: (quote expr) - #name extensible dispatch: registered handler transforms next expression #z3 reader macro demo (reader_z3.py): translates define-primitive declarations from primitives.sx into SMT-LIB verification conditions. Same source, two interpretations — bootstrappers compile to executable code, #z3 extracts proof obligations. 48 parser tests (SX spec + Python), all passing. Rebootstrapped JS+Python. Demo page at /plans/reader-macro-demo with side-by-side examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
260 lines
8.4 KiB
Plaintext
260 lines
8.4 KiB
Plaintext
;; ==========================================================================
|
|
;; test-parser.sx — Tests for the SX parser and serializer
|
|
;;
|
|
;; Requires: test-framework.sx loaded first.
|
|
;; Modules tested: parser.sx
|
|
;;
|
|
;; Platform functions required (beyond test framework):
|
|
;; sx-parse (source) -> list of AST expressions
|
|
;; sx-serialize (expr) -> SX source string
|
|
;; make-symbol (name) -> Symbol value
|
|
;; make-keyword (name) -> Keyword value
|
|
;; symbol-name (sym) -> string
|
|
;; keyword-name (kw) -> string
|
|
;; ==========================================================================
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Literal parsing
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "parser-literals"
|
|
(deftest "parse integers"
|
|
(assert-equal (list 42) (sx-parse "42"))
|
|
(assert-equal (list 0) (sx-parse "0"))
|
|
(assert-equal (list -7) (sx-parse "-7")))
|
|
|
|
(deftest "parse floats"
|
|
(assert-equal (list 3.14) (sx-parse "3.14"))
|
|
(assert-equal (list -0.5) (sx-parse "-0.5")))
|
|
|
|
(deftest "parse strings"
|
|
(assert-equal (list "hello") (sx-parse "\"hello\""))
|
|
(assert-equal (list "") (sx-parse "\"\"")))
|
|
|
|
(deftest "parse escape: newline"
|
|
(assert-equal (list "a\nb") (sx-parse "\"a\\nb\"")))
|
|
|
|
(deftest "parse escape: tab"
|
|
(assert-equal (list "a\tb") (sx-parse "\"a\\tb\"")))
|
|
|
|
(deftest "parse escape: quote"
|
|
(assert-equal (list "a\"b") (sx-parse "\"a\\\"b\"")))
|
|
|
|
(deftest "parse booleans"
|
|
(assert-equal (list true) (sx-parse "true"))
|
|
(assert-equal (list false) (sx-parse "false")))
|
|
|
|
(deftest "parse nil"
|
|
(assert-equal (list nil) (sx-parse "nil")))
|
|
|
|
(deftest "parse keywords"
|
|
(let ((result (sx-parse ":hello")))
|
|
(assert-length 1 result)
|
|
(assert-equal "hello" (keyword-name (first result)))))
|
|
|
|
(deftest "parse symbols"
|
|
(let ((result (sx-parse "foo")))
|
|
(assert-length 1 result)
|
|
(assert-equal "foo" (symbol-name (first result))))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Composite parsing
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "parser-lists"
|
|
(deftest "parse empty list"
|
|
(let ((result (sx-parse "()")))
|
|
(assert-length 1 result)
|
|
(assert-equal (list) (first result))))
|
|
|
|
(deftest "parse list of numbers"
|
|
(let ((result (sx-parse "(1 2 3)")))
|
|
(assert-length 1 result)
|
|
(assert-equal (list 1 2 3) (first result))))
|
|
|
|
(deftest "parse nested lists"
|
|
(let ((result (sx-parse "(1 (2 3) 4)")))
|
|
(assert-length 1 result)
|
|
(assert-equal (list 1 (list 2 3) 4) (first result))))
|
|
|
|
(deftest "parse square brackets as list"
|
|
(let ((result (sx-parse "[1 2 3]")))
|
|
(assert-length 1 result)
|
|
(assert-equal (list 1 2 3) (first result))))
|
|
|
|
(deftest "parse mixed types"
|
|
(let ((result (sx-parse "(42 \"hello\" true nil)")))
|
|
(assert-length 1 result)
|
|
(let ((lst (first result)))
|
|
(assert-equal 42 (nth lst 0))
|
|
(assert-equal "hello" (nth lst 1))
|
|
(assert-equal true (nth lst 2))
|
|
(assert-nil (nth lst 3))))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Dict parsing
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "parser-dicts"
|
|
(deftest "parse empty dict"
|
|
(let ((result (sx-parse "{}")))
|
|
(assert-length 1 result)
|
|
(assert-type "dict" (first result))))
|
|
|
|
(deftest "parse dict with keyword keys"
|
|
(let ((result (sx-parse "{:a 1 :b 2}")))
|
|
(assert-length 1 result)
|
|
(let ((d (first result)))
|
|
(assert-type "dict" d)
|
|
(assert-equal 1 (get d "a"))
|
|
(assert-equal 2 (get d "b")))))
|
|
|
|
(deftest "parse dict with string values"
|
|
(let ((result (sx-parse "{:name \"alice\"}")))
|
|
(assert-length 1 result)
|
|
(assert-equal "alice" (get (first result) "name")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Comments and whitespace
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "parser-whitespace"
|
|
(deftest "skip line comments"
|
|
(assert-equal (list 42) (sx-parse ";; comment\n42"))
|
|
(assert-equal (list 1 2) (sx-parse "1 ;; middle\n2")))
|
|
|
|
(deftest "skip whitespace"
|
|
(assert-equal (list 42) (sx-parse " 42 "))
|
|
(assert-equal (list 1 2) (sx-parse " 1 \n\t 2 ")))
|
|
|
|
(deftest "parse multiple top-level expressions"
|
|
(assert-length 3 (sx-parse "1 2 3"))
|
|
(assert-equal (list 1 2 3) (sx-parse "1 2 3")))
|
|
|
|
(deftest "empty input"
|
|
(assert-equal (list) (sx-parse "")))
|
|
|
|
(deftest "only comments"
|
|
(assert-equal (list) (sx-parse ";; just a comment\n;; another"))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Quote sugar
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "parser-quote-sugar"
|
|
(deftest "quasiquote"
|
|
(let ((result (sx-parse "`foo")))
|
|
(assert-length 1 result)
|
|
(let ((expr (first result)))
|
|
(assert-type "list" expr)
|
|
(assert-equal "quasiquote" (symbol-name (first expr))))))
|
|
|
|
(deftest "unquote"
|
|
(let ((result (sx-parse ",foo")))
|
|
(assert-length 1 result)
|
|
(let ((expr (first result)))
|
|
(assert-type "list" expr)
|
|
(assert-equal "unquote" (symbol-name (first expr))))))
|
|
|
|
(deftest "splice-unquote"
|
|
(let ((result (sx-parse ",@foo")))
|
|
(assert-length 1 result)
|
|
(let ((expr (first result)))
|
|
(assert-type "list" expr)
|
|
(assert-equal "splice-unquote" (symbol-name (first expr)))))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Serializer
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "serializer"
|
|
(deftest "serialize number"
|
|
(assert-equal "42" (sx-serialize 42)))
|
|
|
|
(deftest "serialize string"
|
|
(assert-equal "\"hello\"" (sx-serialize "hello")))
|
|
|
|
(deftest "serialize boolean"
|
|
(assert-equal "true" (sx-serialize true))
|
|
(assert-equal "false" (sx-serialize false)))
|
|
|
|
(deftest "serialize nil"
|
|
(assert-equal "nil" (sx-serialize nil)))
|
|
|
|
(deftest "serialize keyword"
|
|
(assert-equal ":foo" (sx-serialize (make-keyword "foo"))))
|
|
|
|
(deftest "serialize symbol"
|
|
(assert-equal "bar" (sx-serialize (make-symbol "bar"))))
|
|
|
|
(deftest "serialize list"
|
|
(assert-equal "(1 2 3)" (sx-serialize (list 1 2 3))))
|
|
|
|
(deftest "serialize empty list"
|
|
(assert-equal "()" (sx-serialize (list))))
|
|
|
|
(deftest "serialize nested"
|
|
(assert-equal "(1 (2 3) 4)" (sx-serialize (list 1 (list 2 3) 4)))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Round-trip: parse then serialize
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "parser-roundtrip"
|
|
(deftest "roundtrip number"
|
|
(assert-equal "42" (sx-serialize (first (sx-parse "42")))))
|
|
|
|
(deftest "roundtrip string"
|
|
(assert-equal "\"hello\"" (sx-serialize (first (sx-parse "\"hello\"")))))
|
|
|
|
(deftest "roundtrip list"
|
|
(assert-equal "(1 2 3)" (sx-serialize (first (sx-parse "(1 2 3)")))))
|
|
|
|
(deftest "roundtrip nested"
|
|
(assert-equal "(a (b c))"
|
|
(sx-serialize (first (sx-parse "(a (b c))"))))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Reader macros
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "reader-macros"
|
|
(deftest "datum comment discards expr"
|
|
(assert-equal (list 42) (sx-parse "#;(ignored) 42")))
|
|
|
|
(deftest "datum comment in list"
|
|
(assert-equal (list (list 1 3)) (sx-parse "(1 #;2 3)")))
|
|
|
|
(deftest "datum comment discards nested"
|
|
(assert-equal (list 99) (sx-parse "#;(a (b c) d) 99")))
|
|
|
|
(deftest "raw string basic"
|
|
(assert-equal (list "hello") (sx-parse "#|hello|")))
|
|
|
|
(deftest "raw string with quotes"
|
|
(assert-equal (list "say \"hi\"") (sx-parse "#|say \"hi\"|")))
|
|
|
|
(deftest "raw string with backslashes"
|
|
(assert-equal (list "a\\nb") (sx-parse "#|a\\nb|")))
|
|
|
|
(deftest "raw string empty"
|
|
(assert-equal (list "") (sx-parse "#||")))
|
|
|
|
(deftest "quote shorthand symbol"
|
|
(let ((result (first (sx-parse "#'foo"))))
|
|
(assert-equal "quote" (symbol-name (first result)))
|
|
(assert-equal "foo" (symbol-name (nth result 1)))))
|
|
|
|
(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)))))
|