diff options
-rw-r--r-- | src/js_ast.zig | 1210 | ||||
-rw-r--r-- | src/js_parser.zig | 460 | ||||
-rw-r--r-- | src/logger.zig | 32 | ||||
-rw-r--r-- | src/string_immutable.zig | 8 |
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; + } +} |