diff options
author | 2021-04-23 15:09:56 -0700 | |
---|---|---|
committer | 2021-04-23 15:09:56 -0700 | |
commit | f384465fa7f492a6cfd71a1eef827f02fcd897ee (patch) | |
tree | 0309f117021809c070f05f49fe71970f3072aa3c /src | |
parent | a01148834ca6de0671acad174c508b594bf1d4d0 (diff) | |
download | bun-f384465fa7f492a6cfd71a1eef827f02fcd897ee.tar.gz bun-f384465fa7f492a6cfd71a1eef827f02fcd897ee.tar.zst bun-f384465fa7f492a6cfd71a1eef827f02fcd897ee.zip |
for!
Diffstat (limited to 'src')
-rw-r--r-- | src/js_ast.zig | 4 | ||||
-rw-r--r-- | src/js_parser.zig | 204 | ||||
-rw-r--r-- | src/logger.zig | 8 |
3 files changed, 213 insertions, 3 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig index cdf06f2b2..8d034174c 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -2127,7 +2127,7 @@ pub const S = struct { // This is set to true for automatically-generated expressions that should // not affect tree shaking. For example, calling a function from the runtime // that doesn't have externally-visible side effects. - does_not_affect_tree_shaking: bool, + does_not_affect_tree_shaking: bool = false, }; pub const Comment = struct { text: string }; @@ -2196,7 +2196,7 @@ pub const S = struct { pub const For = struct { // May be a SConst, SLet, SVar, or SExpr - init: StmtNodeIndex, test_: ?ExprNodeIndex, update: ?ExprNodeIndex, body: StmtNodeIndex }; + init: ?StmtNodeIndex = null, test_: ?ExprNodeIndex = null, update: ?ExprNodeIndex = null, body: StmtNodeIndex }; pub const ForIn = struct { // May be a SConst, SLet, SVar, or SExpr diff --git a/src/js_parser.zig b/src/js_parser.zig index 4e40a77f7..a041c9ce6 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -36,6 +36,11 @@ const Op = js_ast.Op; const Scope = js_ast.Scope; const locModuleScope = logger.Loc.Empty; +const ExprOrLetStmt = struct { + stmt_or_expr: js_ast.StmtOrExpr, + decls: []G.Decl = &([_]G.Decl{}), +}; + const Tup = std.meta.Tuple; fn notimpl() noreturn { @@ -1822,7 +1827,149 @@ const P = struct { ); }, .t_for => { - notimpl(); + _ = try p.pushScopeForParsePass(.block, loc); + defer p.popScope(); + + p.lexer.next(); + + // "for await (let x of y) {}" + var isForAwait = p.lexer.isContextualKeyword("await"); + if (isForAwait) { + const await_range = p.lexer.range(); + if (!p.fn_or_arrow_data_parse.allow_await) { + try p.log.addRangeError(p.source, await_range, "Cannot use \"await\" outside an async function"); + isForAwait = false; + } else { + // TODO: improve error handling here + // didGenerateError := p.markSyntaxFeature(compat.ForAwait, awaitRange) + // if p.fnOrArrowDataParse.isTopLevel && !didGenerateError { + // p.topLevelAwaitKeyword = awaitRange + // p.markSyntaxFeature(compat.TopLevelAwait, awaitRange) + // } + } + p.lexer.next(); + } + + p.lexer.expect(.t_open_paren); + + var init_: ?Stmt = null; + var test_: ?Expr = null; + var update: ?Expr = null; + + // "in" expressions aren't allowed here + p.allow_in = false; + + var bad_let_range: ?logger.Range = null; + if (p.lexer.isContextualKeyword("let")) { + bad_let_range = p.lexer.range(); + } + + var decls: []G.Decl = &([_]G.Decl{}); + var init_loc = p.lexer.loc(); + var is_var = false; + switch (p.lexer.token) { + // for (var ) + .t_var => { + is_var = true; + p.lexer.next(); + var stmtOpts = ParseStatementOptions{}; + decls = p.parseAndDeclareDecls(.hoisted, &stmtOpts); + init_ = p.s(S.Local{ .kind = .k_const, .decls = decls }, init_loc); + }, + // for (const ) + .t_const => { + p.lexer.next(); + var stmtOpts = ParseStatementOptions{}; + decls = p.parseAndDeclareDecls(.cconst, &stmtOpts); + init_ = p.s(S.Local{ .kind = .k_const, .decls = decls }, init_loc); + }, + // for (;) + .t_semicolon => {}, + else => { + var stmtOpts = ParseStatementOptions{ .lexical_decl = .allow_all }; + + const res = try p.parseExprOrLetStmt(&stmtOpts); + switch (res.stmt_or_expr) { + .stmt => |stmt| { + bad_let_range = null; + init_ = stmt; + }, + .expr => |expr| { + init_ = p.s(S.SExpr{ + .value = expr, + }, init_loc); + }, + } + }, + } + + // "in" expressions are allowed again + p.allow_in = true; + + // Detect for-of loops + if (p.lexer.isContextualKeyword("of") or isForAwait) { + if (bad_let_range) |r| { + try p.log.addRangeError(p.source, r, "\"let\" must be wrapped in parentheses to be used as an expression here"); + fail(); + } + + if (isForAwait and !p.lexer.isContextualKeyword("of")) { + if (init_) |init_stmt| { + p.lexer.expectedString("\"of\""); + } else { + p.lexer.unexpected(); + } + } + + try p.forbidInitializers(decls, "of", false); + p.lexer.next(); + const value = p.parseExpr(.comma); + p.lexer.expect(.t_close_paren); + var stmtOpts = ParseStatementOptions{}; + const body = p.parseStmt(&stmtOpts) catch unreachable; + return p.s(S.ForOf{ .is_await = isForAwait, .init = init_ orelse unreachable, .value = value, .body = body }, loc); + } + + // Detect for-in loops + if (p.lexer.token == .t_in) { + try p.forbidInitializers(decls, "in", false); + p.lexer.next(); + const value = p.parseExpr(.comma); + p.lexer.expect(.t_close_paren); + var stmtOpts = ParseStatementOptions{}; + const body = p.parseStmt(&stmtOpts) catch unreachable; + return p.s(S.ForIn{ .init = init_ orelse unreachable, .value = value, .body = body }, loc); + } + + // Only require "const" statement initializers when we know we're a normal for loop + if (init_) |init_stmt| { + switch (init_stmt.data) { + .s_local => |local| { + if (local.kind == .k_const) { + try p.requireInitializers(decls); + } + }, + else => {}, + } + } + + p.lexer.expect(.t_semicolon); + if (p.lexer.token != .t_semicolon) { + test_ = p.parseExpr(.lowest); + } + + p.lexer.expect(.t_semicolon); + + if (p.lexer.token != .t_close_paren) { + update = p.parseExpr(.lowest); + } + + var stmtOpts = ParseStatementOptions{}; + const body = p.parseStmt(&stmtOpts) catch unreachable; + return p.s( + S.For{ .init = init_, .test_ = test_, .update = update, .body = body }, + loc, + ); }, .t_import => { notimpl(); @@ -1854,6 +2001,61 @@ const P = struct { return js_ast.Stmt.empty(); } + pub fn forbidInitializers(p: *P, decls: []G.Decl, loop_type: string, is_var: bool) !void { + if (decls.len > 1) { + try p.log.addErrorFmt(p.source, decls[0].binding.loc, p.allocator, "for-{s} loops must have a single declaration", .{loop_type}); + } else if (decls.len == 1) { + if (decls[0].value) |value| { + if (is_var) { + + // This is a weird special case. Initializers are allowed in "var" + // statements with identifier bindings. + return; + } + + try p.log.addErrorFmt(p.source, value.loc, p.allocator, "for-{s} loop variables cannot have an initializer", .{loop_type}); + } + } + } + + pub fn parseExprOrLetStmt(p: *P, opts: *ParseStatementOptions) !ExprOrLetStmt { + var let_range = p.lexer.range(); + var raw = p.lexer.raw(); + + if (p.lexer.token != .t_identifier or !strings.eql(raw, "let")) { + return ExprOrLetStmt{ .stmt_or_expr = js_ast.StmtOrExpr{ .expr = p.parseExpr(.lowest) } }; + } + + p.lexer.next(); + + switch (p.lexer.token) { + .t_identifier, .t_open_bracket, .t_open_brace => { + if (opts.lexical_decl == .allow_all or !p.lexer.has_newline_before or p.lexer.token == .t_open_bracket) { + if (opts.lexical_decl != .allow_all) { + try p.forbidLexicalDecl(let_range.loc); + } + + const decls = p.parseAndDeclareDecls(.other, opts); + return ExprOrLetStmt{ + .stmt_or_expr = js_ast.StmtOrExpr{ + .stmt = p.s(S.Local{ + .kind = .k_let, + .decls = decls, + .is_export = opts.is_export, + }, let_range.loc), + }, + .decls = decls, + }; + } + }, + else => {}, + } + + const ref = p.storeNameInRef(raw) catch unreachable; + const expr = p.e(E.Identifier{ .ref = ref }, let_range.loc); + return ExprOrLetStmt{ .stmt_or_expr = js_ast.StmtOrExpr{ .expr = p.parseExpr(.lowest) } }; + } + pub fn requireInitializers(p: *P, decls: []G.Decl) !void { for (decls) |decl| { if (decl.value) |val| { diff --git a/src/logger.zig b/src/logger.zig index ce57c6172..634064086 100644 --- a/src/logger.zig +++ b/src/logger.zig @@ -162,6 +162,14 @@ pub const Log = struct { }); } + pub fn addErrorFmt(log: *Log, source: ?Source, l: Loc, allocator: *std.mem.Allocator, comptime text: string, args: anytype) !void { + log.errors += 1; + try log.addMsg(Msg{ + .kind = .err, + .data = rangeData(source, Range{ .loc = l }, std.fmt.allocPrint(allocator, text, args) catch unreachable), + }); + } + pub fn addRangeWarning(log: *Log, source: ?Source, r: Range, text: string) !void { log.warnings += 1; try log.addMsg(Msg{ |