Skip to content

Commit 4d199e7

Browse files
Refactor parsing of string template literals. (rescript-lang#94)
The previous approach put the scanner in "Template" mode when the parser was going to parse template literals. This resulted in some very awkward code; upon scanning a token there was logic checking whether we were in "Template" mode. Every non-template literal token would pay for this extra branch… The new parsing strategy works differently: when the parser needs to parse template literals, it just asks the scanner immediately for a template literal token. This is both more performant an easier to reason about. Template literals are a different language, it makes sense to split this into a separate scanning function.
1 parent 684dcc4 commit 4d199e7

File tree

7 files changed

+53
-54
lines changed

7 files changed

+53
-54
lines changed

syntax/src/napkin_core.ml

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,9 +2093,10 @@ and parseTemplateExpr ?(prefix="js") p =
20932093
let op = Location.mknoloc (Longident.Lident "^") in
20942094
Ast_helper.Exp.ident op
20952095
in
2096-
let rec loop acc p =
2096+
let rec parseParts acc =
20972097
let startPos = p.Parser.startPos in
2098-
match p.Parser.token with
2098+
Parser.nextTemplateLiteralToken p;
2099+
match p.token with
20992100
| TemplateTail txt ->
21002101
Parser.next p;
21012102
let loc = mkLoc startPos p.prevEndPos in
@@ -2111,8 +2112,6 @@ and parseTemplateExpr ?(prefix="js") p =
21112112
let loc = mkLoc startPos p.prevEndPos in
21122113
let expr = parseExprBlock p in
21132114
let fullLoc = mkLoc startPos p.prevEndPos in
2114-
Scanner.setTemplateMode p.scanner;
2115-
Parser.expect Rbrace p;
21162115
let txt = if p.mode = ParseForTypeChecker then parseTemplateStringLiteral txt else txt in
21172116
let str = Ast_helper.Exp.constant ~loc (Pconst_string(txt, Some prefix)) in
21182117
let next =
@@ -2123,27 +2122,23 @@ and parseTemplateExpr ?(prefix="js") p =
21232122
Ast_helper.Exp.apply ~loc:fullLoc hiddenOperator
21242123
[Nolabel, a; Nolabel, expr]
21252124
in
2126-
loop next p
2127-
| token ->
2128-
Parser.err p (Diagnostics.unexpected token p.breadcrumbs);
2129-
acc
2125+
parseParts next
2126+
| token ->
2127+
Parser.err p (Diagnostics.unexpected token p.breadcrumbs);
2128+
Ast_helper.Exp.constant (Pconst_string("", None))
21302129
in
2131-
Scanner.setTemplateMode p.scanner;
2132-
Parser.expect Backtick p;
2133-
let startPos = p.Parser.startPos in
2134-
match p.Parser.token with
2130+
let startPos = p.startPos in
2131+
Parser.nextTemplateLiteralToken p;
2132+
match p.token with
21352133
| TemplateTail txt ->
2136-
let loc = mkLoc startPos p.endPos in
21372134
Parser.next p;
21382135
let txt = if p.mode = ParseForTypeChecker then parseTemplateStringLiteral txt else txt in
2139-
Ast_helper.Exp.constant ~loc (Pconst_string(txt, Some prefix))
2136+
Ast_helper.Exp.constant ~loc:(mkLoc startPos p.prevEndPos) (Pconst_string(txt, Some prefix))
21402137
| TemplatePart txt ->
2141-
let constantLoc = mkLoc startPos p.endPos in
21422138
Parser.next p;
2139+
let constantLoc = mkLoc startPos p.prevEndPos in
21432140
let expr = parseExprBlock p in
21442141
let fullLoc = mkLoc startPos p.prevEndPos in
2145-
Scanner.setTemplateMode p.scanner;
2146-
Parser.expect Rbrace p;
21472142
let txt = if p.mode = ParseForTypeChecker then parseTemplateStringLiteral txt else txt in
21482143
let str = Ast_helper.Exp.constant ~loc:constantLoc (Pconst_string(txt, Some prefix)) in
21492144
let next =
@@ -2152,7 +2147,7 @@ and parseTemplateExpr ?(prefix="js") p =
21522147
else
21532148
expr
21542149
in
2155-
loop next p
2150+
parseParts next
21562151
| token ->
21572152
Parser.err p (Diagnostics.unexpected token p.breadcrumbs);
21582153
Ast_helper.Exp.constant (Pconst_string("", None))
@@ -2643,7 +2638,7 @@ and parseBracedOrRecordExpr p =
26432638
Parser.expect Rbrace p;
26442639
let loc = mkLoc startPos p.prevEndPos in
26452640
let braces = makeBracesAttr loc in
2646-
{expr with pexp_attributes = braces::expr.pexp_attributes}
2641+
{expr with Parsetree.pexp_attributes = braces::expr.Parsetree.pexp_attributes}
26472642
| Rbrace ->
26482643
Parser.next p;
26492644
let loc = mkLoc startPos p.prevEndPos in

syntax/src/napkin_parser.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ let rec next ?prevEndPos p =
6262
p.startPos <- startPos;
6363
p.endPos <- endPos
6464

65+
let nextTemplateLiteralToken p =
66+
let (startPos, endPos, token) = Scanner.scanTemplateLiteralToken p.scanner in
67+
p.token <- token;
68+
p.prevEndPos <- p.endPos;
69+
p.startPos <- startPos;
70+
p.endPos <- endPos
71+
6572
let checkProgress ~prevEndPos ~result p =
6673
if p.endPos == prevEndPos
6774
then None

syntax/src/napkin_parser.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ val make: ?mode:mode -> ?line:int -> string -> string -> t
2828
val expect: ?grammar:Grammar.t -> Token.t -> t -> unit
2929
val optional: t -> Token.t -> bool
3030
val next: ?prevEndPos:Lexing.position -> t -> unit
31+
val nextTemplateLiteralToken: t -> unit
3132
val lookahead: t -> (t -> 'a) -> 'a
3233
val err:
3334
?startPos:Lexing.position ->

syntax/src/napkin_scanner.ml

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ let inJsxMode scanner = match scanner.mode with
4343
| Jsx::_ -> true
4444
| _ -> false
4545

46-
let inTemplateMode scanner = match scanner.mode with
47-
| Template::_ -> true
48-
| _ -> false
49-
5046
let position scanner = Lexing.{
5147
pos_fname = scanner.filename;
5248
(* line number *)
@@ -405,43 +401,42 @@ let scanMultiLineComment scanner =
405401
(Bytes.sub_string scanner.src startOff (scanner.offset - 2 - startOff))
406402
)
407403

408-
let scanTemplate scanner =
404+
let scanTemplateLiteralToken scanner =
409405
let startOff = scanner.offset in
406+
407+
(* if starting } here, consume it *)
408+
if scanner.ch == CharacterCodes.rbrace then (
409+
next scanner
410+
);
410411
let startPos = position scanner in
411412

412413
let rec scan () =
413414
if scanner.ch == CharacterCodes.eof then (
414415
let endPos = position scanner in
415416
scanner.err ~startPos ~endPos Diagnostics.unclosedTemplate;
416-
popMode scanner Template;
417417
Token.TemplateTail(
418-
Bytes.sub_string scanner.src startOff (scanner.offset - 2 - startOff)
418+
Bytes.sub_string scanner.src startOff (scanner.offset - 1 - startOff)
419419
)
420-
)
421-
else if scanner.ch == CharacterCodes.backslash then (
420+
) else if scanner.ch == CharacterCodes.backtick then (
422421
next scanner;
423-
if scanner.ch == CharacterCodes.backtick
422+
Token.TemplateTail(
423+
Bytes.sub_string scanner.src startOff (scanner.offset - 1 - startOff)
424+
)
425+
) else if scanner.ch == CharacterCodes.dollar &&
426+
(peek scanner) == CharacterCodes.lbrace then (
427+
next scanner; (* consume $ *)
428+
next scanner; (* consume { *)
429+
let contents =
430+
Bytes.sub_string scanner.src startOff (scanner.offset - 2 - startOff)
431+
in
432+
Token.TemplatePart contents
433+
) else if scanner.ch == CharacterCodes.backslash then (
434+
next scanner;
435+
if scanner.ch == CharacterCodes.backtick
424436
|| scanner.ch == CharacterCodes.backslash
425437
|| scanner.ch == CharacterCodes.dollar
426438
then next scanner;
427439
scan()
428-
) else if scanner.ch == CharacterCodes.backtick then (
429-
next scanner;
430-
let contents =
431-
Bytes.sub_string scanner.src startOff (scanner.offset - 1 - startOff)
432-
in
433-
popMode scanner Template;
434-
Token.TemplateTail contents
435-
) else if scanner.ch == CharacterCodes.dollar &&
436-
peek scanner == CharacterCodes.lbrace
437-
then (
438-
next scanner; (* consume $ *)
439-
next scanner; (* consume { *)
440-
let contents =
441-
Bytes.sub_string scanner.src startOff (scanner.offset - 2 - startOff)
442-
in
443-
popMode scanner Template;
444-
Token.TemplatePart contents
445440
) else (
446441
if CharacterCodes.isLineBreak scanner.ch then (
447442
scanner.lineOffset <- scanner.offset + 1;
@@ -451,15 +446,15 @@ let scanTemplate scanner =
451446
scan()
452447
)
453448
in
454-
scan()
449+
let token = scan() in
450+
let endPos = position scanner in
451+
(startPos, endPos, token)
455452

456453
let rec scan scanner =
457-
if not (inTemplateMode scanner) then skipWhitespace scanner;
454+
skipWhitespace scanner;
458455
let startPos = position scanner in
459456
let ch = scanner.ch in
460-
let token = if inTemplateMode scanner then
461-
scanTemplate scanner
462-
else if ch == CharacterCodes.underscore then (
457+
let token = if ch == CharacterCodes.underscore then (
463458
let nextCh = peek scanner in
464459
if nextCh == CharacterCodes.underscore || CharacterCodes.isDigit nextCh || CharacterCodes.isLetter nextCh then
465460
scanIdentifier scanner

syntax/src/napkin_scanner.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ val setDiamondMode: t -> unit
2929
val popMode: t -> mode -> unit
3030

3131
val reconsiderLessThan: t -> Napkin_token.t
32+
33+
val scanTemplateLiteralToken: t -> (Lexing.position * Lexing.position * Napkin_token.t)

syntax/src/napkin_token.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
module Comment = Napkin_comment
32
module CharacterCodes = Napkin_character_codes
43

@@ -221,4 +220,4 @@ let isKeywordTxt str =
221220
try let _ = keywordTable str in true with
222221
| Not_found -> false
223222

224-
let catch = Lident "catch"
223+
let catch = Lident "catch"

syntax/tests/parsing/recovery/string/__snapshots__/parse.spec.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
exports[`eof.js 1`] = `"let x = \\"eof here\\""`;
44

5-
exports[`es6template.js 1`] = `"let x = ({js|this contains |js} ^ foo) ^ {js|, missing closin|js}"`;
5+
exports[`es6template.js 1`] = `"let x = ({js|this contains |js} ^ foo) ^ {js|, missing closing|js}"`;
66

77
exports[`unclosed.js 1`] = `
88
"let x = \\"unclosed\\"

0 commit comments

Comments
 (0)