Skip to content

Commit efafcdd

Browse files
committed
jsx wip
1 parent c1ed862 commit efafcdd

File tree

4 files changed

+268
-44
lines changed

4 files changed

+268
-44
lines changed

src/js/parser.zig

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ const ParseError = error{
107107
/// Invalid expression or pattern inside '(...)'
108108
InvalidExpressionInsideParens,
109109
// TODO: remove these two errors
110+
JsxExpectedTagName,
110111
JsxNotImplemented,
111112
TypeScriptNotImplemented,
112113
// The parser instance has already been used
@@ -155,15 +156,19 @@ pub const SourceType = enum { script, module };
155156
pub const Config = struct {
156157
/// Whether we're parsing a script or a module.
157158
source_type: SourceType = .module,
158-
/// Enable strict mode by default
159+
/// Enable strict mode in the entire file
159160
strict_mode: bool = false,
160161
/// Enable JSX support
161162
jsx: bool = false,
162163
/// Enable typescript support
163164
typescript: bool = false,
164165
/// Add a special ast node for expressions wrapped in '()'.
165-
/// Parses `(a)` as `parenthesized_expression { identifier }` instead of { identifier }
166+
/// Parses `(a)` as `parenthesized_expression { identifier }` instead of '{ identifier }'
166167
preserve_parens: bool = true,
168+
169+
pub fn isTsx(self: Config) bool {
170+
return self.jsx and self.typescript;
171+
}
167172
};
168173

169174
allocator: std.mem.Allocator,
@@ -596,21 +601,22 @@ fn defaultImportSpecifier(self: *Self) Error!Node.Index {
596601
}
597602

598603
fn importSpecifier(self: *Self) Error!Node.Index {
599-
var name_token: TokenWithId = undefined; // assigned in the 'blk' block
600-
const name = blk: {
604+
const name, const name_token = blk: {
601605
if (self.isAtToken(.string_literal)) {
602-
name_token = try self.next();
603-
break :blk try self.stringLiteralFromToken(name_token.id);
606+
const name_tok = try self.next();
607+
break :blk .{ try self.stringLiteralFromToken(name_tok.id), name_tok };
604608
}
605609

606-
name_token = try self.expectIdOrKeyword();
610+
const name_tok = try self.expectIdOrKeyword();
607611

608612
// `import {foo as bar }` // <- `foo` is an identifier node
609613
// `import { foo }` // <- `foo` is a BINDING identifier node
610-
break :blk try if (self.isAtToken(.kw_as))
611-
self.identifier(name_token.id)
614+
const name_node = try if (self.isAtToken(.kw_as))
615+
self.identifier(name_tok.id)
612616
else
613-
self.bindingIdentifier(name_token.id);
617+
self.bindingIdentifier(name_tok.id);
618+
619+
break :blk .{ name_node, name_tok };
614620
};
615621

616622
if (self.isAtToken(.kw_as) or !self.isIdentifier(name_token.token.tag)) {
@@ -637,6 +643,8 @@ fn importSpecifier(self: *Self) Error!Node.Index {
637643
);
638644
}
639645

646+
/// A string literal or an identifier reference.
647+
/// https://262.ecma-international.org/15.0/index.html#prod-ModuleExportName
640648
fn moduleExportName(self: *Self) Error!Node.Index {
641649
if (self.isAtToken(.string_literal))
642650
return self.stringLiteral();
@@ -964,6 +972,9 @@ fn labeledOrExpressionStatement(self: *Self) Error!Node.Index {
964972
return self.expressionStatement();
965973
}
966974

975+
/// https://262.ecma-international.org/15.0/index.html#prod-LabelledStatement
976+
/// LabeledStatement:
977+
/// LabelIdentifier ':' LabelledItem
967978
fn labeledStatement(self: *Self) Error!Node.Index {
968979
const label = try self.next();
969980
_ = try self.expectToken(.@":");
@@ -988,6 +999,10 @@ fn labeledStatement(self: *Self) Error!Node.Index {
988999
);
9891000
}
9901001

1002+
/// https://262.ecma-international.org/15.0/index.html#prod-LabelledItem
1003+
/// LabelledItem:
1004+
/// Statement
1005+
/// FunctionDeclaration
9911006
fn labeledItem(self: *Self) Error!Node.Index {
9921007
if (self.isAtToken(.kw_function)) {
9931008
const lookahead = try self.lookAhead();
@@ -1498,7 +1513,7 @@ fn forLoopIterator(self: *Self) Error!struct { ForLoopKind, ast.Node.Index } {
14981513
// for (var/let/const x = 0; x < 10; x++)
14991514
// for (var/let/const x in y)
15001515
// for (var/let/const x of y)
1501-
const maybe_decl_kw = blk: {
1516+
const maybe_vardecl_keyword = blk: {
15021517
const curr = self.current.token.tag;
15031518
if (curr == .kw_var or curr == .kw_const) {
15041519
break :blk try self.next();
@@ -1512,7 +1527,7 @@ fn forLoopIterator(self: *Self) Error!struct { ForLoopKind, ast.Node.Index } {
15121527
break :blk null;
15131528
};
15141529

1515-
if (maybe_decl_kw) |decl_kw| {
1530+
if (maybe_vardecl_keyword) |decl_kw| {
15161531
var loop_kind = ForLoopKind.basic;
15171532
const iterator = try self.completeVarDeclLoopIterator(
15181533
lpar.id,
@@ -1976,6 +1991,11 @@ fn catchClause(self: *Self) Error!Node.Index {
19761991
}, catch_kw.id, self.nodeSpan(body).end);
19771992
}
19781993

1994+
/// https://262.ecma-international.org/15.0/index.html#prod-CatchParameter
1995+
///
1996+
/// CatchParameter:
1997+
/// BindingIdentifier
1998+
/// BindingPattern
19791999
fn catchParameter(self: *Self) Error!?Node.Index {
19802000
if (!self.isAtToken(.@"(")) return null;
19812001
_ = try self.nextToken();
@@ -2033,12 +2053,8 @@ fn caseBlock(self: *Self) Error!ast.SubRange {
20332053
.kw_case => break :blk try self.caseClause(),
20342054
.kw_default => {
20352055
if (saw_default) {
2036-
try self.emitDiagnostic(
2037-
self.current.token.startCoord(self.source),
2038-
"Multiple 'default' clauses are not allowed in a switch statement",
2039-
.{},
2040-
);
2041-
return Error.MultipleDefaults;
2056+
// TODO: emit this error and keep going. No need to stop parsing.
2057+
return self.errorOnToken(self.current.token, ParseError.MultipleDefaults);
20422058
}
20432059

20442060
saw_default = true;
@@ -2521,6 +2537,7 @@ fn errorMessage(err: ParseError) []const u8 {
25212537
ParseError.ExpectedImportSpecifier => "Expected an import specifier, '*', or file path",
25222538
ParseError.InvalidExpressionInsideParens => "Invalid expression or pattern inside (...)",
25232539
ParseError.RestElementNotLastInParams => "Rest element must be the last item in a function parameter list",
2540+
ParseError.JsxExpectedTagName => "Expected a tag name after '<' in JSX code",
25242541
};
25252542
}
25262543

@@ -2804,15 +2821,35 @@ fn expect2(self: *Self, tag1: Token.Tag, tag2: Token.Tag) Error!TokenWithId {
28042821
return Error.UnexpectedToken;
28052822
}
28062823

2807-
/// Consume the next token from the lexer, skipping all comments.
2824+
/// Consume the next token from the lexer, skipping all comments and whitespaces.
2825+
/// Returns the CURRENT token which was consumed in the previous call to 'next',
2826+
/// and advances the pointer to the next token.
28082827
fn next(self: *Self) Error!TokenWithId {
28092828
var next_token = try self.tokenizer.next();
2810-
while (next_token.tag == .comment or
2811-
next_token.tag == .whitespace) : (next_token = try self.tokenizer.next())
2812-
{
2829+
while (next_token.tag == .comment or next_token.tag == .whitespace) {
2830+
try self.saveToken(next_token);
2831+
next_token = try self.tokenizer.next();
2832+
}
2833+
2834+
return self.advanceToToken(next_token);
2835+
}
2836+
2837+
/// Consume the next JSX token from the lexer, skipping all comments and whitespaces.
2838+
///
2839+
/// [is_inside_jsx_tags]: Whether we are currently inside JSX tags (i.e. between '<' and '>').
2840+
fn nextJsx(self: *Self, is_inside_jsx_tags: bool) Error!TokenWithId {
2841+
var next_token = try self.tokenizer.nextJsx(is_inside_jsx_tags);
2842+
while (next_token.tag == .comment or next_token.tag == .whitespace) {
28132843
try self.saveToken(next_token);
2844+
next_token = try self.tokenizer.nextJsx(is_inside_jsx_tags);
28142845
}
28152846

2847+
return self.advanceToToken(next_token);
2848+
}
2849+
2850+
/// Set [next_token] as the current token, and update `self.current`, and `self.prev_token_line`
2851+
/// Returns the newly saved token along with its ID.
2852+
fn advanceToToken(self: *Self, next_token: Token) error{OutOfMemory}!TokenWithId {
28162853
try self.saveToken(next_token);
28172854

28182855
const current = self.current.token;
@@ -2821,6 +2858,7 @@ fn next(self: *Self) Error!TokenWithId {
28212858
self.current.token = next_token;
28222859
self.current.id = @enumFromInt(self.tokens.items.len - 1);
28232860
self.prev_token_line = current.line;
2861+
28242862
return TokenWithId{ .token = current, .id = current_id };
28252863
}
28262864

@@ -2847,7 +2885,7 @@ inline fn nextToken(self: *Self) Error!Token {
28472885
/// without consuming it, or advancing in the source.
28482886
///
28492887
/// NOTE: The token returned by this function should only be used for
2850-
/// inspection, and never added to the AST.
2888+
/// inspection, and should not be added to the AST.
28512889
fn lookAhead(self: *Self) Error!Token {
28522890
const line = self.tokenizer.line;
28532891
const index = self.tokenizer.index;
@@ -2862,7 +2900,7 @@ fn lookAhead(self: *Self) Error!Token {
28622900
return next_token;
28632901
}
28642902

2865-
inline fn peek(self: *Self) Token {
2903+
fn peek(self: *Self) Token {
28662904
return self.current.token;
28672905
}
28682906

@@ -4288,7 +4326,10 @@ fn primaryExpression(self: *Self) Error!Node.Index {
42884326
.@"(" => return self.groupingExprOrArrowParameters(&token),
42894327
.kw_async => return self.asyncExpression(&token),
42904328
.kw_function => return self.functionExpression(token.id, .{}),
4291-
else => {},
4329+
.@"<" => {},
4330+
else => {
4331+
// TODO: JSX
4332+
},
42924333
}
42934334

42944335
if (self.isKeywordIdentifier(token.token.tag)) {
@@ -4312,6 +4353,25 @@ fn primaryExpression(self: *Self) Error!Node.Index {
43124353
return Error.UnexpectedToken;
43134354
}
43144355

4356+
fn jsxExpression(self: *Self) Error!void {
4357+
assert(self.current.token.tag == .@"<");
4358+
_ = try self.nextJsx(true);
4359+
4360+
if (self.isAtToken(.@">")) {
4361+
// TODO: implement jsx fragment
4362+
unreachable;
4363+
}
4364+
4365+
const jsx_identifier = try self.nextJsx(true);
4366+
if (jsx_identifier.token.tag != .jsx_identifier) {
4367+
return self.errorOnToken(jsx_identifier.token, ParseError.JsxExpectedTagName);
4368+
}
4369+
}
4370+
4371+
fn jsxFragment(_: *Self) Error!void {}
4372+
fn jsxElement(_: *Self) Error!void {}
4373+
fn jsxClosingElement(_: *Self) Error!void {}
4374+
43154375
/// Check whether a numeric literal is valid in strict mode.
43164376
/// Decimals and legacy octal literals are not allowed (e.g: 01, 09, 023127, etc.).
43174377
fn isValidStrictModeNumber(self: *const Self, token: *const Token) bool {

src/js/token.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ pub const JsTokenTag = enum(u32) {
199199
kw_from = 145 | Mask.ContextualKeyword,
200200
kw_of = 146 | Mask.ContextualKeyword,
201201

202+
jsx_text = 147,
203+
202204
eof = 149,
203205

204206
pub fn is(self: JsTokenTag, mask: u32) bool {

0 commit comments

Comments
 (0)