Merge pull request #90 from jolt-lang/error-round-5
errors: reader errors carry file:line:col (round 5)
This commit is contained in:
commit
ca83b4a1de
5 changed files with 88 additions and 24 deletions
|
|
@ -326,5 +326,5 @@
|
||||||
Returns the result of the last form evaluated."
|
Returns the result of the last form evaluated."
|
||||||
[ctx s &opt file]
|
[ctx s &opt file]
|
||||||
(default file "<eval>")
|
(default file "<eval>")
|
||||||
(eval-forms-positioned ctx (parse-all-positioned s) file))
|
(eval-forms-positioned ctx (parse-all-positioned s file) file))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -361,7 +361,7 @@
|
||||||
[ctx src &opt file]
|
[ctx src &opt file]
|
||||||
(default file "<source>")
|
(default file "<source>")
|
||||||
(def toplevel (get (ctx :env) :toplevel-eval))
|
(def toplevel (get (ctx :env) :toplevel-eval))
|
||||||
(each [f line] (parse-all-positioned src)
|
(each [f line] (parse-all-positioned src file)
|
||||||
(try
|
(try
|
||||||
(if toplevel (toplevel ctx f) (eval-form ctx @{} f))
|
(if toplevel (toplevel ctx f) (eval-form ctx @{} f))
|
||||||
([err fib]
|
([err fib]
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@
|
||||||
(load-ns ctx filepath) → namespace symbol string"
|
(load-ns ctx filepath) → namespace symbol string"
|
||||||
[ctx filepath]
|
[ctx filepath]
|
||||||
(def source (slurp filepath))
|
(def source (slurp filepath))
|
||||||
(def pairs (parse-all-positioned source))
|
(def pairs (parse-all-positioned source filepath))
|
||||||
(var ns-name nil)
|
(var ns-name nil)
|
||||||
(each [form _] pairs
|
(each [form _] pairs
|
||||||
# Extract ns name from the first ns form
|
# Extract ns name from the first ns form
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,36 @@
|
||||||
(= c 13) # \r
|
(= c 13) # \r
|
||||||
(= c 44))) # comma
|
(= c 44))) # comma
|
||||||
|
|
||||||
|
# Reader errors carry the raw byte OFFSET; the parse entry points (parse-string,
|
||||||
|
# parse-all-positioned) convert offset -> line:col against the full source and
|
||||||
|
# re-raise Clojure's 'Syntax error reading source at (file:line:col): msg'
|
||||||
|
# shape. Raising structured here keeps the 15 error sites one-liners.
|
||||||
|
# (jolt-2o7.5)
|
||||||
|
(defn- reader-error [msg pos]
|
||||||
|
(error {:jolt/type :jolt/reader-error :msg msg :pos pos}))
|
||||||
|
|
||||||
|
(defn line-col
|
||||||
|
"1-based [line col] of byte offset in source."
|
||||||
|
[source offset]
|
||||||
|
(var line 1)
|
||||||
|
(var bol 0)
|
||||||
|
(def stop (min offset (length source)))
|
||||||
|
(loop [i :range [0 stop]]
|
||||||
|
(when (= (in source i) (chr "\n")) (++ line) (set bol (+ i 1))))
|
||||||
|
[line (+ 1 (- stop bol))])
|
||||||
|
|
||||||
|
(defn format-reader-error
|
||||||
|
"Clojure-shaped syntax error message for a reader-error struct raised
|
||||||
|
against source; file may be nil (omitted)."
|
||||||
|
[e source file]
|
||||||
|
(def [l c] (line-col source (e :pos)))
|
||||||
|
(if file
|
||||||
|
(string "Syntax error reading source at (" file ":" l ":" c "): " (e :msg))
|
||||||
|
(string "Syntax error reading source at (" l ":" c "): " (e :msg))))
|
||||||
|
|
||||||
|
(defn reader-error? [e]
|
||||||
|
(and (struct? e) (= :jolt/reader-error (e :jolt/type))))
|
||||||
|
|
||||||
(defn skip-whitespace [s pos]
|
(defn skip-whitespace [s pos]
|
||||||
(if (and (< pos (length s))
|
(if (and (< pos (length s))
|
||||||
(whitespace? (s pos)))
|
(whitespace? (s pos)))
|
||||||
|
|
@ -89,7 +119,7 @@
|
||||||
(defn read-symbol [s pos]
|
(defn read-symbol [s pos]
|
||||||
(let [end (read-symbol-name s pos pos)]
|
(let [end (read-symbol-name s pos pos)]
|
||||||
(if (= end pos)
|
(if (= end pos)
|
||||||
(error (string "Unrecognized character at " pos ": " (string/from-bytes (s pos))))
|
(reader-error (string "Unrecognized character: " (string/from-bytes (s pos))) pos)
|
||||||
(let [name (string/slice s pos end)]
|
(let [name (string/slice s pos end)]
|
||||||
(if (= name "nil") [nil end]
|
(if (= name "nil") [nil end]
|
||||||
(if (= name "true") [true end]
|
(if (= name "true") [true end]
|
||||||
|
|
@ -126,12 +156,12 @@
|
||||||
|
|
||||||
(defn read-string-chars [s pos end chars]
|
(defn read-string-chars [s pos end chars]
|
||||||
(if (>= pos end)
|
(if (>= pos end)
|
||||||
(error "Unterminated string")
|
(reader-error "Unterminated string" pos)
|
||||||
(let [c (s pos)]
|
(let [c (s pos)]
|
||||||
(if (= c 92) # backslash
|
(if (= c 92) # backslash
|
||||||
(let [next-pos (+ pos 1)]
|
(let [next-pos (+ pos 1)]
|
||||||
(if (>= next-pos end)
|
(if (>= next-pos end)
|
||||||
(error "Unterminated escape")
|
(reader-error "Unterminated escape" pos)
|
||||||
(read-string-chars s (+ pos 2) end
|
(read-string-chars s (+ pos 2) end
|
||||||
(array/push chars (escape-char (s next-pos))))))
|
(array/push chars (escape-char (s next-pos))))))
|
||||||
(if (= c 34) # closing quote
|
(if (= c 34) # closing quote
|
||||||
|
|
@ -198,19 +228,19 @@
|
||||||
(= (s pos) 48) (or (= (s (+ pos 1)) 120) (= (s (+ pos 1)) 88)))] # 0x / 0X
|
(= (s pos) 48) (or (= (s (+ pos 1)) 120) (= (s (+ pos 1)) 88)))] # 0x / 0X
|
||||||
(if hex?
|
(if hex?
|
||||||
(let [hs (+ pos 2) he (read-hex-digits s hs hs)]
|
(let [hs (+ pos 2) he (read-hex-digits s hs hs)]
|
||||||
(if (= he hs) (error (string "Expected hex digits at " pos)))
|
(if (= he hs) (reader-error "Expected hex digits" pos))
|
||||||
(let [he2 (if (and (< he len) (= (s he) 78)) (+ he 1) he) # trailing N
|
(let [he2 (if (and (< he len) (= (s he) 78)) (+ he 1) he) # trailing N
|
||||||
val (scan-number (string "0x" (string/slice s hs he)))]
|
val (scan-number (string "0x" (string/slice s hs he)))]
|
||||||
[(if neg (- val) val) he2]))
|
[(if neg (- val) val) he2]))
|
||||||
(let [iend (read-digits s pos pos)]
|
(let [iend (read-digits s pos pos)]
|
||||||
(if (= iend pos) (error (string "Expected number at " pos)))
|
(if (= iend pos) (reader-error "Expected number" pos))
|
||||||
(cond
|
(cond
|
||||||
# radix integer: <base>r<digits>, e.g. 2r1010, 16rFF, 36rZ
|
# radix integer: <base>r<digits>, e.g. 2r1010, 16rFF, 36rZ
|
||||||
(and (< iend len) (or (= (s iend) 114) (= (s iend) 82)))
|
(and (< iend len) (or (= (s iend) 114) (= (s iend) 82)))
|
||||||
(let [base (scan-number (string/slice s pos iend))
|
(let [base (scan-number (string/slice s pos iend))
|
||||||
ds (+ iend 1)
|
ds (+ iend 1)
|
||||||
de (read-alnum s ds ds)]
|
de (read-alnum s ds ds)]
|
||||||
(if (= de ds) (error (string "Expected radix digits at " ds)))
|
(if (= de ds) (reader-error "Expected radix digits" ds))
|
||||||
(var acc 0)
|
(var acc 0)
|
||||||
(var i ds)
|
(var i ds)
|
||||||
(while (< i de) (set acc (+ (* acc base) (radix-digit-val (s i)))) (++ i))
|
(while (< i de) (set acc (+ (* acc base) (radix-digit-val (s i)))) (++ i))
|
||||||
|
|
@ -238,7 +268,7 @@
|
||||||
(defn read-list-items [s pos items]
|
(defn read-list-items [s pos items]
|
||||||
(let [pos (skip-whitespace s pos)]
|
(let [pos (skip-whitespace s pos)]
|
||||||
(if (>= pos (length s))
|
(if (>= pos (length s))
|
||||||
(error "Unterminated list"))
|
(reader-error "Unterminated list" pos))
|
||||||
(if (= (s pos) 41) # )
|
(if (= (s pos) 41) # )
|
||||||
[items (+ pos 1)]
|
[items (+ pos 1)]
|
||||||
(let [[form new-pos] (read-form s pos)]
|
(let [[form new-pos] (read-form s pos)]
|
||||||
|
|
@ -256,7 +286,7 @@
|
||||||
(defn read-vec-items [s pos items]
|
(defn read-vec-items [s pos items]
|
||||||
(let [pos (skip-whitespace s pos)]
|
(let [pos (skip-whitespace s pos)]
|
||||||
(if (>= pos (length s))
|
(if (>= pos (length s))
|
||||||
(error "Unterminated vector"))
|
(reader-error "Unterminated vector" pos))
|
||||||
(if (= (s pos) 93) # ]
|
(if (= (s pos) 93) # ]
|
||||||
[(tuple/slice (tuple ;items)) (+ pos 1)]
|
[(tuple/slice (tuple ;items)) (+ pos 1)]
|
||||||
(let [[form new-pos] (read-form s pos)]
|
(let [[form new-pos] (read-form s pos)]
|
||||||
|
|
@ -282,7 +312,7 @@
|
||||||
(defn read-kvs [s pos kvs]
|
(defn read-kvs [s pos kvs]
|
||||||
(let [pos (skip-whitespace s pos)]
|
(let [pos (skip-whitespace s pos)]
|
||||||
(if (>= pos (length s))
|
(if (>= pos (length s))
|
||||||
(error "Unterminated map"))
|
(reader-error "Unterminated map" pos))
|
||||||
(if (= (s pos) 125) # }
|
(if (= (s pos) 125) # }
|
||||||
[(reader-map kvs) (+ pos 1)]
|
[(reader-map kvs) (+ pos 1)]
|
||||||
(let [[key new-pos] (read-form s pos)]
|
(let [[key new-pos] (read-form s pos)]
|
||||||
|
|
@ -321,7 +351,7 @@
|
||||||
(defn read-set-items [s pos items]
|
(defn read-set-items [s pos items]
|
||||||
(let [pos (skip-whitespace s pos)]
|
(let [pos (skip-whitespace s pos)]
|
||||||
(if (>= pos (length s))
|
(if (>= pos (length s))
|
||||||
(error "Unterminated set"))
|
(reader-error "Unterminated set" pos))
|
||||||
(if (= (s pos) 125) # }
|
(if (= (s pos) 125) # }
|
||||||
[{:jolt/type :jolt/set :value (tuple/slice (tuple ;items))} (+ pos 1)]
|
[{:jolt/type :jolt/set :value (tuple/slice (tuple ;items))} (+ pos 1)]
|
||||||
(let [[form new-pos] (read-form s pos)]
|
(let [[form new-pos] (read-form s pos)]
|
||||||
|
|
@ -477,7 +507,7 @@
|
||||||
(set done [(struct ;[:jolt/type :jolt/tagged :tag :regex :form (string/slice s (+ pos 2) i)])
|
(set done [(struct ;[:jolt/type :jolt/tagged :tag :regex :form (string/slice s (+ pos 2) i)])
|
||||||
(+ i 1)])
|
(+ i 1)])
|
||||||
(++ i))))
|
(++ i))))
|
||||||
(if done done (error "Unterminated regex literal")))
|
(if done done (reader-error "Unterminated regex literal" pos)))
|
||||||
|
|
||||||
(defn read-dispatch [s pos]
|
(defn read-dispatch [s pos]
|
||||||
# pos is at #
|
# pos is at #
|
||||||
|
|
@ -499,7 +529,7 @@
|
||||||
(= name "Inf") [math/inf end]
|
(= name "Inf") [math/inf end]
|
||||||
(= name "-Inf") [(- math/inf) end]
|
(= name "-Inf") [(- math/inf) end]
|
||||||
(= name "NaN") [math/nan end]
|
(= name "NaN") [math/nan end]
|
||||||
(error (string "Invalid symbolic value: ##" name))))
|
(reader-error (string "Invalid symbolic value: ##" name) pos)))
|
||||||
# unknown dispatch — tagged literal
|
# unknown dispatch — tagged literal
|
||||||
(let [end (read-symbol-name s pos pos)
|
(let [end (read-symbol-name s pos pos)
|
||||||
tag (string/slice s pos end)
|
tag (string/slice s pos end)
|
||||||
|
|
@ -581,13 +611,13 @@
|
||||||
|
|
||||||
# unmatched closing delimiters
|
# unmatched closing delimiters
|
||||||
(= c 41)
|
(= c 41)
|
||||||
(error (string "Unmatched closing paren at " pos))
|
(reader-error "Unmatched delimiter: )" pos)
|
||||||
|
|
||||||
(= c 93)
|
(= c 93)
|
||||||
(error (string "Unmatched closing bracket at " pos))
|
(reader-error "Unmatched delimiter: ]" pos)
|
||||||
|
|
||||||
(= c 125)
|
(= c 125)
|
||||||
(error (string "Unmatched closing brace at " pos))
|
(reader-error "Unmatched delimiter: }" pos)
|
||||||
|
|
||||||
# vector
|
# vector
|
||||||
(= c 91)
|
(= c 91)
|
||||||
|
|
@ -637,10 +667,15 @@
|
||||||
(defn parse-string
|
(defn parse-string
|
||||||
"Parse a Clojure source string and return the first form."
|
"Parse a Clojure source string and return the first form."
|
||||||
[s]
|
[s]
|
||||||
(let [[form pos] (read-form s 0)]
|
(try
|
||||||
(if (and (struct? form) (= :jolt/skip (form :jolt/type)))
|
(let [[form pos] (read-form s 0)]
|
||||||
(parse-string (string/slice s pos))
|
(if (and (struct? form) (= :jolt/skip (form :jolt/type)))
|
||||||
form)))
|
(parse-string (string/slice s pos))
|
||||||
|
form))
|
||||||
|
([err fib]
|
||||||
|
(if (reader-error? err)
|
||||||
|
(error (format-reader-error err s nil))
|
||||||
|
(propagate err fib)))))
|
||||||
|
|
||||||
(defn parse-next
|
(defn parse-next
|
||||||
"Parse the first form from a string. Returns [form remaining-string]."
|
"Parse the first form from a string. Returns [form remaining-string]."
|
||||||
|
|
@ -658,7 +693,7 @@
|
||||||
leading trivia itself, so the form's start line is the running newline
|
leading trivia itself, so the form's start line is the running newline
|
||||||
count plus the newlines in the trivia (whitespace, commas, ; comments)
|
count plus the newlines in the trivia (whitespace, commas, ; comments)
|
||||||
ahead of it. (jolt-2o7.4)"
|
ahead of it. (jolt-2o7.4)"
|
||||||
[source]
|
[source &opt file]
|
||||||
(def out @[])
|
(def out @[])
|
||||||
(var s source)
|
(var s source)
|
||||||
(var line 1)
|
(var line 1)
|
||||||
|
|
@ -674,7 +709,17 @@
|
||||||
(or (= c (chr " ")) (= c (chr "\t")) (= c (chr "\r")) (= c (chr ","))) (++ i)
|
(or (= c (chr " ")) (= c (chr "\t")) (= c (chr "\r")) (= c (chr ","))) (++ i)
|
||||||
(= c (chr ";")) (while (and (< i n) (not= (in s i) (chr "\n"))) (++ i))
|
(= c (chr ";")) (while (and (< i n) (not= (in s i) (chr "\n"))) (++ i))
|
||||||
(set scanning false)))
|
(set scanning false)))
|
||||||
(def [form rest*] (parse-next s))
|
(def [form rest*]
|
||||||
|
(try (parse-next s)
|
||||||
|
([err fib]
|
||||||
|
(if (reader-error? err)
|
||||||
|
# err's :pos is relative to the remaining slice — rebase onto the
|
||||||
|
# full source so line:col are absolute (jolt-2o7.5)
|
||||||
|
(error (format-reader-error
|
||||||
|
{:jolt/type :jolt/reader-error :msg (err :msg)
|
||||||
|
:pos (+ (- (length source) (length s)) (err :pos))}
|
||||||
|
source file))
|
||||||
|
(propagate err fib)))))
|
||||||
(def consumed (- (length s) (length rest*)))
|
(def consumed (- (length s) (length rest*)))
|
||||||
(def form-line line)
|
(def form-line line)
|
||||||
# count newlines inside the consumed chunk past the trivia
|
# count newlines inside the consumed chunk past the trivia
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,25 @@
|
||||||
(run-err "-e" `(+ 1 "a")`)
|
(run-err "-e" `(+ 1 "a")`)
|
||||||
(fn [s] (nil? (string/find "<eval>" s))))
|
(fn [s] (nil? (string/find "<eval>" s))))
|
||||||
|
|
||||||
|
# --- round 5: reader errors carry file:line:col ------------------------------
|
||||||
|
(check "unterminated string positions in -e"
|
||||||
|
(run-err "-e" `(+ 1 "abc`)
|
||||||
|
(has "Syntax error reading source at (<eval>:1:10): Unterminated string"))
|
||||||
|
(check "unterminated list names script file:line:col"
|
||||||
|
(do (spit (string r4 "/syn.clj") "(ns syn)\n\n(defn f [x]\n (+ x 1\n")
|
||||||
|
(run-err (string r4 "/syn.clj")))
|
||||||
|
(has ":5:1): Unterminated list"))
|
||||||
|
(check "unmatched delimiter positioned"
|
||||||
|
(do (spit (string r4 "/app/synreq.clj") "(ns app.synreq)\n\n(def x ])\n")
|
||||||
|
(spit (string r4 "/app/top2.clj") "(ns app.top2 (:require [app.synreq :as q]))\n(defn -main [& a] nil)\n")
|
||||||
|
(os/setenv "JOLT_PATH" r4)
|
||||||
|
(run-err "-m" "app.top2"))
|
||||||
|
(fn [s] (and (string/find "/app/synreq.clj:3:8): Unmatched delimiter: ]" s)
|
||||||
|
(string/find "/app/top2.clj:1" s))))
|
||||||
|
(check "bad token positioned"
|
||||||
|
(run-err "-e" "(def x ##Huh)")
|
||||||
|
(has "Invalid symbolic value: ##Huh"))
|
||||||
|
|
||||||
(check "JOLT_DEBUG restores the raw trace"
|
(check "JOLT_DEBUG restores the raw trace"
|
||||||
(do (os/setenv "JOLT_DEBUG" "1")
|
(do (os/setenv "JOLT_DEBUG" "1")
|
||||||
(def r (run-err "-e" `(+ 1 "a")`))
|
(def r (run-err "-e" `(+ 1 "a")`))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue