Syntax errors were positionless ('Unterminated list', no idea where).
Now they use Clojure's shape:
Error: Syntax error reading source at (src/app/syn.clj:3:8): Unmatched delimiter: ]
at src/app/top.clj:1
The reader's 15 error sites raise a {:jolt/reader-error :msg :pos}
struct carrying the byte offset (every site already had pos in scope).
The parse entry points convert offset -> line:col on demand and
re-raise the formatted message: parse-string against its own string
(no file), parse-all-positioned against the full source with the
file threaded in from the loaders — rebasing slice-relative offsets
onto the original source so positions stay absolute. No per-token
cost; nothing is tracked until an error actually happens.
Unmatched-delimiter messages match Clojure's 'Unmatched delimiter: )'
wording. cli-test rows assert positions for unterminated string/list,
unmatched delimiter through a require (composing with round 4's
'while loading' chain), and bad ## tokens. Gate green, suite 4718
steady, bench within noise.
117 lines
5.4 KiB
Text
117 lines
5.4 KiB
Text
# Smoke-test the command-line flags by running main.janet from source (no build
|
|
# needed, so it can't go stale).
|
|
|
|
(defn- run [& args]
|
|
(def p (os/spawn ["janet" "src/jolt/main.janet" ;args] :p {:out :pipe :err :pipe}))
|
|
(def out (:read (p :out) :all))
|
|
(os/proc-wait p)
|
|
(string (or out "")))
|
|
|
|
(defn- run-err [& args]
|
|
(def p (os/spawn ["janet" "src/jolt/main.janet" ;args] :p {:out :pipe :err :pipe}))
|
|
(def err (:read (p :err) :all))
|
|
(os/proc-wait p)
|
|
(string (or err "")))
|
|
|
|
(var fails 0)
|
|
(defn check [label got pred]
|
|
(if (pred got) (print " ok " label)
|
|
(do (++ fails) (printf " FAIL %s: got %q" label got))))
|
|
|
|
(defn- has [sub] (fn [s] (string/find sub s)))
|
|
|
|
(check "--version" (run "--version") (has "jolt v"))
|
|
(check "version" (run "version") (has "jolt v"))
|
|
(check "--help" (run "--help") (has "Usage"))
|
|
(check "help lists nrepl-server" (run "help") (has "nrepl-server"))
|
|
(check "-e" (run "-e" "(+ 1 2)") (has "3"))
|
|
(check "--eval" (run "--eval" "(* 6 7)") (has "42"))
|
|
|
|
# -m requires a namespace and calls its -main with the remaining args
|
|
(def tmp (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-cli-" (os/time)))
|
|
(os/mkdir tmp) (os/mkdir (string tmp "/app"))
|
|
(spit (string tmp "/app/core.clj")
|
|
"(ns app.core)\n(defn -main [& args] (println \"MAIN\" (count args)))\n")
|
|
(os/setenv "JOLT_PATH" tmp)
|
|
(check "-m calls -main with args" (run "-m" "app.core" "a" "b") (has "MAIN 2"))
|
|
(os/setenv "JOLT_PATH" nil)
|
|
(os/rm (string tmp "/app/core.clj")) (os/rmdir (string tmp "/app")) (os/rmdir tmp)
|
|
|
|
|
|
|
|
# --- user-facing error output (jolt-2o7 rounds 1+2) --------------------------
|
|
# Messages are Clojure-shaped; traces show the USER'S fns (compiled fn names
|
|
# carry ns/fn-name, demangled by report-error) and never jolt internals.
|
|
(check "arith error message rewritten"
|
|
(run-err "-e" `(+ 1 "a")`)
|
|
(has `Cannot add 1 and "a"`))
|
|
(check "arity error names the fn"
|
|
(run-err "-e" "(defn afn [x] x) (afn 1 2)")
|
|
(has "Wrong number of args (2) passed to: user/afn"))
|
|
(check "nil-call (nil value) keeps the hint"
|
|
(run-err "-e" "(def x nil) (x 1)")
|
|
(has "Cannot call nil as a function"))
|
|
# round 3: typos die at resolve time with Clojure's message, not as nil-calls
|
|
(check "unresolved symbol named at resolve time"
|
|
(run-err "-e" "(undefined-fn 1)")
|
|
(has "Unable to resolve symbol: undefined-fn in this context"))
|
|
(check "typo inside fn body also resolves to the message"
|
|
(run-err "-e" "(defn f [] (no-such 1)) (f)")
|
|
(has "Unable to resolve symbol: no-such"))
|
|
(check "trace shows the user's call chain"
|
|
(run-err "-e" "(defn inner [x] (let [r (+ x :k)] r)) (defn outer [x] (let [v (inner x)] v)) (outer 1)")
|
|
(fn [s] (and (string/find "at user/inner" s) (string/find "at user/outer" s))))
|
|
(check "no jolt-internal frames in user errors"
|
|
(run-err "-e" `(+ 1 "a")`)
|
|
(fn [s] (nil? (string/find "src/jolt/" s))))
|
|
# --- round 4: load errors carry file:line + the require chain ---------------
|
|
(def r4 (string (or (os/getenv "TMPDIR") "/tmp") "/jolt-cli-r4-" (os/time)))
|
|
(os/mkdir r4) (os/mkdir (string r4 "/app"))
|
|
(spit (string r4 "/app/broken.clj") "(ns app.broken)\n\n(def config\n (+ 1 \"boom\"))\n")
|
|
(spit (string r4 "/app/mid.clj") "(ns app.mid (:require [app.broken :as b]))\n")
|
|
(spit (string r4 "/app/top.clj") "(ns app.top (:require [app.mid :as m]))\n(defn -main [& a] nil)\n")
|
|
(os/setenv "JOLT_PATH" r4)
|
|
(def deep-err (run-err "-m" "app.top"))
|
|
(check "load error names the failing file:line" deep-err
|
|
(has "at " ))
|
|
(check "load error points into broken.clj line 3" deep-err
|
|
(has "/app/broken.clj:3"))
|
|
(check "require chain shows the loading path" deep-err
|
|
(fn [s] (and (string/find "while loading" s) (string/find "/app/mid.clj" s) (string/find "/app/top.clj" s))))
|
|
(check "script errors name the script file"
|
|
(do (spit (string r4 "/scr.clj") "(ns scr)\n\n(+ 1 \"x\")\n")
|
|
(run-err (string r4 "/scr.clj")))
|
|
(has "/scr.clj:3"))
|
|
(check "no synthetic <eval> position on one-liners"
|
|
(run-err "-e" `(+ 1 "a")`)
|
|
(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"
|
|
(do (os/setenv "JOLT_DEBUG" "1")
|
|
(def r (run-err "-e" `(+ 1 "a")`))
|
|
(os/setenv "JOLT_DEBUG" nil)
|
|
r)
|
|
(has "could not find method"))
|
|
|
|
(if (> fails 0)
|
|
(error (string "cli-test: " fails " failing check(s)"))
|
|
(print "\nAll CLI tests passed!"))
|