From d724aa70692cc6b39b7b1215fdb4d27d6c37a46e Mon Sep 17 00:00:00 2001 From: Yogthos Date: Sat, 6 Jun 2026 00:10:17 -0400 Subject: [PATCH] reader: one-char literals for non-symbol chars (\{ \( \, \%) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit read-char read the char name with symbol-char?, so a literal whose char isn't a symbol char (\{, \(, \), \,, \%, …) came back as an empty name and errored 'Unsupported character'. Now a single non-symbol char after \ is taken as a one-character literal of that char. Surfaced by funcool/cuerdas (uses \{), which now loads. Spec cases added. --- doc/tools-deps.md | 5 +++-- src/jolt/reader.janet | 12 +++++++++--- test/spec/reader-syntax-spec.janet | 6 ++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/doc/tools-deps.md b/doc/tools-deps.md index d98b4e8..96dbe9d 100644 --- a/doc/tools-deps.md +++ b/doc/tools-deps.md @@ -86,8 +86,9 @@ env read would be frozen at build). libs and reports load/run status (network-gated behind `JOLT_CONFORMANCE=1`, so CI stays offline). First run: - `medley` — loads and runs. - - `cuerdas` — fails to load: the reader rejects a char/literal it uses - (`Unsupported character: \`). A genuine reader gap to chase. + - `cuerdas` — now loads (it used `\{`, a one-char literal the reader rejected; + fixed). A function (`kebab`) still hits a separate runtime gap — a smaller, + per-function issue rather than a whole-namespace failure. - `stuartsierra/dependency` — `Long/MAX_VALUE`: JVM interop, so out of scope (it isn't actually pure-`cljc`). diff --git a/src/jolt/reader.janet b/src/jolt/reader.janet index c2e6164..65b0b36 100644 --- a/src/jolt/reader.janet +++ b/src/jolt/reader.janet @@ -314,9 +314,15 @@ (defn read-char [s pos] # pos is at backslash; produce a char value directly (self-evaluating) - (let [end (read-char-name-end s (+ pos 1)) - char-name (string/slice s (+ pos 1) end)] - [(char-from-name char-name) end])) + (when (>= (+ pos 1) (length s)) + (error "unexpected end of input after \\")) + (let [end (read-char-name-end s (+ pos 1))] + (if (= end (+ pos 1)) + # The char right after \ isn't a symbol char (e.g. \{ \( \, \% \" ): it's a + # one-character literal of that character itself. + [(make-char (s (+ pos 1))) (+ pos 2)] + (let [char-name (string/slice s (+ pos 1) end)] + [(char-from-name char-name) end])))) (defn read-anon-fn [s pos] # pos is at #, next char is ( diff --git a/test/spec/reader-syntax-spec.janet b/test/spec/reader-syntax-spec.janet index ce6995f..ee3c25a 100644 --- a/test/spec/reader-syntax-spec.janet +++ b/test/spec/reader-syntax-spec.janet @@ -12,6 +12,12 @@ ["namespaced keyword" "true" "(= :a/b :a/b)"] ["char" "\\a" "\\a"] ["char newline" "true" "(= \\newline (first \"\\n\"))"] + # single non-symbol chars are one-char literals (\{ \( \, \% etc.) + ["char open-brace" "123" "(int \\{)"] + ["char open-paren" "40" "(int \\()"] + ["char comma" "44" "(int \\,)"] + ["char percent" "37" "(int \\%)"] + ["char unicode" "65" "(int \\u0041)"] ["hex literal" "255" "0xff"] ["hex uppercase" "31" "0X1F"] ["bigint suffix N" "42" "42N"]