Skip to content

Commit 365db47

Browse files
committed
Add escape sequence parsing
1 parent b94ab1c commit 365db47

6 files changed

Lines changed: 62 additions & 3 deletions

File tree

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const mamlLanguage = LRLanguage.define({
1717
"PropertyName/Identifier": t.propertyName,
1818
"PropertyName/String": t.propertyName,
1919
String: t.string,
20+
Escape: t.escape,
2021
RawString: t.special(t.string),
2122
Number: t.number,
2223
"True False": t.bool,

src/syntax.grammar

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ value {
1515
Null
1616
}
1717

18+
String { '"' (stringContent | Escape)* '"' }
19+
1820
Object { "{" (Property ","?)* "}" }
1921

2022
Array { "[" (value ","?)* "]" }
@@ -27,17 +29,17 @@ True { @extend<Identifier, "true"> }
2729
False { @extend<Identifier, "false"> }
2830
Null { @extend<Identifier, "null"> }
2931

32+
@external tokens stringTokenizer from "./tokens" { stringContent, Escape }
33+
3034
@tokens {
3135
space { $[ \t\n\r]+ }
3236

3337
LineComment { "#" ![\n\r]* }
3438

35-
@precedence { RawString, String, Number, Identifier }
39+
@precedence { RawString, Number, Identifier }
3640

3741
RawString { '"""' (![\"] | '"' ![\"] | '""' ![\"])* '"""' }
3842

39-
String { '"' (![\"\\\n\r] | "\\" _)* '"' }
40-
4143
Number { "-"? ("0" | $[1-9] $[0-9]*) ("." $[0-9]+)? ($[eE] $[+\-]? $[0-9]+)? }
4244

4345
Identifier { $[A-Za-z0-9_\-]+ }

src/syntax.grammar.terms.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export declare const stringContent: number
2+
export declare const Escape: number

src/tokens.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {ExternalTokenizer} from "@lezer/lr"
2+
import {stringContent, Escape} from "./syntax.grammar.terms"
3+
4+
const Backslash = 92 as number, Quote = 34 as number, LF = 10 as number, CR = 13 as number,
5+
uChar = 117 as number, LBrace = 123 as number, RBrace = 125 as number
6+
7+
function isHex(ch: number) {
8+
return (ch >= 48 && ch <= 57) || (ch >= 65 && ch <= 70) || (ch >= 97 && ch <= 102)
9+
}
10+
11+
export const stringTokenizer = new ExternalTokenizer((input) => {
12+
if (input.next === Backslash) {
13+
input.advance()
14+
if (input.next === uChar) {
15+
input.advance()
16+
if (input.next === LBrace) {
17+
input.advance()
18+
while (isHex(input.next)) input.advance()
19+
if (input.next === RBrace) input.advance()
20+
}
21+
} else if (input.next >= 0) {
22+
input.advance()
23+
}
24+
input.acceptToken(Escape)
25+
} else if (input.next !== Quote && input.next !== LF && input.next !== CR && input.next >= 0) {
26+
while (input.next !== Quote && input.next !== Backslash &&
27+
input.next !== LF && input.next !== CR && input.next >= 0) {
28+
input.advance()
29+
}
30+
input.acceptToken(stringContent)
31+
}
32+
}, {contextual: true})

test/test.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,22 @@ describe("maml parser", () => {
141141
it("parses string with escape sequences", () => {
142142
const nodes = topNodes('"hello\\nworld"')
143143
expect(nodes).toContain("String")
144+
expect(nodes).toContain("Escape")
145+
})
146+
147+
it("parses unicode escape sequence", () => {
148+
const nodes = topNodes('"\\u{263A}"')
149+
expect(nodes).toContain("Escape")
150+
})
151+
152+
it("parses all compact escape sequences", () => {
153+
const input = '"\\n\\r\\t\\\\\\"\\/"'
154+
const tree = parse(input)
155+
let escapeCount = 0
156+
tree.cursor().iterate((node) => {
157+
if (node.name === "Escape") escapeCount++
158+
})
159+
expect(escapeCount).toBe(6)
144160
})
145161

146162
it("parses raw string with quotes inside", () => {

vitest.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {defineConfig} from "vitest/config"
2+
import {lezer} from "@lezer/generator/rollup"
3+
4+
export default defineConfig({
5+
plugins: [lezer()],
6+
})

0 commit comments

Comments
 (0)