Files
rose-ash/spec/tests/test-source-locations.sx
giles 99c5c44cc1 Step 14: source locations — pos-to-loc, error-loc, sx-parse-loc — 15 tests
Pure SX layer: pos-to-loc (offset→line/col), error-loc (parse result→loc),
format-parse-error (human-readable error with source context line).
OCaml platform: cst_to_ast_loc (CST spans→loc dicts), sx-parse-loc
primitive (parse with locations), source-loc accessor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 08:03:45 +00:00

139 lines
4.1 KiB
Plaintext

;; Step 14: Source maps / error reporting
;; Source locations tracked through parse → compile → eval.
;; pos-to-loc converts character offset to {:line :col}.
;; Errors include source context.
;; ── pos-to-loc: offset → line/col ────────────────────────────────
(defsuite
"pos-to-loc"
(deftest
"first char is line 1 col 0"
(let
((loc (pos-to-loc "hello" 0)))
(assert= (get loc :line) 1)
(assert= (get loc :col) 0)))
(deftest
"mid-line position"
(let
((loc (pos-to-loc "hello" 3)))
(assert= (get loc :line) 1)
(assert= (get loc :col) 3)))
(deftest
"second line"
(let
((loc (pos-to-loc "abc\ndef" 4)))
(assert= (get loc :line) 2)
(assert= (get loc :col) 0)))
(deftest
"second line mid"
(let
((loc (pos-to-loc "abc\ndef" 6)))
(assert= (get loc :line) 2)
(assert= (get loc :col) 2)))
(deftest
"third line"
(let
((loc (pos-to-loc "a\nb\nc" 4)))
(assert= (get loc :line) 3)
(assert= (get loc :col) 0)))
(deftest
"end of input"
(let
((loc (pos-to-loc "abc" 3)))
(assert= (get loc :line) 1)
(assert= (get loc :col) 3))))
;; ── Parser combinator error locations ─────────────────────────────
(defsuite
"parse-error-loc"
(deftest
"failed parse includes line and col"
(let
((result (run-parser digit "abc")))
(assert (not (ok? result)))
(let
((loc (error-loc result "abc")))
(assert= (get loc :line) 1)
(assert= (get loc :col) 0))))
(deftest
"failed parse after consuming"
(let
((result (run-parser (seq2 letter digit) "ab")))
(assert (not (ok? result)))
(let
((loc (error-loc result "ab")))
(assert= (get loc :line) 1)
(assert= (get loc :col) 1))))
(deftest
"failed parse on second line"
(let
((result (run-parser (seq2 (parse-char "\n") digit) "\na")))
(assert (not (ok? result)))
(let
((loc (error-loc result "\na")))
(assert= (get loc :line) 2)
(assert= (get loc :col) 0)))))
;; ── format-parse-error: human-readable error ──────────────────────
(defsuite
"format-parse-error"
(deftest
"single line error"
(let
((result (run-parser digit "hello"))
(msg (format-parse-error result "hello")))
(assert (string-contains? msg "line 1"))
(assert (string-contains? msg "digit"))))
(deftest
"multi-line error shows line number"
(let
((input "abc\ndef\n!!!")
(result
(run-parser (seq (list (parse-string "abc\ndef\n") letter)) input))
(msg (format-parse-error result input)))
(assert (string-contains? msg "line 3"))))
(deftest
"error includes source context"
(let
((result (run-parser digit "let x = foo"))
(msg (format-parse-error result "let x = foo")))
(assert (string-contains? msg "let x = foo")))))
;; ── source-loc on parsed AST ──────────────────────────────────────
(defsuite
"ast-source-loc"
(deftest
"sx-parse-loc returns forms with locations"
(let
((forms (sx-parse-loc "(+ 1 2)")))
(assert (not (empty? forms)))
(let
((loc (source-loc (first forms))))
(assert (not (nil? loc)))
(assert= (get loc :line) 1)
(assert= (get loc :col) 0))))
(deftest
"multi-line sx-parse-loc"
(let
((forms (sx-parse-loc "(define x 1)\n(+ x 2)")))
(assert= (len forms) 2)
(let
((loc1 (source-loc (first forms)))
(loc2 (source-loc (nth forms 1))))
(assert= (get loc1 :line) 1)
(assert= (get loc1 :col) 0)
(assert= (get loc2 :line) 2)
(assert= (get loc2 :col) 0))))
(deftest
"form value is accessible"
(let
((forms (sx-parse-loc "(+ 1 2)")))
(let
((form (get (first forms) :form)))
(assert (list? form))
(assert= (len form) 3)))))