Make the REPL read multi-line forms and render real error messages
The REPL evaluated one line at a time, so a form split across lines (e.g. `(+` then `1 2)`) raised instead of waiting. The read loop now accumulates lines until delimiters are balanced — skipping string, char, regex and comment context — printing a `... ` continuation prompt for each extra line. Reader/runtime errors rendered as Chez's "attempt to apply non-procedure #[chez-pmap...]" instead of their real message. Two causes: jolt-throw raised the thrown value raw. When a throw crossed the host `eval` boundary, Chez re-wrapped the non-condition into a compound condition whose message extraction applies the value, losing the message and crashing on ex-info's empty-map :data. jolt-throw now raises a &jolt-throw condition wrapping the value; catch (lowered to `guard`), jolt-report-uncaught and jolt-render-throwable unwrap it back via jolt-unwrap-throw, so ex-data/ex-message and the backtrace tag survive. Every reader/post-prelude EOF-throw site used `(empty-pmap)` (with parens), applying the empty-map value as a procedure and crashing during ex-info construction before jolt-throw ran. Fixed to `empty-pmap`. Re-minted the seed; smoke 23/23, unit 574/574.
This commit is contained in:
parent
a58bca3bee
commit
240458d994
10 changed files with 225 additions and 132 deletions
|
|
@ -258,6 +258,7 @@
|
|||
(let [n {:op :try :body (analyze-seq ctx @body env)}
|
||||
n (if (seq @catches)
|
||||
(let [evar-name (gen-name "catch")
|
||||
raw-name (gen-name "catch-raw")
|
||||
evar (symbol evar-name)
|
||||
dispatch
|
||||
(reduce
|
||||
|
|
@ -278,6 +279,7 @@
|
|||
(list 'throw evar)
|
||||
(reverse @catches))]
|
||||
(assoc n :catch-sym evar-name
|
||||
:catch-raw-sym raw-name
|
||||
:catch-body (analyze-seq ctx (list dispatch)
|
||||
(add-locals env [evar-name]))))
|
||||
n)
|
||||
|
|
|
|||
|
|
@ -666,14 +666,18 @@
|
|||
:else
|
||||
(invoke))))
|
||||
|
||||
;; try/catch/finally. throw raises the jolt value RAW (jolt-throw =
|
||||
;; Scheme `raise`); catch lowers to `guard` with an `else` clause (the IR drops
|
||||
;; the class), finally to `dynamic-wind`'s after-thunk (runs on success, catch and
|
||||
;; escape — Clojure finally semantics). Both keys optional on the node.
|
||||
;; try/catch/finally. throw raises a Chez condition wrapping the jolt value
|
||||
;; (jolt-throw = Scheme `raise` of a &jolt-throw condition); catch lowers to
|
||||
;; `guard`, whose raw binding is unwrapped via jolt-unwrap-throw so the catch var
|
||||
;; receives the jolt value (preserving ex-data/ex-message and the backtrace
|
||||
;; identity tag). finally lowers to `dynamic-wind`'s after-thunk (runs on
|
||||
;; success, catch and escape — Clojure finally semantics). Both keys optional.
|
||||
(defn- emit-try [node]
|
||||
(let [core (if-let [cs (:catch-sym node)]
|
||||
(str "(guard (" (munge-name cs) " (else " (emit (:catch-body node)) ")) "
|
||||
(emit (:body node)) ")")
|
||||
(let [raw (munge-name (:catch-raw-sym node))]
|
||||
(str "(guard (" raw " (else (let ((" (munge-name cs) " (jolt-unwrap-throw " raw "))) "
|
||||
(emit (:catch-body node)) "))) "
|
||||
(emit (:body node)) ")"))
|
||||
(emit (:body node)))]
|
||||
(if-let [fin (:finally node)]
|
||||
(str "(dynamic-wind (lambda () #f) (lambda () " core ") (lambda () " (emit fin) "))")
|
||||
|
|
|
|||
|
|
@ -88,6 +88,53 @@
|
|||
(let [{:keys [roots]} (deps/resolve-project (project-dir))]
|
||||
(println (str/join ":" roots))))
|
||||
|
||||
(defn- repl-form-complete?
|
||||
"True when `s` has balanced ()/[]/{}, no open string/char/regex, and at most
|
||||
a trailing comment past the last form. Drives the REPL's read-until-complete
|
||||
decision so a form split across lines is accumulated, not evaluated half-read."
|
||||
[s]
|
||||
(let [n (count s)]
|
||||
(loop [i 0 depth 0 state :code] ; state: :code :string :regex :comment
|
||||
(if (>= i n)
|
||||
(and (<= depth 0) (#{:code :comment} state))
|
||||
(let [c (get s i)]
|
||||
(case state
|
||||
:code (cond
|
||||
(= c \;) (recur (inc i) depth :comment)
|
||||
(= c \\) (recur (+ i 2) depth :code) ; char literal: \(
|
||||
(= c \") (recur (inc i) depth :string)
|
||||
(= c \#) (recur (inc i) depth
|
||||
(if (= (get s (inc i)) \") :regex :code))
|
||||
(#{\( \[ \{} c) (recur (inc i) (inc depth) :code)
|
||||
(#{\) \] \}} c) (recur (inc i) (dec depth) :code)
|
||||
:else (recur (inc i) depth :code))
|
||||
:string (cond
|
||||
(= c \\) (recur (+ i 2) depth :string) ; escaped char
|
||||
(= c \") (recur (inc i) depth :code)
|
||||
:else (recur (inc i) depth :string))
|
||||
:regex (cond
|
||||
(= c \\) (recur (+ i 2) depth :regex)
|
||||
(= c \") (recur (inc i) depth :code)
|
||||
:else (recur (inc i) depth :regex))
|
||||
:comment (recur (inc i) depth
|
||||
(if (#{\newline \return} c) :code :comment))))))))
|
||||
|
||||
(defn- repl-read-form []
|
||||
;; Read lines — printing a secondary prompt for continuations — until the
|
||||
;; accumulated buffer is a complete form. Returns the (possibly multi-line)
|
||||
;; buffer, or nil on EOF at the primary prompt.
|
||||
(loop [buf nil]
|
||||
(print (if buf "... " "user=> ")) (flush)
|
||||
(let [line (read-line)]
|
||||
(cond
|
||||
(nil? line) buf ; EOF: nil at primary, partial mid-form
|
||||
(nil? buf) (cond
|
||||
(str/blank? line) (recur nil) ; skip a blank first line
|
||||
(repl-form-complete? line) line
|
||||
:else (recur line))
|
||||
:else (let [nb (str buf "\n" line)]
|
||||
(if (repl-form-complete? nb) nb (recur nb)))))))
|
||||
|
||||
(defn- repl []
|
||||
;; resolve the project so deps (git libs) are on the roots and native libs are
|
||||
;; loaded — same context a run gets, so (require '[some.lib]) works in the REPL.
|
||||
|
|
@ -95,15 +142,14 @@
|
|||
(catch :default _ nil))
|
||||
(println ";; jolt repl — :repl/quit or ^D to exit")
|
||||
(loop []
|
||||
(print "user=> ") (flush)
|
||||
(let [line (read-line)]
|
||||
(when line
|
||||
(let [form (repl-read-form)]
|
||||
(when form
|
||||
;; :repl/quit / :exit exit the loop — a reliable gesture that works in any
|
||||
;; terminal, unlike ^D (some terminals/editors don't deliver it as EOF).
|
||||
(if (#{:repl/quit :exit} (try (read-string line) (catch :default _ nil)))
|
||||
(if (#{:repl/quit :exit} (try (read-string form) (catch :default _ nil)))
|
||||
nil
|
||||
(do
|
||||
(try (println (pr-str (load-string line)))
|
||||
(try (println (pr-str (load-string form)))
|
||||
(catch :default e
|
||||
(println "error:" (or (ex-message e)
|
||||
(try ((resolve 'jolt.host/condition-message) e) (catch :default _ nil))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue