Skip to content

Commit bbcbc53

Browse files
authored
Allow backticks to be quoted in string literals (#820)
* allow backticks to be quoted in string literals * remove redundant empty string
1 parent 87df783 commit bbcbc53

File tree

2 files changed

+73
-5
lines changed

2 files changed

+73
-5
lines changed

parser/lexer/lexer.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,15 +242,47 @@ func (l *Lexer) scanString(quote rune) (n int) {
242242
}
243243

244244
func (l *Lexer) scanRawString(quote rune) (n int) {
245-
ch := l.next() // read character after back tick
246-
for ch != quote {
247-
if ch == eof {
245+
var escapedQuotes int
246+
loop:
247+
for {
248+
ch := l.next()
249+
for ch == quote && l.peek() == quote {
250+
// skip current and next char which are the quote escape sequence
251+
l.next()
252+
ch = l.next()
253+
escapedQuotes++
254+
}
255+
switch ch {
256+
case quote:
257+
break loop
258+
case eof:
248259
l.error("literal not terminated")
249260
return
250261
}
251-
ch = l.next()
252262
n++
253263
}
254-
l.emitValue(String, l.source.String()[l.start.byte+1:l.end.byte-1])
264+
str := l.source.String()[l.start.byte+1 : l.end.byte-1]
265+
266+
// handle simple case where no quoted backtick was found, then no allocation
267+
// is needed for the new string
268+
if escapedQuotes == 0 {
269+
l.emitValue(String, str)
270+
return
271+
}
272+
273+
var b strings.Builder
274+
var skipped bool
275+
b.Grow(len(str) - escapedQuotes)
276+
for _, r := range str {
277+
if r == quote {
278+
if !skipped {
279+
skipped = true
280+
continue
281+
}
282+
skipped = false
283+
}
284+
b.WriteRune(r)
285+
}
286+
l.emitValue(String, b.String())
255287
return
256288
}

parser/lexer/lexer_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ func TestLex(t *testing.T) {
6868
{Kind: EOF},
6969
},
7070
},
71+
{
72+
"`escaped backticks` `` `a``b` ```` `a``` ```b` ```a````b``` ```````` ```a````` `````b```",
73+
[]Token{
74+
{Kind: String, Value: "escaped backticks"},
75+
{Kind: String, Value: ""},
76+
{Kind: String, Value: "a`b"},
77+
{Kind: String, Value: "`"},
78+
{Kind: String, Value: "a`"},
79+
{Kind: String, Value: "`b"},
80+
{Kind: String, Value: "`a``b`"},
81+
{Kind: String, Value: "```"},
82+
{Kind: String, Value: "`a``"},
83+
{Kind: String, Value: "``b`"},
84+
{Kind: EOF},
85+
},
86+
},
7187
{
7288
"a and orb().val #.",
7389
[]Token{
@@ -332,6 +348,26 @@ literal not terminated (1:10)
332348
| id "hello
333349
| .........^
334350
351+
id ` + "`" + `hello
352+
literal not terminated (1:10)
353+
| id ` + "`" + `hello
354+
| .........^
355+
356+
id ` + "`" + `hello` + "``" + `
357+
literal not terminated (1:12)
358+
| id ` + "`" + `hello` + "``" + `
359+
| ...........^
360+
361+
id ` + "```" + `hello
362+
literal not terminated (1:12)
363+
| id ` + "```" + `hello
364+
| ...........^
365+
366+
id ` + "`" + `hello` + "``" + ` world
367+
literal not terminated (1:18)
368+
| id ` + "`" + `hello` + "``" + ` world
369+
| .................^
370+
335371
früh ♥︎
336372
unrecognized character: U+2665 '♥' (1:6)
337373
| früh ♥︎

0 commit comments

Comments
 (0)