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>
139 lines
4.1 KiB
Plaintext
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)))))
|