fix REPL treating a regex literal as an unbalanced form
repl-form-complete? entered the :regex state on '#' but only consumed the '#', so the opening '"' was then read by the :regex handler as the CLOSING quote. The regex body got scanned in :code state, and any delimiter or quote inside it (a group like #"(a)", a char class #"[0-9]+") threw off the paren/string count — so a one-line regex form was judged incomplete and the REPL hung waiting for continuation lines. Consume the '#"' together. Adds a self-checking predicate test (test/chez/repl-reader-test.clj, run via joltc so jolt.main resolves) and an end-to-end regex REPL case in smoke.sh.
This commit is contained in:
parent
4889505204
commit
e4cbbb8912
3 changed files with 53 additions and 2 deletions
|
|
@ -75,6 +75,18 @@ else
|
||||||
fails=$((fails + 1))
|
fails=$((fails + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Unit-checks the REPL read-until-complete predicate over balanced/unbalanced,
|
||||||
|
# string, comment and regex-literal inputs. A multi-form `joltc run` so jolt.main
|
||||||
|
# is loaded and its private var resolves; the file self-checks and prints a sentinel.
|
||||||
|
rr_out="$(bin/joltc run test/chez/repl-reader-test.clj 2>/dev/null)"
|
||||||
|
if printf '%s' "$rr_out" | grep -q 'REPL-READER OK'; then
|
||||||
|
pass=$((pass + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL: repl-form-complete? predicate"
|
||||||
|
echo " $(printf '%s' "$rr_out" | grep REPL-READER | tail -1)"
|
||||||
|
fails=$((fails + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
# REPL must exit on :repl/quit / :exit — a reliable exit that works in any
|
# REPL must exit on :repl/quit / :exit — a reliable exit that works in any
|
||||||
# terminal, unlike ^D (which some terminals/editors don't deliver as EOF).
|
# terminal, unlike ^D (which some terminals/editors don't deliver as EOF).
|
||||||
# Pipe: an evaluable form, the quit keyword, then a sentinel that must NOT run.
|
# Pipe: an evaluable form, the quit keyword, then a sentinel that must NOT run.
|
||||||
|
|
@ -107,5 +119,16 @@ else
|
||||||
fails=$((fails + 1))
|
fails=$((fails + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# A single-line regex literal is complete on its own — the #" opens a regex whose
|
||||||
|
# body (delimiters, quotes and all) must not be miscounted as unbalanced parens.
|
||||||
|
repl_out="$(printf '(re-find #"(a)(b)" "ab")\n:exit\n' | bin/joltc repl 2>/dev/null)"
|
||||||
|
if printf '%s' "$repl_out" | grep -q 'ab' && ! printf '%s' "$repl_out" | grep -q 'error'; then
|
||||||
|
pass=$((pass + 1))
|
||||||
|
else
|
||||||
|
echo " FAIL: repl should evaluate a one-line regex literal, not wait for more input"
|
||||||
|
printf '%s\n' "$repl_out" | sed 's/^/ | /'
|
||||||
|
fails=$((fails + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
echo "cli smoke: $pass passed, $fails failed"
|
echo "cli smoke: $pass passed, $fails failed"
|
||||||
[ "$fails" -eq 0 ]
|
[ "$fails" -eq 0 ]
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,9 @@
|
||||||
(= c \;) (recur (inc i) depth :comment)
|
(= c \;) (recur (inc i) depth :comment)
|
||||||
(= c \\) (recur (+ i 2) depth :code) ; char literal: \(
|
(= c \\) (recur (+ i 2) depth :code) ; char literal: \(
|
||||||
(= c \") (recur (inc i) depth :string)
|
(= c \") (recur (inc i) depth :string)
|
||||||
(= c \#) (recur (inc i) depth
|
(= c \#) (if (= (get s (inc i)) \")
|
||||||
(if (= (get s (inc i)) \") :regex :code))
|
(recur (+ i 2) depth :regex) ; consume the #" together
|
||||||
|
(recur (inc i) depth :code))
|
||||||
(#{\( \[ \{} c) (recur (inc i) (inc depth) :code)
|
(#{\( \[ \{} c) (recur (inc i) (inc depth) :code)
|
||||||
(#{\) \] \}} c) (recur (inc i) (dec depth) :code)
|
(#{\) \] \}} c) (recur (inc i) (dec depth) :code)
|
||||||
:else (recur (inc i) depth :code))
|
:else (recur (inc i) depth :code))
|
||||||
|
|
|
||||||
27
test/chez/repl-reader-test.clj
Normal file
27
test/chez/repl-reader-test.clj
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
;; Self-checking regression for the REPL's read-until-complete predicate
|
||||||
|
;; (jolt.main/repl-form-complete?), which decides whether a line buffer is a whole
|
||||||
|
;; form or the REPL should keep reading continuation lines. Runs via `bin/joltc run`
|
||||||
|
;; (jolt.main is loaded, so the private var resolves); prints a sentinel the smoke
|
||||||
|
;; gate greps. The regex cases are the ones that regressed: a #"..." literal opens a
|
||||||
|
;; regex whose body — parens, quotes and all — must not be miscounted as delimiters.
|
||||||
|
(require 'jolt.main)
|
||||||
|
|
||||||
|
(def complete? jolt.main/repl-form-complete?)
|
||||||
|
|
||||||
|
;; [input expected-complete?]
|
||||||
|
(def cases
|
||||||
|
[["(+ 1 2)" true] ; balanced
|
||||||
|
["(+ 1" false] ; open paren -> keep reading
|
||||||
|
["(defn g [] (foo (bar" false] ; deeply unbalanced
|
||||||
|
["[1 2 {:a 3}]" true] ; mixed bracket types balance
|
||||||
|
["(str \")\")" true] ; a close-paren inside a string doesn't count
|
||||||
|
["\\(" true] ; a paren char literal doesn't count
|
||||||
|
["(+ 1 2) ; ) ) trailing" true] ; parens in a line comment don't count
|
||||||
|
["(re-find #\"(a)(b)\" \"ab\")" true] ; groups inside a regex must not count as depth
|
||||||
|
["#\"[0-9]+\"" true] ; a bare regex literal is a complete form
|
||||||
|
["#\"a(b" false]]) ; an unterminated regex is incomplete
|
||||||
|
|
||||||
|
(let [bad (remove (fn [[in exp]] (= exp (boolean (complete? in)))) cases)]
|
||||||
|
(println (if (empty? bad)
|
||||||
|
"REPL-READER OK"
|
||||||
|
(str "REPL-READER FAIL " (pr-str (map first bad))))))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue