Skip to content

Commit 4aa0ebd

Browse files
committed
Error on unterminated strings.
Add a step1 test to make sure that implementations are properly throwing an error on unclosed strings. Fix 47 implementations and update the guide to note the correct behavior.
1 parent c7ed245 commit 4aa0ebd

File tree

48 files changed

+218
-106
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+218
-106
lines changed

awk/reader.awk

+6-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ function reader_read_atom(token)
1818
case /^:/:
1919
return ":" token
2020
case /^"/:
21-
return reader_read_string(token)
21+
if (token ~ /"$/) {
22+
return reader_read_string(token)
23+
} else {
24+
return "!\"Expected '\"', got EOF."
25+
}
2226
case /^-?[0-9]+$/:
2327
return "+" token
2428
default:
@@ -147,7 +151,7 @@ function reader_read_from(reader, current)
147151

148152
function reader_tokenizer(str, reader, len, r)
149153
{
150-
for (len = 0; match(str, /^[ \t\r\n,]*(~@|[\[\]{}()'`~^@]|\"(\\[^\r\n]|[^\\"\r\n])*\"|;[^\r\n]*|[^ \t\r\n\[\]{}('"`,;)^~@][^ \t\r\n\[\]{}('"`,;)]*)/, r); ) {
154+
for (len = 0; match(str, /^[ \t\r\n,]*(~@|[\[\]{}()'`~^@]|\"(\\[^\r\n]|[^\\"\r\n])*\"?|;[^\r\n]*|[^ \t\r\n\[\]{}('"`,;)^~@][^ \t\r\n\[\]{}('"`,;)]*)/, r); ) {
151155
if (substr(r[1], 1, 1) != ";") {
152156
reader[len++] = r[1]
153157
}

basic/reader.in.bas

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ SUB READ_FORM
166166
READ_STRING:
167167
REM PRINT "READ_STRING"
168168
C=ASC(MID$(T$,LEN(T$),1))
169-
IF C<>34 THEN R=-1:ER=-1:E$="expected '"+CHR$(34)+"'":GOTO READ_FORM_RETURN
169+
IF C<>34 THEN R=-1:ER=-1:E$="expected '"+CHR$(34)+"', got EOF":GOTO READ_FORM_RETURN
170170
R$=MID$(T$,2,LEN(T$)-2)
171171
S1$=CHR$(92)+CHR$(92):S2$=CHR$(127):GOSUB REPLACE: REM protect backslashes
172172
S1$=CHR$(92)+CHR$(34):S2$=CHR$(34):GOSUB REPLACE: REM unescape quotes

c/reader.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Reader *tokenize(char *line) {
5252

5353
Reader *reader = reader_new();
5454

55-
regex = g_regex_new ("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)", 0, 0, &err);
55+
regex = g_regex_new ("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)", 0, 0, &err);
5656
g_regex_match (regex, line, 0, &matchInfo);
5757

5858
if (err != NULL) {
@@ -89,7 +89,7 @@ MalVal *read_atom(Reader *reader) {
8989
token = reader_next(reader);
9090
//g_print("read_atom token: %s\n", token);
9191

92-
regex = g_regex_new ("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)", 0, 0, &err);
92+
regex = g_regex_new ("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"?$|:(.*)|(^[^\"]*$)", 0, 0, &err);
9393
g_regex_match (regex, token, 0, &matchInfo);
9494

9595
if (g_match_info_fetch_pos(matchInfo, 1, &pos, NULL) && pos != -1) {
@@ -109,6 +109,9 @@ MalVal *read_atom(Reader *reader) {
109109
atom = &mal_false;
110110
} else if (g_match_info_fetch_pos(matchInfo, 6, &pos, NULL) && pos != -1) {
111111
//g_print("read_atom string: %s\n", token);
112+
int end = strlen(token)-1;
113+
if (token[end] != '"') { abort("expected '\"', got EOF"); }
114+
token[end] = '\0';
112115
atom = malval_new_string(g_strcompress(g_match_info_fetch(matchInfo, 6)));
113116
} else if (g_match_info_fetch_pos(matchInfo, 7, &pos, NULL) && pos != -1) {
114117
//g_print("read_atom keyword\n");

clojure/src/mal/reader.cljc

+10-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
(def tok-re #"[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:[\\].|[^\\\"])*\"?|;.*|[^\s\[\]{}()'\"`@,;]+)")
1919
(def int-re #"^-?[0-9]+$")
20+
(def badstr-re #"^\"(.*)[^\"]$")
2021
(def str-re #"^\"(.*)\"$")
2122

2223
(defn tokenize [s]
@@ -32,14 +33,15 @@
3233
(defn read-atom [rdr]
3334
(let [token (rdr-next rdr)]
3435
(cond
35-
(re-seq int-re token) #?(:cljs (js/parseInt token)
36-
:clj (Integer/parseInt token))
37-
(re-seq str-re token) (unescape (second (re-find str-re token)))
38-
(= \: (get token 0)) (keyword (subs token 1))
39-
(= "nil" token) nil
40-
(= "true" token) true
41-
(= "false" token) false
42-
:else (symbol token))))
36+
(re-seq int-re token) #?(:cljs (js/parseInt token)
37+
:clj (Integer/parseInt token))
38+
(re-seq badstr-re token) (throw-str (str "expected '\"', got EOF"))
39+
(re-seq str-re token) (unescape (second (re-find str-re token)))
40+
(= \: (get token 0)) (keyword (subs token 1))
41+
(= "nil" token) nil
42+
(= "true" token) true
43+
(= "false" token) false
44+
:else (symbol token))))
4345

4446
(declare read-form)
4547

crystal/reader.cr

+6-4
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,11 @@ class Reader
8181
when token == "true" then true
8282
when token == "false" then false
8383
when token == "nil" then nil
84-
when token[0] == '"' then token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"",
85-
"\\n" => "\n",
86-
"\\\\" => "\\"})
84+
when token[0] == '"'
85+
parse_error "expected '\"', got EOF" if token[-1] != '"'
86+
token[1..-2].gsub(/\\(.)/, {"\\\"" => "\"",
87+
"\\n" => "\n",
88+
"\\\\" => "\\"})
8789
when token[0] == ':' then "\u029e#{token[1..-1]}"
8890
else Mal::Symbol.new token
8991
end
@@ -121,7 +123,7 @@ class Reader
121123
end
122124

123125
def tokenize(str)
124-
regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/
126+
regex = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/
125127
str.scan(regex).map { |m| m[1] }.reject(&.empty?)
126128
end
127129

cs/reader.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public string next() {
3939

4040
public static List<string> tokenize(string str) {
4141
List<string> tokens = new List<string>();
42-
string pattern = @"[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""|;.*|[^\s \[\]{}()'""`~@,;]*)";
42+
string pattern = @"[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""?|;.*|[^\s \[\]{}()'""`~@,;]*)";
4343
Regex regex = new Regex(pattern);
4444
foreach (Match match in regex.Matches(str)) {
4545
string token = match.Groups[1].Value;
@@ -53,7 +53,7 @@ public static List<string> tokenize(string str) {
5353

5454
public static MalVal read_atom(Reader rdr) {
5555
string token = rdr.next();
56-
string pattern = @"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*"")$|:(.*)|(^[^""]*$)";
56+
string pattern = @"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^("".*)|:(.*)|(^[^""]*$)";
5757
Regex regex = new Regex(pattern);
5858
Match match = regex.Match(token);
5959
//Console.WriteLine("token: ^" + token + "$");
@@ -70,6 +70,9 @@ public static MalVal read_atom(Reader rdr) {
7070
return Mal.types.False;
7171
} else if (match.Groups[6].Value != String.Empty) {
7272
string str = match.Groups[6].Value;
73+
if (str[str.Length-1] != '"') {
74+
throw new ParseError("expected '\"', got EOF");
75+
}
7376
str = str.Substring(1, str.Length-2)
7477
.Replace("\\\\", "\u029e")
7578
.Replace("\\\"", "\"")

d/reader.d

+5-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class Reader
4444
}
4545
}
4646

47-
auto tokenize_ctr = ctRegex!(r"[\s,]*(~@|[\[\]{}()'`~^@]|" `"` `(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"` r"`,;)]*)");
47+
auto tokenize_ctr = ctRegex!(r"[\s,]*(~@|[\[\]{}()'`~^@]|" `"` `(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"` r"`,;)]*)");
4848

4949
string[] tokenize(string str)
5050
{
@@ -87,6 +87,10 @@ MalType read_atom(Reader reader)
8787
case ':':
8888
return new MalString("\u029e" ~ token[1..$]);
8989
case '"':
90+
if (token[$-1] != '"')
91+
{
92+
throw new Exception("expected '\"', got EOF");
93+
}
9094
return parse_string(token);
9195
default:
9296
auto captures = matchFirst(token, integer_ctr);

dart/reader.dart

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'types.dart';
22

33
final malRegExp = new RegExp(
4-
r"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)""");
4+
r"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)""");
55

66
class Reader {
77
final List<String> tokens;
@@ -115,6 +115,9 @@ MalType read_atom(Reader reader) {
115115
}
116116

117117
if (token[0] == '"') {
118+
if (token[token.length -1 ] != '"') {
119+
throw new ParseException("expected '\"', got EOF");
120+
}
118121
var sanitizedToken = token
119122
// remove surrounding quotes
120123
.substring(1, token.length - 1)

factor/lib/reader/reader.factor

+13-9
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,23 @@ USING: arrays combinators grouping hashtables kernel lists locals
44
make lib.types math.parser regexp sequences splitting strings ;
55
IN: lib.reader
66

7-
CONSTANT: token-regex R/ (~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)~^@]+)/
7+
CONSTANT: token-regex R/ (~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)~^@]+)/
88

99
DEFER: read-form
1010

1111
: (read-string) ( str -- maltype )
12-
rest but-last R/ \\./ [
13-
{
14-
{ [ dup >string "\\\\" = ] [ drop "\\" ] }
15-
{ [ dup >string "\\n" = ] [ drop "\n" ] }
16-
{ [ dup >string "\\\"" = ] [ drop "\"" ] }
17-
[ ]
18-
} cond
19-
] re-replace-with ;
12+
dup last CHAR: " = [
13+
rest but-last R/ \\./ [
14+
{
15+
{ [ dup >string "\\\\" = ] [ drop "\\" ] }
16+
{ [ dup >string "\\n" = ] [ drop "\n" ] }
17+
{ [ dup >string "\\\"" = ] [ drop "\"" ] }
18+
[ ]
19+
} cond
20+
] re-replace-with
21+
] [
22+
"expected '\"', got EOF" throw
23+
] if ;
2024

2125
: (read-atom) ( str -- maltype )
2226
{

fantom/src/mallib/fan/reader.fan

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Reader
1818
{
1919
private static Str[] tokenize(Str s)
2020
{
21-
r := Regex <|[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)|>
21+
r := Regex <|[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)|>
2222
m := r.matcher(s)
2323
tokens := Str[,]
2424
while (m.find())
@@ -39,10 +39,14 @@ class Reader
3939
{
4040
token := reader.next
4141
intRegex := Regex <|^-?\d+$|>
42+
strRegex := Regex <|^".*"|>
43+
strBadRegex := Regex <|^".*|>
4244
if (token == "nil") return MalNil.INSTANCE
4345
if (token == "true") return MalTrue.INSTANCE
4446
if (token == "false") return MalFalse.INSTANCE
4547
if (intRegex.matches(token)) return MalInteger(token.toInt)
48+
if (strRegex.matches(token)) return MalString.make(unescape_str(token[1..-2]))
49+
if (strBadRegex.matches(token)) throw Err("expected '\"', got EOF")
4650
if (token[0] == '"') return MalString.make(unescape_str(token[1..-2]))
4751
if (token[0] == ':') return MalString.makeKeyword(token[1..-1])
4852
return MalSymbol(token)

go/src/reader/reader.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func tokenize(str string) []string {
4242
results := make([]string, 0, 1)
4343
// Work around lack of quoting in backtick
4444
re := regexp.MustCompile(`[\s,]*(~@|[\[\]{}()'` + "`" +
45-
`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"` + "`" +
45+
`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"` + "`" +
4646
`,;)]*)`)
4747
for _, group := range re.FindAllStringSubmatch(str, -1) {
4848
if (group[1] == "") || (group[1][0] == ';') {
@@ -66,6 +66,9 @@ func read_atom(rdr Reader) (MalType, error) {
6666
}
6767
return i, nil
6868
} else if (*token)[0] == '"' {
69+
if (*token)[len(*token)-1] != '"' {
70+
return nil, errors.New("expected '\"', got EOF")
71+
}
6972
str := (*token)[1 : len(*token)-1]
7073
return strings.Replace(
7174
strings.Replace(

groovy/reader.groovy

+9-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class reader {
2929
}
3030

3131
def static tokenizer(String str) {
32-
def m = str =~ /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/
32+
def m = str =~ /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/
3333
def tokens = []
3434
while (m.find()) {
3535
String token = m.group(1)
@@ -44,7 +44,7 @@ class reader {
4444

4545
def static read_atom(Reader rdr) {
4646
def token = rdr.next()
47-
def m = token =~ /(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^"(.*)"$|:(.*)|(^[^"]*$)/
47+
def m = token =~ /(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^"(.*)"$|^"(.*)$|:(.*)|(^[^"]*$)/
4848
if (!m.find()) {
4949
throw new MalException("unrecognized token '$token'")
5050
}
@@ -57,11 +57,16 @@ class reader {
5757
} else if (m.group(5) != null) {
5858
false
5959
} else if (m.group(6) != null) {
60+
if (token[token.length() - 1] != '"') {
61+
throw new MalException("expected '\"', got EOF")
62+
}
6063
StringEscapeUtils.unescapeJava(m.group(6))
6164
} else if (m.group(7) != null) {
62-
"\u029e" + m.group(7)
65+
throw new MalException("expected '\"', got EOF")
6366
} else if (m.group(8) != null) {
64-
new MalSymbol(m.group(8))
67+
"\u029e" + m.group(8)
68+
} else if (m.group(9) != null) {
69+
new MalSymbol(m.group(9))
6570
} else {
6671
throw new MalException("unrecognized '${m.group(0)}'")
6772
}

haxe/reader/Reader.hx

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class Reader {
7373
"\n"),
7474
"\""),
7575
"\\"));
76-
case _ if (re_str.match(token)):
76+
case _ if (re_str_bad.match(token)):
7777
throw 'expected \'"\', got EOF';
7878
case _:
7979
MalSymbol(token);

hy/reader.hy

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
(def tok-re (.compile re "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}()'\"`@,;]+)"))
1919
(def int-re (.compile re "-?[0-9]+$"))
20+
(def str-re (.compile re "^\".*\"$"))
21+
(def str-bad-re (.compile re "^\".*$"))
2022

2123
(defn tokenize [str]
2224
(list-comp
@@ -34,7 +36,8 @@
3436
(setv token (.next rdr))
3537
(if
3638
(.match re int-re token) (int token)
37-
(= "\"" (get token 0)) (Str (unescape (cut token 1 -1)))
39+
(.match re str-re token) (Str (unescape (cut token 1 -1)))
40+
(.match re str-bad-re token) (raise (Exception (+ "expected '\"', got EOF")))
3841
(= ":" (get token 0)) (Keyword token)
3942
(= "nil" token) None
4043
(= "true" token) True

io/MalReader.io

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ MalReader := Object clone do (
1616
)
1717
)
1818

19-
tokenizerRegex := Regex with("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)")
19+
tokenizerRegex := Regex with("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)")
2020

2121
tokenize := method(str,
2222
tokenizerRegex matchesIn(str) \
@@ -28,6 +28,7 @@ MalReader := Object clone do (
2828
numberRegex := Regex with("^-?[0-9]+$")
2929

3030
read_string := method(token,
31+
(token endsWithSeq("\"")) ifFalse(Exception raise("expected '\"', got EOF"))
3132
placeholder := 127 asCharacter
3233
token exSlice(1, -1) replaceSeq("\\\\", placeholder) replaceSeq("\\\"", "\"") replaceSeq("\\n", "\n") replaceSeq(placeholder, "\\")
3334
)

java/src/main/java/mal/reader.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public String next() {
3535

3636
public static ArrayList<String> tokenize(String str) {
3737
ArrayList<String> tokens = new ArrayList<String>();
38-
Pattern pattern = Pattern.compile("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)");
38+
Pattern pattern = Pattern.compile("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)");
3939
Matcher matcher = pattern.matcher(str);
4040
while (matcher.find()) {
4141
String token = matcher.group(1);
@@ -51,7 +51,7 @@ public static ArrayList<String> tokenize(String str) {
5151
public static MalVal read_atom(Reader rdr)
5252
throws ParseError {
5353
String token = rdr.next();
54-
Pattern pattern = Pattern.compile("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)");
54+
Pattern pattern = Pattern.compile("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|^\"(.*)$|:(.*)|(^[^\"]*$)");
5555
Matcher matcher = pattern.matcher(token);
5656
if (!matcher.find()) {
5757
throw new ParseError("unrecognized token '" + token + "'");
@@ -67,9 +67,11 @@ public static MalVal read_atom(Reader rdr)
6767
} else if (matcher.group(6) != null) {
6868
return new MalString(StringEscapeUtils.unescapeJson(matcher.group(6)));
6969
} else if (matcher.group(7) != null) {
70-
return new MalString("\u029e" + matcher.group(7));
70+
throw new ParseError("expected '\"', got EOF");
7171
} else if (matcher.group(8) != null) {
72-
return new MalSymbol(matcher.group(8));
72+
return new MalString("\u029e" + matcher.group(8));
73+
} else if (matcher.group(9) != null) {
74+
return new MalSymbol(matcher.group(9));
7375
} else {
7476
throw new ParseError("unrecognized '" + matcher.group(0) + "'");
7577
}

julia/reader.jl

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ end
2626

2727

2828
function tokenize(str)
29-
re = r"[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\.|[^\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)"
29+
re = r"[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\.|[^\\\"])*\"?|;.*|[^\s\[\]{}('\"`,;)]*)"
3030
tokens = map((m) -> m.captures[1], eachmatch(re, str))
3131
filter((t) -> t != "" && t[1] != ';', tokens)
3232
end
@@ -41,6 +41,8 @@ function read_atom(rdr)
4141
replace(token[2:end-1], r"\\.", (r) -> get(Dict("\\n"=>"\n",
4242
"\\\""=>"\"",
4343
"\\\\"=>"\\"), r, r))
44+
elseif ismatch(r"^\".*$", token)
45+
error("expected '\"', got EOF")
4446
elseif token[1] == ':'
4547
"\u029e$(token[2:end])"
4648
elseif token == "nil"

0 commit comments

Comments
 (0)