aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js_ast.zig1210
-rw-r--r--src/js_parser.zig460
-rw-r--r--src/logger.zig32
-rw-r--r--src/string_immutable.zig8
4 files changed, 1296 insertions, 414 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig
index cf476bc54..4bcc945a3 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -6,25 +6,33 @@ usingnamespace @import("ast/base.zig");
const ImportRecord = @import("import_record.zig").ImportRecord;
-// I REALLY DID NOT WANT THESE TO BE POINTERS
-// Unfortunately, this appears to be a language limitation of Zig.
-// The problem was this:
-// - Nesting any of Expr, Binding, or Stmt cause a cyclic dependency
-//
-// If the performance seems inadaquete with pointers, we can switch away from
-// what esbuild is doing. esbuild seems to mostly copy from Go's AST source
-// Though it's also possible that `Stmt` and `Expr` are just really common words for parsing languages.
-// One idea was to use a set of three arrays
-// - stmt
-// - binding
-// - expr
-// then, instead of pointers, it would store an index relative to the root
-// these numbers could l ikely be u16, so half the bits.
-// That approach really complicates stuff though
-// If it was easy to do "builders" in Zig then I think it would work.
-pub const BindingNodeIndex = *Binding;
-pub const StmtNodeIndex = *Stmt;
-pub const ExprNodeIndex = *Expr;
+// There are three types.
+// 1. Expr (expression)
+// 2. Stmt (statement)
+// 3. Binding
+// Q: "What's the difference between an expression and a statement?"
+// A: > Expression: Something which evaluates to a value. Example: 1+2/x
+// > Statement: A line of code which does something. Example: GOTO 100
+// > https://stackoverflow.com/questions/19132/expression-versus-statement/19224#19224
+
+// Expr, Binding, and Stmt each wrap a Data:
+// Data is where the actual data where the node lives.
+// There are four possible versions of this structure:
+// [ ] 1. *Expr, *Stmt, *Binding
+// [ ] 1a. *Expr, *Stmt, *Binding something something dynamic dispatch
+// [ ] 2. *Data
+// [x] 3. Data.(*) (The union value in Data is a pointer)
+// I chose #3 mostly for code simplification -- sometimes, the data is modified in-place.
+// But also it uses the least memory.
+// Since Data is a union, the size in bytes of Data is the max of all types
+// So with #1 or #2, if S.Function consumes 768 bits, that means Data must be >= 768 bits
+// Which means "true" in codenow takes up over 768 bits, probably more than what v8 spends
+// With this approach, Data is the size of a pointer. The value of the type decides the size.
+// It's not really clear which approach is best without benchmarking it.
+
+pub const BindingNodeIndex = Binding;
+pub const StmtNodeIndex = Stmt;
+pub const ExprNodeIndex = Expr;
pub const ExprNodeList = []Expr;
pub const StmtNodeList = []Stmt;
@@ -43,11 +51,19 @@ pub const BindingNodeList = []Binding;
// be an array of arrays indexed first by source index, then by inner index.
// The maps can be merged quickly by creating a single outer array containing
// all inner arrays from all parsed files.
-pub const Ref = struct {
- source_index: ?u32 = null,
- inner_index: u32,
+pub const Ref = packed struct {
+ source_index: Ref.Int = std.math.maxInt(Ref.Int),
+ inner_index: Ref.Int,
+
+ const Int = u32;
+ const None = Ref{ .inner_index = std.math.maxInt(Ref.Int) };
+ pub fn isNull(self: *const Ref) bool {
+ return self.source_index == std.math.maxInt(Ref.Int) and self.inner_index == std.math.maxInt(Ref.Int);
+ }
- const None = Ref{ .source_index = null, .inner_index = std.math.maxInt(u32) };
+ pub fn isSourceNull(self: *const Ref) bool {
+ return self.source_index == std.math.maxInt(Ref.Int);
+ }
};
pub const ImportItemStatus = packed enum {
@@ -62,11 +78,41 @@ pub const ImportItemStatus = packed enum {
pub const LocRef = struct { loc: logger.Loc, ref: ?Ref };
+pub const Flags = struct {
+
+ // Instead of 4 bytes for booleans, we can store it in 4 bits
+ // It will still round up to 1 byte. But that's 3 bytes less!
+ pub const Property = packed struct {
+ is_computed: bool = false,
+ is_method: bool = false,
+ is_static: bool = false,
+ was_shorthand: bool = false,
+ is_spread: bool = false,
+
+ const None = Flags.Property{};
+ };
+
+ pub const Function = packed struct {
+ is_async: bool = false,
+ is_generator: bool = false,
+ has_rest_arg: bool = false,
+ has_if_scope: bool = false,
+
+ // This is true if the function is a method
+ is_unique_formal_parameters: bool = false,
+
+ // Only applicable to function statements.
+ is_export: bool = false,
+
+ const None = Flags.Function{};
+ };
+};
+
pub const Binding = struct {
loc: logger.Loc,
data: B,
- pub const Tag = enum {
+ pub const Tag = packed enum {
b_identifier,
b_array,
b_property,
@@ -76,19 +122,19 @@ pub const Binding = struct {
pub fn init(t: anytype, loc: logger.Loc) Binding {
switch (@TypeOf(t)) {
- B.Identifier => {
+ *B.Identifier => {
return Binding{ .loc = loc, .data = B{ .b_identifier = t } };
},
- B.Array => {
+ *B.Array => {
return Binding{ .loc = loc, .data = B{ .b_array = t } };
},
- B.Property => {
+ *B.Property => {
return Binding{ .loc = loc, .data = B{ .b_property = t } };
},
- B.Object => {
+ *B.Object => {
return Binding{ .loc = loc, .data = B{ .b_object = t } };
},
- B.Missing => {
+ *B.Missing => {
return Binding{ .loc = loc, .data = B{ .b_missing = t } };
},
else => {
@@ -96,38 +142,60 @@ pub const Binding = struct {
},
}
}
+
+ pub fn alloc(allocator: *std.mem.Allocator, t: anytype, loc: logger.Loc) Binding {
+ switch (@TypeOf(t)) {
+ B.Identifier => {
+ var data = allocator.create(B.Identifier) catch unreachable;
+ data.* = t;
+ return Binding{ .loc = loc, .data = B{ .b_identifier = data } };
+ },
+ B.Array => {
+ var data = allocator.create(B.Array) catch unreachable;
+ data.* = t;
+ return Binding{ .loc = loc, .data = B{ .b_array = data } };
+ },
+ B.Property => {
+ var data = allocator.create(B.Property) catch unreachable;
+ data.* = t;
+ return Binding{ .loc = loc, .data = B{ .b_property = data } };
+ },
+ B.Object => {
+ var data = allocator.create(B.Object) catch unreachable;
+ data.* = t;
+ return Binding{ .loc = loc, .data = B{ .b_object = data } };
+ },
+ B.Missing => {
+ var data = allocator.create(B.Missing) catch unreachable;
+ data.* = t;
+ return Binding{ .loc = loc, .data = B{ .b_missing = data } };
+ },
+ else => {
+ @compileError("Invalid type passed to Binding.alloc");
+ },
+ }
+ }
};
pub const B = union(Binding.Tag) {
- b_identifier: B.Identifier,
- b_array: B.Array,
- b_property: B.Property,
- b_object: B.Object,
- b_missing: B.Missing,
+ b_identifier: *B.Identifier,
+ b_array: *B.Array,
+ b_property: *B.Property,
+ b_object: *B.Object,
+ b_missing: *B.Missing,
pub const Identifier = struct {
ref: Ref,
};
pub const Property = struct {
- pub const Kind = enum {
- normal,
- get,
- set,
- spread,
- };
-
+ flags: Flags.Property = Flags.Property.None,
key: ExprNodeIndex,
- value: ?BindingNodeIndex = null,
- kind: Kind = Kind.normal,
- initializer: ?ExprNodeIndex,
- is_computed: bool = false,
- is_method: bool = false,
- is_static: bool = false,
- was_shorthand: bool = false,
+ value: BindingNodeIndex,
+ default_value: ?ExprNodeIndex = null,
};
- pub const Object = struct { properties: []Property };
+ pub const Object = struct { properties: []Property, is_single_line: bool = false };
pub const Array = struct {
items: []ArrayBinding,
@@ -166,11 +234,11 @@ pub const G = struct {
};
pub const Class = struct {
- class_keyword: logger.Range,
+ class_keyword: logger.Range = logger.Range.None,
ts_decorators: ?ExprNodeList = null,
- name: logger.Loc,
+ name: logger.Loc = logger.Loc.Empty,
extends: ?ExprNodeIndex = null,
- body_loc: logger.Loc,
+ body_loc: logger.Loc = logger.Loc.Empty,
properties: []Property = &([_]Property{}),
};
@@ -194,11 +262,15 @@ pub const G = struct {
// class Foo { a = 1 }
//
initializer: ?ExprNodeIndex = null,
- kind: B.Property.Kind,
- is_computed: bool = false,
- is_method: bool = false,
- is_static: bool = false,
- was_shorthand: bool = false,
+ kind: Kind = Kind.normal,
+ flags: Flags.Property = Flags.Property.None,
+
+ pub const Kind = packed enum {
+ normal,
+ get,
+ set,
+ spread,
+ };
};
pub const FnBody = struct {
@@ -213,13 +285,7 @@ pub const G = struct {
body: ?FnBody = null,
arguments_ref: ?Ref = null,
- is_async: bool = false,
- is_generator: bool = false,
- has_rest_arg: bool = false,
- has_if_scope: bool = false,
-
- // This is true if the function is a method
- is_unique_formal_parameters: bool = false,
+ flags: Flags.Function = Flags.Function.None,
};
pub const Arg = struct {
@@ -487,7 +553,7 @@ pub const Symbol = struct {
}
};
-pub const OptionalChain = packed enum {
+pub const OptionalChain = packed enum(u2) {
// "a?.b"
start,
@@ -504,7 +570,7 @@ pub const E = struct {
is_parenthesized: bool = false,
};
- pub const Unary = packed struct {
+ pub const Unary = struct {
op: Op.Code,
value: ExprNodeIndex,
};
@@ -597,7 +663,7 @@ pub const E = struct {
pub const Function = struct { func: G.Fn };
- pub const Identifier = struct {
+ pub const Identifier = packed struct {
ref: Ref = Ref.None,
// If we're inside a "with" statement, this identifier may be a property
@@ -636,7 +702,7 @@ pub const E = struct {
// "{x}" shorthand syntax wasn't aware that the "x" in this case is actually
// "{x: importedNamespace.x}". This separate type forces code to opt-in to
// doing this instead of opt-out.
- pub const ImportIdentifier = struct {
+ pub const ImportIdentifier = packed struct {
ref: Ref,
// If true, this was originally an identifier expression such as "foo". If
@@ -737,178 +803,103 @@ pub const Stmt = struct {
data: Data,
pub fn empty() Stmt {
- return Stmt.init(S.Empty{}, logger.Loc.Empty);
+ return Stmt.init(&Stmt.None, logger.Loc.Empty);
}
- pub fn init(t: anytype, loc: logger.Loc) Stmt {
- switch (@TypeOf(t)) {
+ var None = S.Empty{};
+
+ pub fn init(st: anytype, loc: logger.Loc) Stmt {
+ if (@typeInfo(@TypeOf(st)) != .Pointer) {
+ @compileError("Stmt.init needs a pointer.");
+ }
+
+ switch (@TypeOf(st.*)) {
S.Block => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_block = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_block = st } };
+ },
+ S.SExpr => {
+ return Stmt{ .loc = loc, .data = Data{ .s_expr = st } };
},
S.Comment => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_comment = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_comment = st } };
},
S.Directive => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_directive = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_directive = st } };
},
S.ExportClause => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_export_clause = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_export_clause = st } };
},
S.Empty => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_empty = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_empty = st } };
},
S.TypeScript => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_type_script = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_type_script = st } };
},
S.Debugger => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_debugger = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_debugger = st } };
},
S.ExportFrom => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_export_from = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_export_from = st } };
},
S.ExportDefault => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_export_default = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_export_default = st } };
},
S.Enum => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_enum = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_enum = st } };
},
S.Namespace => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_namespace = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_namespace = st } };
},
S.Function => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_function = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_function = st } };
},
S.Class => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_class = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_class = st } };
},
S.If => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_if = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_if = st } };
},
S.For => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_for = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_for = st } };
},
S.ForIn => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_for_in = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_for_in = st } };
},
S.ForOf => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_for_of = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_for_of = st } };
},
S.DoWhile => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_do_while = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_do_while = st } };
},
S.While => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_while = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_while = st } };
},
S.With => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_with = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_with = st } };
},
S.Try => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_try = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_try = st } };
},
S.Switch => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_switch = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_switch = st } };
},
S.Import => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_import = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_import = st } };
},
S.Return => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_return = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_return = st } };
},
S.Throw => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_throw = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_throw = st } };
},
S.Local => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_local = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_local = st } };
},
S.Break => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_break = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_break = st } };
},
S.Continue => {
- return Stmt{
- .loc = loc,
- .data = Data{ .s_continue = t },
- };
+ return Stmt{ .loc = loc, .data = Data{ .s_continue = st } };
},
else => {
@compileError("Invalid type in Stmt.init");
@@ -916,7 +907,160 @@ pub const Stmt = struct {
}
}
- pub const Tag = enum {
+ pub fn alloc(allocator: *std.mem.Allocator, origData: anytype, loc: logger.Loc) Stmt {
+ switch (@TypeOf(origData)) {
+ S.Block => {
+ var st = allocator.create(S.Block) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_block = st } };
+ },
+ S.SExpr => {
+ var st = allocator.create(S.SExpr) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_expr = st } };
+ },
+ S.Comment => {
+ var st = allocator.create(S.Comment) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_comment = st } };
+ },
+ S.Directive => {
+ var st = allocator.create(S.Directive) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_directive = st } };
+ },
+ S.ExportClause => {
+ var st = allocator.create(S.ExportClause) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_export_clause = st } };
+ },
+ S.Empty => {
+ var st = allocator.create(S.Empty) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_empty = st } };
+ },
+ S.TypeScript => {
+ var st = allocator.create(S.TypeScript) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_type_script = st } };
+ },
+ S.Debugger => {
+ var st = allocator.create(S.Debugger) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_debugger = st } };
+ },
+ S.ExportFrom => {
+ var st = allocator.create(S.ExportFrom) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_export_from = st } };
+ },
+ S.ExportDefault => {
+ var st = allocator.create(S.ExportDefault) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_export_default = st } };
+ },
+ S.Enum => {
+ var st = allocator.create(S.Enum) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_enum = st } };
+ },
+ S.Namespace => {
+ var st = allocator.create(S.Namespace) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_namespace = st } };
+ },
+ S.Function => {
+ var st = allocator.create(S.Function) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_function = st } };
+ },
+ S.Class => {
+ var st = allocator.create(S.Class) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_class = st } };
+ },
+ S.If => {
+ var st = allocator.create(S.If) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_if = st } };
+ },
+ S.For => {
+ var st = allocator.create(S.For) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_for = st } };
+ },
+ S.ForIn => {
+ var st = allocator.create(S.ForIn) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_for_in = st } };
+ },
+ S.ForOf => {
+ var st = allocator.create(S.ForOf) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_for_of = st } };
+ },
+ S.DoWhile => {
+ var st = allocator.create(S.DoWhile) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_do_while = st } };
+ },
+ S.While => {
+ var st = allocator.create(S.While) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_while = st } };
+ },
+ S.With => {
+ var st = allocator.create(S.With) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_with = st } };
+ },
+ S.Try => {
+ var st = allocator.create(S.Try) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_try = st } };
+ },
+ S.Switch => {
+ var st = allocator.create(S.Switch) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_switch = st } };
+ },
+ S.Import => {
+ var st = allocator.create(S.Import) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_import = st } };
+ },
+ S.Return => {
+ var st = allocator.create(S.Return) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_return = st } };
+ },
+ S.Throw => {
+ var st = allocator.create(S.Throw) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_throw = st } };
+ },
+ S.Local => {
+ var st = allocator.create(S.Local) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_local = st } };
+ },
+ S.Break => {
+ var st = allocator.create(S.Break) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_break = st } };
+ },
+ S.Continue => {
+ var st = allocator.create(S.Continue) catch unreachable;
+ st.* = origData;
+ return Stmt{ .loc = loc, .data = Data{ .s_continue = st } };
+ },
+ else => {
+ @compileError("Invalid type in Stmt.init");
+ },
+ }
+ }
+
+ pub const Tag = packed enum {
s_block,
s_comment,
s_directive,
@@ -945,37 +1089,39 @@ pub const Stmt = struct {
s_local,
s_break,
s_continue,
+ s_expr,
};
pub const Data = union(Tag) {
- s_block: S.Block,
- s_comment: S.Comment,
- s_directive: S.Directive,
- s_export_clause: S.ExportClause,
- s_empty: S.Empty,
- s_type_script: S.TypeScript,
- s_debugger: S.Debugger,
- s_export_from: S.ExportFrom,
- s_export_default: S.ExportDefault,
- s_enum: S.Enum,
- s_namespace: S.Namespace,
- s_function: S.Function,
- s_class: S.Class,
- s_if: S.If,
- s_for: S.For,
- s_for_in: S.ForIn,
- s_for_of: S.ForOf,
- s_do_while: S.DoWhile,
- s_while: S.While,
- s_with: S.With,
- s_try: S.Try,
- s_switch: S.Switch,
- s_import: S.Import,
- s_return: S.Return,
- s_throw: S.Throw,
- s_local: S.Local,
- s_break: S.Break,
- s_continue: S.Continue,
+ s_block: *S.Block,
+ s_expr: *S.SExpr,
+ s_comment: *S.Comment,
+ s_directive: *S.Directive,
+ s_export_clause: *S.ExportClause,
+ s_empty: *S.Empty,
+ s_type_script: *S.TypeScript,
+ s_debugger: *S.Debugger,
+ s_export_from: *S.ExportFrom,
+ s_export_default: *S.ExportDefault,
+ s_enum: *S.Enum,
+ s_namespace: *S.Namespace,
+ s_function: *S.Function,
+ s_class: *S.Class,
+ s_if: *S.If,
+ s_for: *S.For,
+ s_for_in: *S.ForIn,
+ s_for_of: *S.ForOf,
+ s_do_while: *S.DoWhile,
+ s_while: *S.While,
+ s_with: *S.With,
+ s_try: *S.Try,
+ s_switch: *S.Switch,
+ s_import: *S.Import,
+ s_return: *S.Return,
+ s_throw: *S.Throw,
+ s_local: *S.Local,
+ s_break: *S.Break,
+ s_continue: *S.Continue,
};
pub fn caresAboutScope(self: *Stmt) bool {
@@ -998,111 +1144,391 @@ pub const Expr = struct {
loc: logger.Loc,
data: Data,
- pub const Flags = enum { none, ts_decorator };
+ pub const EFlags = enum { none, ts_decorator };
+
+ pub fn init(exp: anytype, loc: logger.Loc) Expr {
+ switch (@TypeOf(exp)) {
+ *E.Array => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_array = exp },
+ };
+ },
+ *E.Unary => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_unary = exp },
+ };
+ },
+ *E.Binary => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_binary = exp },
+ };
+ },
+ *E.This => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_this = exp },
+ };
+ },
+ *E.Boolean => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_boolean = exp },
+ };
+ },
+ *E.Super => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_super = exp },
+ };
+ },
+ *E.Null => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_null = exp },
+ };
+ },
+ *E.Undefined => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_undefined = exp },
+ };
+ },
+ *E.New => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_new = exp },
+ };
+ },
+ *E.NewTarget => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_new_target = exp },
+ };
+ },
+ *E.Function => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_function = exp },
+ };
+ },
+ *E.ImportMeta => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_import_meta = exp },
+ };
+ },
+ *E.Call => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_call = exp },
+ };
+ },
+ *E.Dot => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_dot = exp },
+ };
+ },
+ *E.Index => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_index = exp },
+ };
+ },
+ *E.Arrow => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_arrow = exp },
+ };
+ },
+ *E.Identifier => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_identifier = exp },
+ };
+ },
+ *E.ImportIdentifier => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_import_identifier = exp },
+ };
+ },
+ *E.PrivateIdentifier => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_private_identifier = exp },
+ };
+ },
+ *E.JSXElement => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_jsx_element = exp },
+ };
+ },
+ *E.Missing => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_missing = exp },
+ };
+ },
+ *E.Number => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_number = exp },
+ };
+ },
+ *E.BigInt => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_big_int = exp },
+ };
+ },
+ *E.Object => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_object = exp },
+ };
+ },
+ *E.Spread => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_spread = exp },
+ };
+ },
+ *E.String => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_string = exp },
+ };
+ },
+ *E.TemplatePart => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_template_part = exp },
+ };
+ },
+ *E.Template => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_template = exp },
+ };
+ },
+ *E.RegExp => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_reg_exp = exp },
+ };
+ },
+ *E.Await => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_await = exp },
+ };
+ },
+ *E.Yield => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_yield = exp },
+ };
+ },
+ *E.If => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_if = exp },
+ };
+ },
+ *E.RequireOrRequireResolve => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_require_or_require_resolve = exp },
+ };
+ },
+ *E.Import => {
+ return Expr{
+ .loc = loc,
+ .data = Data{ .e_import = exp },
+ };
+ },
+ else => {
+ @compileError("Expr.init needs a pointer to E.*");
+ },
+ }
+ }
- pub fn init(data: anytype, loc: logger.Loc) Expr {
- switch (@TypeOf(data)) {
+ pub fn alloc(allocator: *std.mem.Allocator, st: anytype, loc: logger.Loc) Expr {
+ switch (@TypeOf(st)) {
E.Array => {
- return Expr{ .loc = loc, .data = Data{ .e_array = data } };
+ var dat = allocator.create(E.Array) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_array = dat } };
},
E.Unary => {
- return Expr{ .loc = loc, .data = Data{ .e_unary = data } };
+ var dat = allocator.create(E.Unary) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_unary = dat } };
},
E.Binary => {
- return Expr{ .loc = loc, .data = Data{ .e_binary = data } };
+ var dat = allocator.create(E.Binary) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_binary = dat } };
+ },
+ E.This => {
+ var dat = allocator.create(E.This) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_this = dat } };
},
E.Boolean => {
- return Expr{ .loc = loc, .data = Data{ .e_boolean = data } };
+ var dat = allocator.create(E.Boolean) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_boolean = dat } };
},
E.Super => {
- return Expr{ .loc = loc, .data = Data{ .e_super = data } };
+ var dat = allocator.create(E.Super) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_super = dat } };
},
E.Null => {
- return Expr{ .loc = loc, .data = Data{ .e_null = data } };
- },
- E.This => {
- return Expr{ .loc = loc, .data = Data{ .e_this = data } };
+ var dat = allocator.create(E.Null) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_null = dat } };
},
E.Undefined => {
- return Expr{ .loc = loc, .data = Data{ .e_undefined = data } };
+ var dat = allocator.create(E.Undefined) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_undefined = dat } };
},
E.New => {
- return Expr{ .loc = loc, .data = Data{ .e_new = data } };
+ var dat = allocator.create(E.New) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_new = dat } };
},
E.NewTarget => {
- return Expr{ .loc = loc, .data = Data{ .e_new_target = data } };
+ var dat = allocator.create(E.NewTarget) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_new_target = dat } };
+ },
+ E.Function => {
+ var dat = allocator.create(E.Function) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_function = dat } };
},
E.ImportMeta => {
- return Expr{ .loc = loc, .data = Data{ .e_import_meta = data } };
+ var dat = allocator.create(E.ImportMeta) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_import_meta = dat } };
},
E.Call => {
- return Expr{ .loc = loc, .data = Data{ .e_call = data } };
+ var dat = allocator.create(E.Call) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_call = dat } };
},
E.Dot => {
- return Expr{ .loc = loc, .data = Data{ .e_dot = data } };
+ var dat = allocator.create(E.Dot) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_dot = dat } };
},
E.Index => {
- return Expr{ .loc = loc, .data = Data{ .e_index = data } };
+ var dat = allocator.create(E.Index) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_index = dat } };
},
E.Arrow => {
- return Expr{ .loc = loc, .data = Data{ .e_arrow = data } };
+ var dat = allocator.create(E.Arrow) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_arrow = dat } };
},
E.Identifier => {
- return Expr{ .loc = loc, .data = Data{ .e_identifier = data } };
+ var dat = allocator.create(E.Identifier) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_identifier = dat } };
},
E.ImportIdentifier => {
- return Expr{ .loc = loc, .data = Data{ .e_import_identifier = data } };
+ var dat = allocator.create(E.ImportIdentifier) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_import_identifier = dat } };
},
E.PrivateIdentifier => {
- return Expr{ .loc = loc, .data = Data{ .e_private_identifier = data } };
+ var dat = allocator.create(E.PrivateIdentifier) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_private_identifier = dat } };
},
E.JSXElement => {
- return Expr{ .loc = loc, .data = Data{ .e_jsx_element = data } };
+ var dat = allocator.create(E.JSXElement) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_jsx_element = dat } };
},
E.Missing => {
- return Expr{ .loc = loc, .data = Data{ .e_missing = data } };
+ var dat = allocator.create(E.Missing) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_missing = dat } };
},
E.Number => {
- return Expr{ .loc = loc, .data = Data{ .e_number = data } };
+ var dat = allocator.create(E.Number) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_number = dat } };
},
E.BigInt => {
- return Expr{ .loc = loc, .data = Data{ .e_big_int = data } };
+ var dat = allocator.create(E.BigInt) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_big_int = dat } };
},
E.Object => {
- return Expr{ .loc = loc, .data = Data{ .e_object = data } };
+ var dat = allocator.create(E.Object) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_object = dat } };
},
E.Spread => {
- return Expr{ .loc = loc, .data = Data{ .e_spread = data } };
+ var dat = allocator.create(E.Spread) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_spread = dat } };
},
E.String => {
- return Expr{ .loc = loc, .data = Data{ .e_string = data } };
+ var dat = allocator.create(E.String) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_string = dat } };
},
E.TemplatePart => {
- return Expr{ .loc = loc, .data = Data{ .e_template_part = data } };
+ var dat = allocator.create(E.TemplatePart) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_template_part = dat } };
},
E.Template => {
- return Expr{ .loc = loc, .data = Data{ .e_template = data } };
+ var dat = allocator.create(E.Template) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_template = dat } };
},
E.RegExp => {
- return Expr{ .loc = loc, .data = Data{ .e_reg_exp = data } };
+ var dat = allocator.create(E.RegExp) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_reg_exp = dat } };
},
E.Await => {
- return Expr{ .loc = loc, .data = Data{ .e_await = data } };
+ var dat = allocator.create(E.Await) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_await = dat } };
},
E.Yield => {
- return Expr{ .loc = loc, .data = Data{ .e_yield = data } };
+ var dat = allocator.create(E.Yield) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_yield = dat } };
},
E.If => {
- return Expr{ .loc = loc, .data = Data{ .e_if = data } };
+ var dat = allocator.create(E.If) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_if = dat } };
},
E.RequireOrRequireResolve => {
- return Expr{ .loc = loc, .data = Data{ .e_require_or_require_resolve = data } };
+ var dat = allocator.create(E.RequireOrRequireResolve) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_require_or_require_resolve = dat } };
},
E.Import => {
- return Expr{ .loc = loc, .data = Data{ .e_import = data } };
- },
- E.Function => {
- return Expr{ .loc = loc, .data = Data{ .e_function = data } };
+ var dat = allocator.create(E.Import) catch unreachable;
+ dat.* = st;
+ return Expr{ .loc = loc, .data = Data{ .e_import = dat } };
},
else => {
@compileError("Invalid type passed to Expr.init");
@@ -1110,7 +1536,7 @@ pub const Expr = struct {
}
}
- pub const Tag = enum {
+ pub const Tag = packed enum {
e_array,
e_unary,
e_binary,
@@ -1147,41 +1573,140 @@ pub const Expr = struct {
e_this,
};
+ pub fn assign(a: *Expr, b: *Expr, allocator: *std.mem.Allocator) Expr {
+ std.debug.assert(a != b);
+ return alloc(allocator, E.Binary{
+ .op = .bin_assign,
+ .left = a.*,
+ .right = b.*,
+ }, a.loc);
+ }
+ pub fn at(expr: *Expr, t: anytype, allocator: *std.mem.allocator) callconv(.Inline) Expr {
+ return alloc(allocator, t, loc);
+ }
+
+ // Wraps the provided expression in the "!" prefix operator. The expression
+ // will potentially be simplified to avoid generating unnecessary extra "!"
+ // operators. For example, calling this with "!!x" will return "!x" instead
+ // of returning "!!!x".
+ pub fn not(expr: Expr, allocator: *std.mem.Allocator) Expr {
+ return maybeSimplifyNot(&expr, allocator) orelse expr;
+ }
+
+ // The given "expr" argument should be the operand of a "!" prefix operator
+ // (i.e. the "x" in "!x"). This returns a simplified expression for the
+ // whole operator (i.e. the "!x") if it can be simplified, or false if not.
+ // It's separate from "Not()" above to avoid allocation on failure in case
+ // that is undesired.
+ pub fn maybeSimplifyNot(expr: *Expr, allocator: *std.mem.Allocator) ?Expr {
+ switch (expr.data) {
+ .e_null, .e_undefined => {
+ return expr.at(E.Boolean{ .value = true }, allocator);
+ },
+ .e_boolean => |b| {
+ return expr.at(E.Boolean{ .value = b.value }, allocator);
+ },
+ .e_number => |n| {
+ return expr.at(E.Boolean{ .value = (n.value == 0 or std.math.isNan(n.value)) }, allocator);
+ },
+ .e_big_int => |b| {
+ return expr.at(E.Boolean{ .value = strings.eql(b.value, "0") }, allocator);
+ },
+ .e_function,
+ .e_arrow,
+ .e_reg_exp,
+ => |b| {
+ return expr.at(E.Boolean{ .value = false }, allocator);
+ },
+ // "!!!a" => "!a"
+ .e_unary => |un| {
+ if (un.op == Op.Code.un_not and isBooleanValue(un.value)) {
+ return un.value.*;
+ }
+ },
+ .e_binary => |*ex| {
+ // TODO: evaluate whether or not it is safe to do this mutation since it's modifying in-place.
+ // Make sure that these transformations are all safe for special values.
+ // For example, "!(a < b)" is not the same as "a >= b" if a and/or b are
+ // NaN (or undefined, or null, or possibly other problem cases too).
+ switch (ex.op) {
+ Op.Code.bin_loose_eq => {
+ ex.op = .bin_loose_ne;
+ return expr.*;
+ },
+ Op.Code.bin_op_loose_ne => {
+ ex.op = .bin_loose_eq;
+ return expr.*;
+ },
+ Op.Code.bin_op_strict_eq => {
+ ex.op = .bin_strict_ne;
+ return expr.*;
+ },
+ Op.Code.bin_op_strict_ne => {
+ ex.op = .bin_strict_eq;
+ return expr.*;
+ },
+ Op.Code.bin_op_comma => {
+ ex.right = ex.right.not();
+ return expr.*;
+ },
+ else => {},
+ }
+ },
+
+ else => {},
+ }
+
+ return null;
+ }
+
+ pub fn assignStmt(a: *Expr, b: *Expr, allocator: *std.mem.Allocator) Stmt {
+ return Stmt.alloc(
+ allocator,
+ S.SExpr{
+ .op = .assign,
+ .left = a,
+ .right = b,
+ },
+ loc,
+ );
+ }
+
pub const Data = union(Tag) {
- e_array: E.Array,
- e_unary: E.Unary,
- e_binary: E.Binary,
- e_this: E.This,
- e_boolean: E.Boolean,
- e_super: E.Super,
- e_null: E.Null,
- e_undefined: E.Undefined,
- e_new: E.New,
- e_new_target: E.NewTarget,
- e_function: E.Function,
- e_import_meta: E.ImportMeta,
- e_call: E.Call,
- e_dot: E.Dot,
- e_index: E.Index,
- e_arrow: E.Arrow,
- e_identifier: E.Identifier,
- e_import_identifier: E.ImportIdentifier,
- e_private_identifier: E.PrivateIdentifier,
- e_jsx_element: E.JSXElement,
- e_missing: E.Missing,
- e_number: E.Number,
- e_big_int: E.BigInt,
- e_object: E.Object,
- e_spread: E.Spread,
- e_string: E.String,
- e_template_part: E.TemplatePart,
- e_template: E.Template,
- e_reg_exp: E.RegExp,
- e_await: E.Await,
- e_yield: E.Yield,
- e_if: E.If,
- e_require_or_require_resolve: E.RequireOrRequireResolve,
- e_import: E.Import,
+ e_array: *E.Array,
+ e_unary: *E.Unary,
+ e_binary: *E.Binary,
+ e_this: *E.This,
+ e_boolean: *E.Boolean,
+ e_super: *E.Super,
+ e_null: *E.Null,
+ e_undefined: *E.Undefined,
+ e_new: *E.New,
+ e_new_target: *E.NewTarget,
+ e_function: *E.Function,
+ e_import_meta: *E.ImportMeta,
+ e_call: *E.Call,
+ e_dot: *E.Dot,
+ e_index: *E.Index,
+ e_arrow: *E.Arrow,
+ e_identifier: *E.Identifier,
+ e_import_identifier: *E.ImportIdentifier,
+ e_private_identifier: *E.PrivateIdentifier,
+ e_jsx_element: *E.JSXElement,
+ e_missing: *E.Missing,
+ e_number: *E.Number,
+ e_big_int: *E.BigInt,
+ e_object: *E.Object,
+ e_spread: *E.Spread,
+ e_string: *E.String,
+ e_template_part: *E.TemplatePart,
+ e_template: *E.Template,
+ e_reg_exp: *E.RegExp,
+ e_await: *E.Await,
+ e_yield: *E.Yield,
+ e_if: *E.If,
+ e_require_or_require_resolve: *E.RequireOrRequireResolve,
+ e_import: *E.Import,
pub fn isOptionalChain(self: *Expr) bool {
return switch (self) {
@@ -1225,6 +1750,14 @@ pub const EnumValue = struct {
pub const S = struct {
pub const Block = struct { stmts: StmtNodeList };
+ pub const SExpr = struct {
+ value: ExprNodeIndex,
+
+ // 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,
+ };
pub const Comment = struct { text: string };
@@ -1265,7 +1798,6 @@ pub const S = struct {
pub const Function = struct {
func: G.Fn,
- is_export: bool,
};
pub const Class = struct {
@@ -1366,7 +1898,7 @@ pub const S = struct {
pub const Catch = struct {
loc: logger.Loc,
- binding: ?BindingNodeIndex,
+ binding: ?BindingNodeIndex = null,
body: StmtNodeList,
};
@@ -1379,7 +1911,7 @@ pub const Case = struct { loc: logger.Loc, value: ?ExprNodeIndex, body: StmtNode
pub const Op = struct {
// If you add a new token, remember to add it to "OpTable" too
- pub const Code = packed enum(u8) {
+ pub const Code = packed enum(u6) {
// Prefix
un_pos,
un_neg,
@@ -1446,7 +1978,7 @@ pub const Op = struct {
bin_logical_and_assign,
};
- pub const Level = packed enum(u23) {
+ pub const Level = packed enum(u6) {
lowest,
comma,
spread,
@@ -1642,12 +2174,12 @@ pub fn isDynamicExport(exp: ExportsKind) bool {
return kind == .cjs || kind == .esm_with_dyn;
}
-pub const DeclaredSymbol = struct {
+pub const DeclaredSymbol = packed struct {
ref: Ref,
is_top_level: bool = false,
};
-pub const Dependency = struct {
+pub const Dependency = packed struct {
source_index: u32 = 0,
part_index: u32 = 0,
};
@@ -1889,7 +2421,8 @@ pub const Scope = struct {
};
test "Binding.init" {
- var binding = Binding.init(
+ var binding = Binding.alloc(
+ std.heap.page_allocator,
B.Identifier{ .ref = Ref{ .source_index = 0, .inner_index = 10 } },
logger.Loc{ .start = 1 },
);
@@ -1906,7 +2439,8 @@ test "Binding.init" {
}
test "Stmt.init" {
- var stmt = Stmt.init(
+ var stmt = Stmt.alloc(
+ std.heap.page_allocator,
S.Continue{},
logger.Loc{ .start = 1 },
);
@@ -1948,9 +2482,11 @@ test "Stmt.init" {
}
test "Expr.init" {
- const ident = Expr.init(E.Identifier{}, logger.Loc{ .start = 100 });
+ var allocator = std.heap.page_allocator;
+ const ident = Expr.alloc(allocator, E.Identifier{}, logger.Loc{ .start = 100 });
var list = [_]Expr{ident};
- var expr = Expr.init(
+ var expr = Expr.alloc(
+ allocator,
E.Array{ .items = list[0..] },
logger.Loc{ .start = 1 },
);
@@ -1958,8 +2494,10 @@ test "Expr.init" {
std.testing.expect(@as(Expr.Tag, expr.data) == Expr.Tag.e_array);
std.testing.expect(expr.data.e_array.items[0].loc.start == 100);
- std.debug.print("--logger.Loc {d} bits\n", .{@bitSizeOf(logger.Loc)});
- std.debug.print("--logger.Range {d} bits\n", .{@bitSizeOf(logger.Range)});
+ std.debug.print("--Ref {d} bits\n", .{@bitSizeOf(Ref)});
+ std.debug.print("--LocRef {d} bits\n", .{@bitSizeOf(LocRef)});
+ std.debug.print("--logger.Loc {d} bits\n", .{@bitSizeOf(logger.Loc)});
+ std.debug.print("--logger.Range {d} bits\n", .{@bitSizeOf(logger.Range)});
std.debug.print("----------Expr: {d} bits\n", .{@bitSizeOf(Expr)});
std.debug.print("ExprNodeList: {d} bits\n", .{@bitSizeOf(ExprNodeList)});
std.debug.print("E.Array: {d} bits\n", .{@bitSizeOf(E.Array)});
@@ -1998,3 +2536,75 @@ test "Expr.init" {
std.debug.print("E.Import: {d} bits\n", .{@bitSizeOf(E.Import)});
std.debug.print("----------Expr: {d} bits\n", .{@bitSizeOf(Expr)});
}
+
+// -- ESBuild bit sizes
+// EArray | 256
+// EArrow | 512
+// EAwait | 192
+// EBinary | 448
+// ECall | 448
+// EDot | 384
+// EIdentifier | 96
+// EIf | 576
+// EImport | 448
+// EImportIdentifier | 96
+// EIndex | 448
+// EJSXElement | 448
+// ENew | 448
+// EnumValue | 384
+// EObject | 256
+// EPrivateIdentifier | 64
+// ERequire | 32
+// ERequireResolve | 32
+// EString | 256
+// ETemplate | 640
+// EUnary | 256
+// Expr | 192
+// ExprOrStmt | 128
+// EYield | 128
+// Finally | 256
+// Fn | 704
+// FnBody | 256
+// LocRef | 96
+// NamedExport | 96
+// NamedImport | 512
+// NameMinifier | 256
+// NamespaceAlias | 192
+// opTableEntry | 256
+// Part | 1088
+// Property | 640
+// PropertyBinding | 512
+// Ref | 64
+// SBlock | 192
+// SBreak | 64
+// SClass | 704
+// SComment | 128
+// SContinue | 64
+// Scope | 704
+// ScopeMember | 96
+// SDirective | 256
+// SDoWhile | 384
+// SEnum | 448
+// SExportClause | 256
+// SExportDefault | 256
+// SExportEquals | 192
+// SExportFrom | 320
+// SExportStar | 192
+// SExpr | 256
+// SFor | 384
+// SForIn | 576
+// SForOf | 640
+// SFunction | 768
+// SIf | 448
+// SImport | 320
+// SLabel | 320
+// SLazyExport | 192
+// SLocal | 256
+// SNamespace | 448
+// Span | 192
+// SReturn | 64
+// SSwitch | 448
+// SThrow | 192
+// Stmt | 192
+// STry | 384
+// -- ESBuild bit sizes
diff --git a/src/js_parser.zig b/src/js_parser.zig
index 963b8e108..5d604b247 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -15,6 +15,7 @@ const ExprNodeIndex = js_ast.ExprNodeIndex;
const ExprNodeList = js_ast.ExprNodeList;
const StmtNodeList = js_ast.StmtNodeList;
const BindingNodeList = js_ast.BindingNodeList;
+const assert = std.debug.assert;
const Ref = js_ast.Ref;
const LocRef = js_ast.LocRef;
@@ -32,8 +33,7 @@ const Op = js_ast.Op;
const Scope = js_ast.Scope;
const locModuleScope = logger.Loc.Empty;
-const s = Stmt.init;
-const e = Expr.init;
+const Tup = std.meta.Tuple;
fn notimpl() noreturn {
std.debug.panic("Not implemented yet!!", .{});
@@ -47,6 +47,8 @@ fn fail() noreturn {
std.debug.panic("Something went wrong :cry;", .{});
}
+const ExprBindingTuple = struct { expr: ?ExprNodeIndex = null, binding: ?Binding = null, override_expr: ?ExprNodeIndex = null };
+
const TempRef = struct {
ref: js_ast.Ref,
value: *js_ast.Expr,
@@ -86,6 +88,7 @@ const SymbolMergeResult = enum {
const Map = std.AutoHashMap;
const List = std.ArrayList;
+const LocList = List(logger.Loc);
const StmtList = List(Stmt);
const SymbolUseMap = Map(js_ast.Ref, js_ast.Symbol.Use);
@@ -94,6 +97,7 @@ const StringBoolMap = std.StringHashMap(bool);
const RefBoolMap = Map(js_ast.Ref, bool);
const RefRefMap = Map(js_ast.Ref, js_ast.Ref);
const ImportRecord = importRecord.ImportRecord;
+const Flags = js_ast.Flags;
const ScopeOrder = struct {
loc: logger.Loc,
scope: *js_ast.Scope,
@@ -524,6 +528,29 @@ const P = struct {
//
after_arrow_body_loc: logger.Loc = logger.Loc.Empty,
+ pub fn s(p: *P, t: anytype, loc: logger.Loc) Stmt {
+ if (@typeInfo(@TypeOf(t)) == .Pointer) {
+ return Stmt.init(t, loc);
+ } else {
+ return Stmt.alloc(p.allocator, t, loc);
+ }
+ }
+ pub fn e(p: *P, t: anytype, loc: logger.Loc) Expr {
+ if (@typeInfo(@TypeOf(t)) == .Pointer) {
+ return Expr.init(t, loc);
+ } else {
+ return Expr.alloc(p.allocator, t, loc);
+ }
+ }
+
+ pub fn b(p: *P, t: anytype, loc: logger.Loc) Binding {
+ if (@typeInfo(@TypeOf(t)) == .Pointer) {
+ return Binding.init(t, loc);
+ } else {
+ return Binding.alloc(p.allocator, t, loc);
+ }
+ }
+
pub fn deinit(parser: *P) void {
parser.allocated_names.deinit();
parser.scopes_for_current_part.deinit();
@@ -582,15 +609,15 @@ const P = struct {
return null;
}
- pub fn logArrowArgErrors(errors: *DeferredArrowArgErrors) void {
+ pub fn logArrowArgErrors(p: *P, errors: *DeferredArrowArgErrors) void {
if (errors.invalid_expr_await.len > 0) {
var r = errors.invalid_expr_await;
- p.log.AddRangeError(&p.source, r, "Cannot use an \"await\" expression here");
+ p.log.addRangeError(p.source, r, "Cannot use an \"await\" expression here") catch unreachable;
}
if (errors.invalid_expr_yield.len > 0) {
var r = errors.invalid_expr_yield;
- p.log.AddRangeError(&p.source, r, "Cannot use a \"yield\" expression here");
+ p.log.addRangeError(p.source, r, "Cannot use a \"yield\" expression here") catch unreachable;
}
}
@@ -783,6 +810,123 @@ const P = struct {
return i;
}
+ // Note: do not write to "p.log" in this function. Any errors due to conversion
+ // from expression to binding should be written to "invalidLog" instead. That
+ // way we can potentially keep this as an expression if it turns out it's not
+ // needed as a binding after all.
+ pub fn convertExprToBinding(p: *P, expr: ExprNodeIndex, invalid_loc: *LocList) ?Binding {
+ switch (expr.data) {
+ .e_missing => {
+ return p.b(B.Missing{}, expr.loc);
+ },
+ .e_identifier => |ex| {
+ return p.b(B.Identifier{ .ref = ex.ref }, expr.loc);
+ },
+ .e_array => |ex| {
+ if (ex.comma_after_spread) |spread| {
+ invalid_loc.append(spread) catch unreachable;
+ }
+
+ if (ex.is_parenthesized) {
+ invalid_loc.append(p.source.rangeOfOperatorBefore(expr.loc, "(").loc) catch unreachable;
+ }
+
+ // p.markSyntaxFeature(Destructing)
+ var items = List(js_ast.ArrayBinding).init(p.allocator);
+ for (items.items) |item| {
+ var is_spread = true;
+ switch (item.default_value.?.data) {
+ .e_identifier => {},
+ else => {
+ // nested rest binding
+ // p.markSyntaxFeature(compat.NestedRestBinding, p.source.RangeOfOperatorAfter(item.Loc, "["))
+ },
+ }
+ var _expr = expr;
+ const res = p.convertExprToBindingAndInitializer(&_expr, invalid_loc, is_spread);
+ assert(res.binding != null);
+ items.append(js_ast.ArrayBinding{ .binding = res.binding orelse unreachable, .default_value = res.override_expr }) catch unreachable;
+ }
+
+ return p.b(B.Array{
+ .items = items.toOwnedSlice(),
+ .has_spread = ex.comma_after_spread != null,
+ .is_single_line = ex.is_single_line,
+ }, expr.loc);
+ },
+ .e_object => |ex| {
+ if (ex.comma_after_spread) |sp| {
+ invalid_loc.append(sp) catch unreachable;
+ }
+
+ if (ex.is_parenthesized) {
+ invalid_loc.append(p.source.rangeOfOperatorBefore(expr.loc, "(").loc) catch unreachable;
+ }
+ // p.markSyntaxFeature(compat.Destructuring, p.source.RangeOfOperatorAfter(expr.Loc, "{"))
+
+ var properties = List(B.Property).init(p.allocator);
+ for (ex.properties) |item| {
+ if (item.flags.is_method or item.kind == .get or item.kind == .set) {
+ invalid_loc.append(item.key.loc) catch unreachable;
+ continue;
+ }
+ var value = &(item.value orelse unreachable);
+ const tup = p.convertExprToBindingAndInitializer(value, invalid_loc, false);
+ const initializer = tup.expr orelse item.initializer;
+
+ properties.append(B.Property{
+ .flags = Flags.Property{
+ .is_spread = item.kind == .spread,
+ .is_computed = item.flags.is_computed,
+ },
+
+ .key = item.key,
+ .value = tup.binding orelse unreachable,
+ .default_value = initializer,
+ }) catch unreachable;
+ }
+
+ return p.b(B.Object{
+ .properties = properties.toOwnedSlice(),
+ .is_single_line = ex.is_single_line,
+ }, expr.loc);
+ },
+ else => {
+ invalid_loc.append(expr.loc) catch unreachable;
+ return null;
+ },
+ }
+
+ return null;
+ }
+
+ pub fn convertExprToBindingAndInitializer(p: *P, expr: *ExprNodeIndex, invalid_log: *LocList, is_spread: bool) ExprBindingTuple {
+ var initializer: ?ExprNodeIndex = null;
+ var override: ?ExprNodeIndex = null;
+ // zig syntax is sometimes painful
+ switch (expr.*.data) {
+ .e_binary => |bin| {
+ if (bin.op == .bin_assign) {
+ initializer = bin.right;
+ override = bin.left;
+ }
+ },
+ else => {},
+ }
+
+ var bind = p.convertExprToBinding(expr.*, invalid_log);
+ if (initializer) |initial| {
+ const equalsRange = p.source.rangeOfOperatorBefore(initial.loc, "=");
+ if (is_spread) {
+ p.log.addRangeError(p.source, equalsRange, "A rest argument cannot have a default initializer") catch unreachable;
+ } else {
+ // p.markSyntaxFeature();
+ }
+ }
+
+ return ExprBindingTuple{ .binding = bind, .expr = initializer };
+ }
+
pub fn forbidLexicalDecl(p: *P, loc: logger.Loc) !void {
try p.log.addRangeError(p.source, p.lexer.range(), "Cannot use a declaration in a single-statement context");
}
@@ -868,9 +1012,10 @@ const P = struct {
p.popAndDiscardScope(scopeIndex);
}
- return Stmt.init(S.Function{
+ func.flags.is_export = opts.is_export;
+
+ return p.s(S.Function{
.func = func,
- .is_export = opts.is_export,
}, func.open_parens_loc);
}
@@ -901,9 +1046,12 @@ const P = struct {
var func = G.Fn{
.name = name,
- .has_rest_arg = false,
- .is_async = opts.allow_await,
- .is_generator = opts.allow_yield,
+ .flags = Flags.Function{
+ .has_rest_arg = false,
+ .is_async = opts.allow_await,
+ .is_generator = opts.allow_yield,
+ },
+
.arguments_ref = null,
.open_parens_loc = p.lexer.loc(),
};
@@ -939,10 +1087,10 @@ const P = struct {
ts_decorators = p.parseTypeScriptDecorators();
}
- if (!func.has_rest_arg and p.lexer.token == T.t_dot_dot_dot) {
+ if (!func.flags.has_rest_arg and p.lexer.token == T.t_dot_dot_dot) {
// p.markSyntaxFeature
p.lexer.next();
- func.has_rest_arg = true;
+ func.flags.has_rest_arg = true;
}
var is_typescript_ctor_field = false;
@@ -1067,7 +1215,7 @@ const P = struct {
p.lexer.expect(T.t_identifier);
p.lexer.expectOrInsertSemicolon();
- return Stmt.init(S.TypeScript{}, loc);
+ return p.s(S.TypeScript{}, loc);
}
if (p.lexer.isContextualKeyword("async")) {
@@ -1140,17 +1288,17 @@ const P = struct {
defaultName = try p.createDefaultName(defaultLoc);
}
// this is probably a panic
- var value = js_ast.StmtOrExpr{ .stmt = &stmt };
- return s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc);
+ var value = js_ast.StmtOrExpr{ .stmt = stmt };
+ return p.s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc);
}
defaultName = try createDefaultName(p, loc);
// TODO: here
- var expr = try p.parseSuffix(try p.parseAsyncPrefixExpr(async_range, Level.comma), Level.comma, null, Expr.Flags.none);
+ var expr = p.parseSuffix(try p.parseAsyncPrefixExpr(async_range, Level.comma), Level.comma, null, Expr.EFlags.none);
p.lexer.expectOrInsertSemicolon();
// this is probably a panic
- var value = js_ast.StmtOrExpr{ .expr = &expr };
- return s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc);
+ var value = js_ast.StmtOrExpr{ .expr = expr };
+ return p.s(S.ExportDefault{ .default_name = defaultName, .value = value }, loc);
}
},
else => {
@@ -1177,7 +1325,7 @@ const P = struct {
run: while (true) {
if (p.lexer.comments_to_preserve_before) |comments| {
for (comments) |comment| {
- try stmts.append(Stmt.init(S.Comment{
+ try stmts.append(p.s(S.Comment{
.text = comment.text,
}, p.lexer.loc()));
}
@@ -1355,7 +1503,7 @@ const P = struct {
.allow_yield = is_generator,
});
- return Expr.init(js_ast.E.Function{
+ return p.e(js_ast.E.Function{
.func = func,
}, loc);
}
@@ -1408,32 +1556,32 @@ const P = struct {
var old_fn_or_arrow_data = p.fn_or_arrow_data_parse;
p.fn_or_arrow_data_parse = data.*;
- var expr = p.m(p.parseExpr(Level.comma));
+ var expr = p.parseExpr(Level.comma);
p.fn_or_arrow_data_parse = old_fn_or_arrow_data;
var stmts = try p.allocator.alloc(Stmt, 1);
- stmts[0] = Stmt.init(S.Return{ .value = expr }, arrow_loc);
+ stmts[0] = p.s(S.Return{ .value = expr }, arrow_loc);
return E.Arrow{ .args = args, .prefer_expr = true, .body = G.FnBody{ .loc = arrow_loc, .stmts = stmts } };
}
pub fn declareBinding(p: *P, kind: Symbol.Kind, binding: BindingNodeIndex, opts: ParseStatementOptions) !void {
switch (binding.data) {
- .b_identifier => |*b| {
+ .b_identifier => |bind| {
if (!opts.is_typescript_declare or (opts.is_namespace_scope and opts.is_export)) {
- b.ref = try p.declareSymbol(kind, binding.loc, p.loadNameFromRef(b.ref));
+ bind.ref = try p.declareSymbol(kind, binding.loc, p.loadNameFromRef(bind.ref));
}
},
- .b_missing => |b| {},
+ .b_missing => |*bind| {},
- .b_array => |*b| {
- for (b.items) |item| {
+ .b_array => |bind| {
+ for (bind.items) |item| {
p.declareBinding(kind, item.binding, opts) catch unreachable;
}
},
- .b_object => |*b| {
- for (b.properties) |prop| {
- const value = prop.value orelse std.debug.panic("Internal error: property {s} is missing a binding!", .{prop});
+ .b_object => |bind| {
+ for (bind.properties) |*prop| {
+ const value = prop.value;
p.declareBinding(kind, value, opts) catch unreachable;
}
},
@@ -1533,16 +1681,16 @@ const P = struct {
}
pub fn loadNameFromRef(p: *P, ref: js_ast.Ref) string {
- if (ref.source_index) |source_index| {
- if (source_index == 0x80000000) {
+ if (!ref.isSourceNull()) {
+ if (ref.source_index == 0x80000000) {
return p.allocated_names.items[ref.inner_index];
}
if (std.builtin.mode != std.builtin.Mode.ReleaseFast) {
- std.debug.assert(ref.inner_index - source_index > 0);
+ assert(ref.inner_index - ref.source_index > 0);
}
- return p.source.contents[ref.inner_index .. ref.inner_index - source_index];
+ return p.source.contents[ref.inner_index .. ref.inner_index - ref.source_index];
} else {
std.debug.panic("Internal error: invalid symbol reference. {s}", .{ref});
}
@@ -1563,24 +1711,24 @@ const P = struct {
switch (p.lexer.token) {
// "async => {}"
.t_equals_greater_than => {
- const arg = G.Arg{ .binding = p.m(Binding.init(
+ const arg = G.Arg{ .binding = p.b(
B.Identifier{
.ref = try p.storeNameInRef("async"),
},
async_range.loc,
- )) };
+ ) };
_ = p.pushScopeForParsePass(.function_args, async_range.loc) catch unreachable;
defer p.popScope();
var arrow_body = try p.parseArrowBodySingleArg(arg, FnOrArrowDataParse{});
- return Expr.init(arrow_body, async_range.loc);
+ return p.e(arrow_body, async_range.loc);
},
// "async x => {}"
.t_identifier => {
// p.markLoweredSyntaxFeature();
const ref = try p.storeNameInRef(p.lexer.identifier);
- var arg = G.Arg{ .binding = p.m(Binding.init(B.Identifier{
+ var arg = G.Arg{ .binding = p.b(B.Identifier{
.ref = ref,
- }, p.lexer.loc())) };
+ }, p.lexer.loc()) };
p.lexer.next();
_ = try p.pushScopeForParsePass(.function_args, async_range.loc);
@@ -1590,7 +1738,7 @@ const P = struct {
.allow_await = true,
});
arrowBody.is_async = true;
- return Expr.init(arrowBody, async_range.loc);
+ return p.e(arrowBody, async_range.loc);
},
// "async()"
@@ -1615,7 +1763,7 @@ const P = struct {
// "async"
// "async + 1"
- return Expr.init(
+ return p.e(
E.Identifier{ .ref = try p.storeNameInRef("async") },
async_range.loc,
);
@@ -1626,18 +1774,18 @@ const P = struct {
}
pub fn parseExprOrBindings(p: *P, level: Level, errors: ?*DeferredErrors) Expr {
- return p.parseExprCommon(level, errors, Expr.Flags.none);
+ return p.parseExprCommon(level, errors, Expr.EFlags.none);
}
pub fn parseExpr(p: *P, level: Level) Expr {
- return p.parseExprCommon(level, null, Expr.Flags.none);
+ return p.parseExprCommon(level, null, Expr.EFlags.none);
}
- pub fn parseExprWithFlags(p: *P, level: Level, flags: Expr.Flags) Expr {
+ pub fn parseExprWithFlags(p: *P, level: Level, flags: Expr.EFlags) Expr {
return p.parseExprCommon(level, null, flags);
}
- pub fn parseExprCommon(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.Flags) Expr {
+ pub fn parseExprCommon(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) Expr {
const had_pure_comment_before = p.lexer.has_pure_comment_before and !p.options.ignore_dce_annotations;
var expr = p.parsePrefix(level, errors, flags);
@@ -1647,7 +1795,7 @@ const P = struct {
// to the expression "a().b()".
if (had_pure_comment_before and level.lt(.call)) {
- expr = p.parseSuffix(expr, .call - 1, errors, flags);
+ expr = p.parseSuffix(expr, @intToEnum(Level, @enumToInt(Level.call) - 1), errors, flags);
switch (expr.data) {
.e_call => |ex| {
ex.can_be_unwrapped_if_unused = true;
@@ -1662,49 +1810,6 @@ const P = struct {
return p.parseSuffix(expr, level, errors, flags);
}
- // This assumes that the open parenthesis has already been parsed by the caller
- pub fn parseParenExpr(p: *P, loc: logger.Loc, opts: ParenExprOpts) !Expr {
- var items = List(Expr).initCapacity(p.allocator, 1);
- var errors = DeferredErrors{};
- var arrowArgErrors = DeferredArrowArgErrors{};
- var spread_range = logger.Range{};
- var type_colon_range = logger.Range{};
- var comma_after_spread = logger.Loc{};
-
- // Push a scope assuming this is an arrow function. It may not be, in which
- // case we'll need to roll this change back. This has to be done ahead of
- // parsing the arguments instead of later on when we hit the "=>" token and
- // we know it's an arrow function because the arguments may have default
- // values that introduce new scopes and declare new symbols. If this is an
- // arrow function, then those new scopes will need to be parented under the
- // scope of the arrow function itself.
- const scopeIndex = p.pushScopeForParsePass(.function_args, loc);
-
- // Allow "in" inside parentheses
- var oldAllowIn = p.allow_in;
- p.allow_in = true;
-
- // Forbid "await" and "yield", but only for arrow functions
- var old_fn_or_arrow_data = p.fn_or_arrow_data_parse;
- p.fn_or_arrow_data_parse.arrow_arg_errors = arrowArgErrors;
-
- // Scan over the comma-separated arguments or expressions
- while (p.lexer.token != .t_close_paren) {
- const item_loc = p.lexer.loc();
- const is_spread = p.lexer.token == .t_dot_dot_dot;
-
- if (is_spread) {
- spread_range = p.lexer.range();
- // p.markSyntaxFeature()
- p.lexer.next();
- }
-
- p.latest_arrow_arg_loc = p.lexer.loc();
- // TODO: here
- var item = p.parseExprOrBindings(.comma, &errors);
- }
- }
-
pub fn popScope(p: *P) void {
const current_scope = p.current_scope orelse unreachable;
// We cannot rename anything inside a scope containing a direct eval() call
@@ -1766,10 +1871,10 @@ const P = struct {
pub fn markExprAsParenthesized(p: *P, expr: *Expr) void {
switch (expr.data) {
- .e_array => |*ex| {
+ .e_array => |ex| {
ex.is_parenthesized = true;
},
- .e_object => |*ex| {
+ .e_object => |ex| {
ex.is_parenthesized = true;
},
else => {
@@ -1794,28 +1899,27 @@ const P = struct {
.t_close_brace, .t_close_paren, .t_colon, .t_comma, .t_semicolon => {},
else => {
if (isStar or !p.lexer.has_newline_before) {
- var expr = p.parseExpr(.yield);
- value = p.m(expr);
+ value = p.parseExpr(.yield);
}
},
}
- return e(E.Yield{
+ return p.e(E.Yield{
.value = value,
.is_star = isStar,
}, loc);
}
- pub fn parseSuffix(p: *P, left: Expr, level: Level, errors: ?*DeferredErrors, flags: Expr.Flags) Expr {
+ pub fn parseSuffix(p: *P, left: Expr, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) Expr {
return _parseSuffix(p, left, level, errors orelse &DeferredErrors.None, flags);
}
- pub fn _parseSuffix(p: *P, left: Expr, level: Level, errors: *DeferredErrors, flags: Expr.Flags) callconv(.Inline) Expr {
+ pub fn _parseSuffix(p: *P, left: Expr, level: Level, errors: *DeferredErrors, flags: Expr.EFlags) callconv(.Inline) Expr {
var expr: Expr = undefined;
var loc = p.lexer.loc();
return expr;
}
- pub fn _parsePrefix(p: *P, level: Level, errors: *DeferredErrors, flags: Expr.Flags) callconv(.Inline) Expr {
+ pub fn _parsePrefix(p: *P, level: Level, errors: *DeferredErrors, flags: Expr.EFlags) callconv(.Inline) Expr {
const loc = p.lexer.loc();
const l = @enumToInt(level);
@@ -1827,17 +1931,17 @@ const P = struct {
switch (p.lexer.token) {
.t_open_paren => {
if (l < @enumToInt(Level.call) and p.fn_or_arrow_data_parse.allow_super_call) {
- return e(E.Super{}, loc);
+ return p.e(E.Super{}, loc);
}
},
.t_dot, .t_open_bracket => {
- return e(E.Super{}, loc);
+ return p.e(E.Super{}, loc);
},
else => {},
}
p.log.addRangeError(p.source, superRange, "Unexpected \"super\"") catch unreachable;
- return e(E.Super{}, loc);
+ return p.e(E.Super{}, loc);
},
.t_open_paren => {
p.lexer.next();
@@ -1858,19 +1962,19 @@ const P = struct {
},
.t_false => {
p.lexer.next();
- return e(E.Boolean{ .value = false }, loc);
+ return p.e(E.Boolean{ .value = false }, loc);
},
.t_true => {
p.lexer.next();
- return e(E.Boolean{ .value = true }, loc);
+ return p.e(E.Boolean{ .value = true }, loc);
},
.t_null => {
p.lexer.next();
- return e(E.Null{}, loc);
+ return p.e(E.Null{}, loc);
},
.t_this => {
p.lexer.next();
- return e(E.This{}, loc);
+ return p.e(E.This{}, loc);
},
.t_identifier => {
const name = p.lexer.identifier;
@@ -1900,12 +2004,12 @@ const P = struct {
p.fn_or_arrow_data_parse.arrow_arg_errors = DeferredArrowArgErrors{ .invalid_expr_await = name_range };
}
- var value = p.m(p.parseExpr(.prefix));
+ var value = p.parseExpr(.prefix);
if (p.lexer.token == T.t_asterisk_asterisk) {
p.lexer.unexpected();
}
- return e(E.Await{ .value = value }, loc);
+ return p.e(E.Await{ .value = value }, loc);
}
}
} else if (strings.eql(name, "yield")) {
@@ -1939,18 +2043,18 @@ const P = struct {
if (p.lexer.token == .t_equals_greater_than) {
const ref = p.storeNameInRef(name) catch unreachable;
var args = p.allocator.alloc(Arg, 1) catch unreachable;
- args[0] = Arg{ .binding = p.m(Binding.init(B.Identifier{
+ args[0] = Arg{ .binding = p.b(B.Identifier{
.ref = ref,
- }, loc)) };
+ }, loc) };
_ = p.pushScopeForParsePass(.function_args, loc) catch unreachable;
defer p.popScope();
- return e(p.parseArrowBody(args, p.m(FnOrArrowDataParse{})) catch unreachable, loc);
+ return p.e(p.parseArrowBody(args, p.m(FnOrArrowDataParse{})) catch unreachable, loc);
}
const ref = p.storeNameInRef(name) catch unreachable;
- return e(E.Identifier{
+ return p.e(E.Identifier{
.ref = ref,
}, loc);
}
@@ -1978,16 +2082,146 @@ const P = struct {
.t_import => {},
else => {
p.lexer.unexpected();
- return Expr.init(E.Missing{}, logger.Loc.Empty);
+ return p.e(E.Missing{}, logger.Loc.Empty);
},
}
- return Expr.init(E.Missing{}, logger.Loc.Empty);
+ return p.e(E.Missing{}, logger.Loc.Empty);
}
- pub fn parsePrefix(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.Flags) Expr {
+ pub fn parsePrefix(p: *P, level: Level, errors: ?*DeferredErrors, flags: Expr.EFlags) Expr {
return p._parsePrefix(level, errors orelse &DeferredErrors.None, flags);
}
+ // This assumes that the open parenthesis has already been parsed by the caller
+ pub fn parseParenExpr(p: *P, loc: logger.Loc, opts: ParenExprOpts) !Expr {
+ var items_list = try List(Expr).initCapacity(p.allocator, 1);
+ var errors = DeferredErrors{};
+ var arrowArgErrors = DeferredArrowArgErrors{};
+ var spread_range = logger.Range{};
+ var type_colon_range = logger.Range{};
+ var comma_after_spread = logger.Loc{};
+
+ // Push a scope assuming this is an arrow function. It may not be, in which
+ // case we'll need to roll this change back. This has to be done ahead of
+ // parsing the arguments instead of later on when we hit the "=>" token and
+ // we know it's an arrow function because the arguments may have default
+ // values that introduce new scopes and declare new symbols. If this is an
+ // arrow function, then those new scopes will need to be parented under the
+ // scope of the arrow function itself.
+ const scopeIndex = p.pushScopeForParsePass(.function_args, loc);
+
+ // Allow "in" inside parentheses
+ var oldAllowIn = p.allow_in;
+ p.allow_in = true;
+
+ // Forbid "await" and "yield", but only for arrow functions
+ var old_fn_or_arrow_data = p.fn_or_arrow_data_parse;
+ p.fn_or_arrow_data_parse.arrow_arg_errors = arrowArgErrors;
+
+ // Scan over the comma-separated arguments or expressions
+ while (p.lexer.token != .t_close_paren) {
+ const item_loc = p.lexer.loc();
+ const is_spread = p.lexer.token == .t_dot_dot_dot;
+
+ if (is_spread) {
+ spread_range = p.lexer.range();
+ // p.markSyntaxFeature()
+ p.lexer.next();
+ }
+
+ // We don't know yet whether these are arguments or expressions, so parse
+ p.latest_arrow_arg_loc = p.lexer.loc();
+
+ var item = p.parseExprOrBindings(.comma, &errors);
+
+ if (is_spread) {
+ item = p.e(E.Spread{ .value = item }, loc);
+ }
+
+ // Skip over types
+ if (p.options.ts and p.lexer.token == .t_colon) {
+ type_colon_range = p.lexer.range();
+ p.lexer.next();
+ p.skipTypescriptType(.lowest);
+ }
+
+ if (p.options.ts and p.lexer.token == .t_equals and !p.forbid_suffix_after_as_loc.eql(p.lexer.loc())) {
+ p.lexer.next();
+ var expr = p.parseExpr(.comma);
+ item = item.assign(&expr, p.allocator);
+ }
+
+ items_list.append(item) catch unreachable;
+
+ if (p.lexer.token != .t_comma) {
+ break;
+ }
+
+ // Spread arguments must come last. If there's a spread argument followed
+ if (is_spread) {
+ comma_after_spread = p.lexer.loc();
+ }
+
+ // Eat the comma token
+ p.lexer.next();
+ }
+ var items = items_list.toOwnedSlice();
+
+ // The parenthetical construct must end with a close parenthesis
+ p.lexer.expect(.t_close_paren);
+
+ // Restore "in" operator status before we parse the arrow function body
+ p.allow_in = oldAllowIn;
+
+ // Also restore "await" and "yield" expression errors
+ p.fn_or_arrow_data_parse = old_fn_or_arrow_data;
+
+ // Are these arguments to an arrow function?
+ if (p.lexer.token == .t_equals_greater_than or opts.force_arrow_fn or (p.options.ts and p.lexer.token == .t_colon)) {
+ var invalidLog = List(logger.Loc).init(p.allocator);
+ var args = List(G.Arg).init(p.allocator);
+
+ if (opts.is_async) {
+ // markl,oweredsyntaxpoksdpokasd
+ }
+
+ // First, try converting the expressions to bindings
+ for (items) |*_item| {
+ var item = _item;
+ var is_spread = false;
+ switch (item.data) {
+ .e_spread => |v| {
+ is_spread = true;
+ item = &v.value;
+ },
+ else => {},
+ }
+
+ const tuple = p.convertExprToBindingAndInitializer(item, &invalidLog, is_spread);
+ assert(tuple.binding != null);
+ // double allocations
+ args.append(G.Arg{
+ .binding = tuple.binding orelse unreachable,
+ .default = tuple.expr,
+ }) catch unreachable;
+ }
+
+ // Avoid parsing TypeScript code like "a ? (1 + 2) : (3 + 4)" as an arrow
+ // function. The ":" after the ")" may be a return type annotation, so we
+ // attempt to convert the expressions to bindings first before deciding
+ // whether this is an arrow function, and only pick an arrow function if
+ // there were no conversion errors.
+ if (p.lexer.token == .t_equals_greater_than or (invalidLog.items.len == 0 and (p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking() or opts.force_arrow_fn))) {
+ if (comma_after_spread.start > 0) {
+ p.log.addRangeError(p.source, logger.Range{ .loc = comma_after_spread, .len = 1 }, "Unexpected \",\" after rest pattern") catch unreachable;
+ }
+ p.logArrowArgErrors(&arrowArgErrors);
+ }
+ }
+
+ return p.e(E.Missing{}, loc);
+ }
+
pub fn init(allocator: *std.mem.Allocator, log: logger.Log, source: logger.Source, lexer: js_lexer.Lexer, opts: Parser.Options) !*P {
var parser = try allocator.create(P);
parser.allocated_names = @TypeOf(parser.allocated_names).init(allocator);
diff --git a/src/logger.zig b/src/logger.zig
index 492761f8d..ccc922cbe 100644
--- a/src/logger.zig
+++ b/src/logger.zig
@@ -28,10 +28,16 @@ pub const Kind = enum {
pub const Loc = packed struct {
start: i32 = -1,
+ // TODO: remove this stupidity
pub fn toUsize(self: *Loc) usize {
return @intCast(usize, self.start);
}
+ // TODO: remove this stupidity
+ pub fn i(self: *const Loc) usize {
+ return @intCast(usize, self.start);
+ }
+
pub const Empty = Loc{ .start = -1 };
pub fn eql(loc: *Loc, other: Loc) bool {
@@ -226,7 +232,31 @@ pub const Source = struct {
}
pub fn textForRange(self: *Source, r: Range) string {
- return self.contents[std.math.lossyCast(usize, r.loc.start)..r.endI()];
+ return self.contents[r.loc.i()..r.endI()];
+ }
+
+ pub fn rangeOfOperatorBefore(self: *Source, loc: Loc, op: string) Range {
+ const text = self.contents[0..loc.i()];
+ const index = strings.index(text, op);
+ if (index >= 0) {
+ return Range{ .loc = Loc{
+ .start = loc.start + index,
+ }, .len = @intCast(i32, op.len) };
+ }
+
+ return Range{ .loc = loc };
+ }
+
+ pub fn rangeOfOperatorAfter(self: *Source, loc: Loc, op: string) Range {
+ const text = self.contents[loc.i()..];
+ const index = strings.index(text, op);
+ if (index >= 0) {
+ return Range{ .loc = Loc{
+ .start = loc.start + index,
+ }, .len = op.len };
+ }
+
+ return Range{ .loc = loc };
}
pub fn initErrorPosition(self: *const Source, _offset: Loc) ErrorPosition {
diff --git a/src/string_immutable.zig b/src/string_immutable.zig
index 7613509c0..f8bf855f4 100644
--- a/src/string_immutable.zig
+++ b/src/string_immutable.zig
@@ -26,3 +26,11 @@ pub fn indexOf(self: string, str: u8) ?usize {
pub fn eql(self: string, other: anytype) bool {
return std.mem.eql(u8, self, other);
}
+
+pub fn index(self: string, str: string) i32 {
+ if (std.mem.indexOf(u8, self, str)) |i| {
+ return @intCast(i32, i);
+ } else {
+ return -1;
+ }
+}