const std = @import("std"); const Location = @import("Location.zig").Location; const Variant = @import("../Common/Variant.zig").Variant; const cpp_std = @import("../cpp_std.zig"); const Ast = @This(); pub const Name = extern struct { value: [*:0]const u8, }; pub const Local = extern struct { name: Name, location: Location, shadow: ?*Local, functionDepth: usize, loopDepth: usize, isConst: bool, /// exported is only a property set after construction isExported: bool = false, annotation: ?*Type, }; pub fn Array(comptime T: type) type { return extern struct { data: ?[*]T = null, size: usize = 0, const This = @This(); pub fn slice(self: This) []T { return if (self.data) |d| d[0..self.size] else &.{}; } }; } pub const TypeList = extern struct { types: Array(*Type), /// Null indicates no tail, not an untyped tail. tailType: ?*TypePack = null, }; pub const Node = extern struct { vtable: *const anyopaque, classIndex: Node.Kind, location: Location, pub const Kind = enum(u32) { unknown, attr, generic_type, generic_type_pack, expr_group, expr_constant_nil, expr_constant_bool, expr_constant_number, expr_constant_integer, expr_constant_string, expr_local, expr_global, expr_varargs, expr_call, expr_index_name, expr_index_expr, expr_function, expr_table, expr_unary, expr_binary, expr_type_assertion, expr_if_else, expr_interp_string, expr_instantiate, stat_block, stat_if, stat_while, stat_repeat, stat_break, stat_continue, stat_return, stat_expr, stat_local, stat_for, stat_for_in, stat_assign, stat_compound_assign, stat_function, stat_local_function, stat_type_alias, stat_type_function, stat_declare_global, stat_declare_function, stat_class, stat_declare_extern_type, type_reference, type_table, type_function, type_typeof, type_optional, type_union, type_intersection, expr_error, stat_error, type_error, type_singleton_bool, type_singleton_string, type_group, type_pack_explicit, type_pack_variadic, type_pack_generic, pub fn Type(self: Kind) type { return switch (self) { .unknown => Node, .attr => Attr, .generic_type => GenericType, .generic_type_pack => GenericTypePack, .expr_group => ExprGroup, .expr_constant_nil => ExprConstantNil, .expr_constant_bool => ExprConstantBool, .expr_constant_number => ExprConstantNumber, .expr_constant_integer => ExprConstantInteger, .expr_constant_string => ExprConstantString, .expr_local => ExprLocal, .expr_global => ExprGlobal, .expr_varargs => ExprVarargs, .expr_call => ExprCall, .expr_index_name => ExprIndexName, .expr_index_expr => ExprIndexExpr, .expr_function => ExprFunction, .expr_table => ExprTable, .expr_unary => ExprUnary, .expr_binary => ExprBinary, .expr_type_assertion => ExprTypeAssertion, .expr_if_else => ExprIfElse, .expr_interp_string => ExprInterpString, .expr_instantiate => ExprInstantiate, .stat_block => StatBlock, .stat_if => StatIf, .stat_while => StatWhile, .stat_repeat => StatRepeat, .stat_break => StatBreak, .stat_continue => StatContinue, .stat_return => StatReturn, .stat_expr => StatExpr, .stat_local => StatLocal, .stat_for => StatFor, .stat_for_in => StatForIn, .stat_assign => StatAssign, .stat_compound_assign => StatCompoundAssign, .stat_function => StatFunction, .stat_local_function => StatLocalFunction, .stat_type_alias => StatTypeAlias, .stat_type_function => StatTypeFunction, .stat_declare_global => StatDeclareGlobal, .stat_declare_function => StatDeclareFunction, .stat_class => StatClass, .stat_declare_extern_type => StatDeclareExternType, .type_reference => TypeReference, .type_table => TypeTable, .type_function => TypeFunction, .type_typeof => TypeTypeof, .type_optional => TypeOptional, .type_union => TypeUnion, .type_intersection => TypeIntersection, .expr_error => ExprError, .stat_error => StatError, .type_error => TypeError, .type_singleton_bool => TypeSingletonBool, .type_singleton_string => TypeSingletonString, .type_group => TypeGroup, .type_pack_explicit => TypePackExplicit, .type_pack_variadic => TypePackVariadic, .type_pack_generic => TypePackGeneric, }; } pub fn Parent(self: Kind) type { return switch (self) { .unknown => Node, .attr, .generic_type, .generic_type_pack, => Node, .expr_group, .expr_constant_nil, .expr_constant_bool, .expr_constant_number, .expr_constant_string, .expr_local, .expr_global, .expr_varargs, .expr_call, .expr_index_name, .expr_index_expr, .expr_function, .expr_table, .expr_unary, .expr_binary, .expr_type_assertion, .expr_if_else, .expr_interp_string, .expr_instantiate, .expr_error, => Expr, .stat_block, .stat_if, .stat_while, .stat_repeat, .stat_break, .stat_continue, .stat_return, .stat_expr, .stat_local, .stat_for, .stat_for_in, .stat_assign, .stat_compound_assign, .stat_function, .stat_local_function, .stat_type_alias, .stat_type_function, .stat_declare_global, .stat_declare_function, .stat_class, .stat_declare_extern_type, .stat_error, => Stat, .type_reference, .type_table, .type_function, .type_typeof, .type_optional, .type_union, .type_intersection, .type_error, .type_singleton_bool, .type_singleton_string, .type_group, => Ast.Type, .type_pack_explicit, .type_pack_variadic, .type_pack_generic, => Ast.TypePack, }; } }; pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; }; pub fn IsFn(base: anytype, comptime to: Node.Kind) bool { return base.classIndex == to; } pub fn AsCastFn(base: anytype, comptime to: Node.Kind) ?*to.Type() { return if (base.classIndex == to) @ptrCast(@alignCast(base)) else null; } pub fn AsStatCastFn(base: anytype) *Stat { return @ptrCast(@alignCast(base)); } pub fn AsExprCastFn(base: anytype) *Expr { return @ptrCast(@alignCast(base)); } pub fn AsTypeCastFn(base: anytype) *Type { return @ptrCast(@alignCast(base)); } pub const Attr = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .attr, location: Location, type: Attr.Type, args: Array(*Expr), name: Name, pub const Type = enum(c_int) { Checked = 0, Native = 1, Deprecated = 2, DebugNoinline = 3, Unknown = 4, }; pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const Expr = extern struct { vtable: *const anyopaque, classIndex: Node.Kind, location: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { return Visitor.selfVisit(visitor, self); } pub fn isLValue(expr: *const Expr) bool { return switch (expr.classIndex) { .expr_local, .expr_global, .expr_index_name, .expr_index_expr => true, else => false, }; } pub fn getIdentifier(node: *Expr) ?Name { if (node.as(.expr_global)) |expr| return expr.name; if (node.as(.expr_local)) |expr| return expr.local.name; return null; } }; pub const Stat = extern struct { vtable: *const anyopaque, classIndex: Node.Kind, location: Location, hasSemicolon: bool, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { return Visitor.selfVisit(visitor, self); } }; pub const GenericType = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .generic_type, location: Location, name: Name, defaultValue: ?*Type = null, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { if (self.defaultValue) |node| try node.visit(visitor); } } }; pub const GenericTypePack = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .generic_type_pack, location: Location, name: Name, defaultValue: ?*TypePack = null, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { if (self.defaultValue) |node| try node.visit(visitor); } } }; pub const ExprGroup = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_group, location: Location, expr: *Expr, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.expr.visit(visitor); } } }; pub const ExprConstantNil = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_constant_nil, location: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const ExprConstantBool = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_constant_bool, location: Location, value: bool, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const ConstantNumberParseResult = enum(c_int) { Ok = 0, Imprecise = 1, Malformed = 2, BinOverflow = 3, HexOverflow = 4, IntOverflow = 5, }; pub const ExprConstantNumber = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_constant_number, location: Location, value: f64, parseResult: ConstantNumberParseResult, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const ExprConstantInteger = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_constant_integer, location: Location, value: i64, parseResult: ConstantNumberParseResult, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const ExprConstantString = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_constant_string, location: Location, value: Array(u8), quoteStyle: QuoteStyle, pub const QuoteStyle = enum(c_int) { /// A string created using double quotes or an interpolated string, /// as in: /// /// "foo", `My name is {protagonist}! / And I'm {antagonist}!` /// QuotedSimple = 0, /// A string created using single quotes, as in: /// /// 'bar' /// QuotedSingle = 1, /// A string created using `[[ ... ]]` as in: /// /// [[ Gee, this sure is a long string. /// it even has a new line in it! ]] /// QuotedRaw = 2, /// A "string" in the context of a table literal, as in: /// /// { foo = 42 } -- `foo` here is a "constant string" /// Unquoted = 3, }; pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const ExprLocal = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_local, location: Location, local: ?*Local, upvalue: bool, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const ExprGlobal = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_global, location: Location, name: Name, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const ExprVarargs = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_varargs, location: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const ExprCall = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_call, location: Location, func: *Expr, /// These will only be filled in specifically `t:f<>()`. /// In `f<>()`, this is parsed as `f<>` as an expression, /// which is then called. typeArguments: Array(TypeOrPack), args: Array(*Expr), self: bool, argLocation: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.func.visit(visitor); for (self.args.slice()) |arg| try arg.visit(visitor); } } }; pub const ExprIndexName = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_index_name, location: Location, expr: *Expr, index: Name, indexLocation: Location, opPosition: Location.Position, op: u8 = '.', pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.expr.visit(visitor); } } }; pub const ExprIndexExpr = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_index_expr, location: Location, expr: *Expr, index: *Expr, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.expr.visit(visitor); try self.index.visit(visitor); } } }; pub const ExprFunction = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_function, location: Location, attributes: Array(*Attr), generics: Array(*GenericType), genericPacks: Array(*GenericTypePack), self: ?*Local, args: Array(*Local), returnAnnotation: ?*TypePack, vararg: bool = false, varargLocation: Location, varargAnnotation: ?*TypePack, body: *StatBlock, functionDepth: usize, debugname: Name, argLocation: cpp_std.Optional(Location), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.args.slice()) |arg| if (arg.annotation) |node| try node.visit(visitor); if (self.varargAnnotation) |node| try node.visit(visitor); if (self.returnAnnotation) |annotation| try annotation.visit(visitor); try self.body.visit(visitor); } } pub fn hasNativeAttribute(self: *ExprFunction) bool { for (self.attributes.slice()) |attr| { if (attr.type == .Native) return true; } return false; } pub fn hasAttribute(self: *ExprFunction, attrType: Attr.Type) bool { for (self.attributes.slice()) |attr| { if (attr.type == attrType) return true; } return false; } }; pub const ExprTable = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_table, location: Location, items: Array(Item), pub const Item = extern struct { pub const Kind = enum(c_int) { List, // foo, in which case key is a nullptr Record, // foo=bar, in which case key is a AstExprConstantString General, // [foo]=bar }; kind: Kind, /// can be nullptr! key: ?*Expr, value: *Expr, pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; }; pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.items.slice()) |item| { if (item.key) |key| try key.visit(visitor); try item.value.visit(visitor); } } } }; pub const ExprUnary = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_unary, location: Location, op: Op, expr: *Expr, pub const Op = enum(c_int) { Not = 0, Minus = 1, Len = 2, pub fn toString(self: Op) []const u8 { return switch (self) { .Not => "not", .Minus => "-", .Len => "#", }; } }; pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.expr.visit(visitor); } } }; pub const ExprBinary = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_binary, location: Location, op: Op, left: *Expr, right: *Expr, pub const Op = enum(c_int) { Add = 0, Sub = 1, Mul = 2, Div = 3, FloorDiv = 4, Mod = 5, Pow = 6, Concat = 7, CompareNe = 8, CompareEq = 9, CompareLt = 10, CompareLe = 11, CompareGt = 12, CompareGe = 13, And = 14, Or = 15, __Count = 16, pub fn toString(self: Op) []const u8 { return switch (self) { .Add => "+", .Sub => "-", .Mul => "*", .Div => "/", .FloorDiv => "//", .Mod => "%", .Pow => "^", .Concat => "..", .CompareNe => "~=", .CompareEq => "==", .CompareLt => "<", .CompareLe => "<=", .CompareGt => ">", .CompareGe => ">=", .And => "and", .Or => "or", .__Count => unreachable, }; } }; pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.left.visit(visitor); try self.right.visit(visitor); } } }; pub const ExprTypeAssertion = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_type_assertion, location: Location, expr: *Expr, annotation: *Type, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.expr.visit(visitor); try self.annotation.visit(visitor); } } }; pub const ExprIfElse = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_if_else, location: Location, condition: *Expr, hasThen: bool, trueExpr: *Expr, hasElse: bool, falseExpr: *Expr, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.condition.visit(visitor); try self.trueExpr.visit(visitor); try self.falseExpr.visit(visitor); } } }; pub const ExprInterpString = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_interp_string, location: Location, /// An interpolated string such as `foo{bar}baz` is represented as /// an array of strings for "foo" and "bar", and an array of expressions for "baz". /// `strings` will always have one more element than `expressions`. strings: Array(Array(u8)), expressions: Array(*Expr), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.expressions.slice()) |expr| try expr.visit(visitor); } } }; /// f<> pub const ExprInstantiate = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_instantiate, location: Location, expr: *Expr, typeArguments: Array(TypeOrPack), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.expr.visit(visitor); try TypeOrPack.visitArray(self.typeArguments, visitor); } } }; pub const StatBlock = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_block, location: Location, hasSemicolon: bool = false, body: Array(*Stat), /// Indicates whether or not this block has been terminated in a /// syntactically valid way. /// /// This is usually but not always done with the 'end' keyword. StatIf /// and StatRepeat are the two main exceptions to this. /// /// The 'then' clause of an if statement can properly be closed by the /// keywords 'else' or 'elseif'. A 'repeat' loop's body is closed with the /// 'until' keyword. hasEnd: bool = false, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) for (self.body.slice()) |stat| try stat.visit(visitor); } }; pub const StatIf = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_if, location: Location, hasSemicolon: bool = false, condition: *Expr, thenbody: *StatBlock, elsebody: ?*Stat, thenLocation: cpp_std.Optional(Location), /// Active for 'elseif' as well elseLocation: cpp_std.Optional(Location), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.condition.visit(visitor); try self.thenbody.visit(visitor); if (self.elsebody) |elsebody| try elsebody.visit(visitor); } } }; pub const StatWhile = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_while, location: Location, hasSemicolon: bool = false, condition: *Expr, body: *StatBlock, hasDo: bool = false, doLocation: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.condition.visit(visitor); try self.body.visit(visitor); } } }; pub const StatRepeat = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_repeat, location: Location, hasSemicolon: bool = false, condition: *Expr, body: *StatBlock, DEPRECATED_hasUntil: bool = false, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.body.visit(visitor); try self.condition.visit(visitor); } } }; pub const StatBreak = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_break, location: Location, hasSemicolon: bool = false, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const StatContinue = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_continue, location: Location, hasSemicolon: bool = false, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const StatReturn = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_return, location: Location, hasSemicolon: bool = false, list: Array(*Expr), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.list.slice()) |expr| try expr.visit(visitor); } } }; pub const StatExpr = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_expr, location: Location, hasSemicolon: bool = false, expr: *Expr, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) try self.expr.visit(visitor); } }; pub const StatLocal = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_local, location: Location, hasSemicolon: bool = false, vars: Array(*Local), values: Array(*Expr), isConst: bool = false, isExported: bool = false, keywordLocation: cpp_std.Optional(Location), equalsSignLocation: cpp_std.Optional(Location), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.vars.slice()) |@"var"| if (@"var".annotation) |node| try node.visit(visitor); for (self.values.slice()) |expr| try expr.visit(visitor); } } }; pub const StatFor = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_for, location: Location, hasSemicolon: bool = false, variable: *Local, from: *Expr, to: *Expr, step: ?*Expr, body: *StatBlock, hasDo: bool = false, doLocation: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { if (self.variable.annotation) |node| try node.visit(visitor); try self.from.visit(visitor); try self.to.visit(visitor); if (self.step) |step| try step.visit(visitor); try self.body.visit(visitor); } } }; pub const StatForIn = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_for_in, location: Location, hasSemicolon: bool = false, vars: Array(*Local), values: Array(*Expr), body: *StatBlock, hasIn: bool = false, inLocation: Location, hasDo: bool = false, doLocation: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.vars.slice()) |@"var"| if (@"var".annotation) |node| try node.visit(visitor); for (self.values.slice()) |expr| try expr.visit(visitor); try self.body.visit(visitor); } } }; pub const StatAssign = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_assign, location: Location, hasSemicolon: bool = false, vars: Array(*Expr), values: Array(*Expr), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.vars.slice()) |lvalue| try lvalue.visit(visitor); for (self.values.slice()) |expr| try expr.visit(visitor); } } }; pub const StatCompoundAssign = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_compound_assign, location: Location, hasSemicolon: bool = false, op: ExprBinary.Op, variable: *Expr, value: *Expr, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.variable.visit(visitor); try self.value.visit(visitor); } } }; pub const StatFunction = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_function, location: Location, hasSemicolon: bool = false, name: *Expr, func: *ExprFunction, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.name.visit(visitor); try self.func.visit(visitor); } } }; pub const StatLocalFunction = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_local_function, location: Location, hasSemicolon: bool = false, name: *Local, func: *ExprFunction, isConst: bool = false, /// Position of the `const` keyword; Position::missing() when isConst is false. constKeywordBegin: Location.Position, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.func.visit(visitor); } } }; pub const StatTypeAlias = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_type_alias, location: Location, hasSemicolon: bool = false, name: Name, nameLocation: Location, generics: Array(*GenericType), genericPacks: Array(*GenericTypePack), type: *Type, exported: bool, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.generics.slice()) |el| try el.visit(visitor); for (self.genericPacks.slice()) |el| try el.visit(visitor); try self.type.visit(visitor); } } }; pub const StatTypeFunction = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_type_function, location: Location, hasSemicolon: bool = false, name: Name, nameLocation: Location, body: *ExprFunction = undefined, exported: bool = false, hasErrors: bool = false, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.body.visit(visitor); } } }; pub const StatDeclareGlobal = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_declare_global, location: Location, hasSemicolon: bool = false, name: Name, nameLocation: Location, type: *Type, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.type.visit(visitor); } } }; pub const ArgumentName = extern struct { name: Name, location: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; }; pub const StatDeclareFunction = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_declare_function, location: Location, hasSemicolon: bool = false, attributes: Array(*Attr), name: Name, nameLocation: Location, generics: Array(*GenericType), genericPacks: Array(*GenericTypePack), params: TypeList, paramNames: Array(ArgumentName), vararg: bool = false, varargLocation: Location, retTypes: *TypePack, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try Visitor.visitTypeList(visitor, self.params); try self.retTypes.visit(visitor); } } pub fn isCheckedFunction(self: *StatDeclareFunction) bool { for (self.attributes.slice()) |attr| { if (attr.type == .Checked) return true; } return false; } pub fn hasAttribute(self: *StatDeclareFunction, attrType: Attr.Type) bool { for (self.attributes.slice()) |attr| { if (attr.type == attrType) return true; } return false; } }; pub const TableAccess = enum(c_int) { Read = 1, Write = 2, ReadWrite = 3, }; pub const DeclaredExternTypeProperty = extern struct { name: Name, nameLocation: Location, ty: *Type = undefined, isMethod: bool = false, location: Location, access: TableAccess = .ReadWrite, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; }; pub const ClassProperty = extern struct { qualifierLocation: Location, name: Name, nameLocation: Location, typeColonLocation: cpp_std.Optional(Location) = .nullopt, ty: ?*Type = null, }; pub const ClassMethod = extern struct { qualifierLocation: cpp_std.Optional(Location), keywordLocation: Location, functionName: Name, nameLocation: Location, function: *ExprFunction, }; const ClassMember = Variant(&.{ ClassProperty, ClassMethod }); pub const StatClass = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_class, location: Location, hasSemicolon: bool = false, name: Name, members: Array(ClassMember), exported: bool, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.members.slice()) |member| { switch (member.typeId) { 0 => if (member.@"union"().@"0".ty) |ty| try ty.visit(visitor), 1 => try member.@"union"().@"1".function.visit(visitor), else => unreachable, } } } } }; pub const TableIndexer = extern struct { indexType: *Type, resultType: *Type, location: Location, access: TableAccess = .ReadWrite, accessLocation: cpp_std.Optional(Location), }; pub const StatDeclareExternType = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_declare_extern_type, location: Location, hasSemicolon: bool = false, name: Name, superName: cpp_std.Optional(Name), props: Array(DeclaredExternTypeProperty), indexer: *TableIndexer, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.props.slice()) |prop| try prop.ty.visit(visitor); } } }; pub const Type = extern struct { vtable: *const anyopaque, classIndex: Node.Kind, location: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { return Visitor.selfVisit(visitor, self); } }; /// Don't have Luau::Variant available, it's a bit of an overhead, but a plain struct is nice to use pub const TypeOrPack = extern struct { type: ?*Type = null, typePack: ?*TypePack = null, pub fn visitArray(self: Array(TypeOrPack), visitor: anytype) !void { for (self.slice()) |param| { if (param.type) |node| try node.visit(visitor) else try param.typePack.?.visit(visitor); } } }; pub const TypeReference = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_reference, location: Location, hasParameterList: bool, prefix: cpp_std.Optional(Name), prefixLocation: cpp_std.Optional(Location), prefixLocal: ?*Local = null, name: Name, nameLocation: Location, parameters: Array(TypeOrPack), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try TypeOrPack.visitArray(self.parameters, visitor); } } }; pub const TableProp = extern struct { name: Name, location: Location, type: *Type, access: TableAccess = .ReadWrite, accessLocation: cpp_std.Optional(Location), }; pub const TypeTable = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_table, location: Location, props: Array(TableProp), indexer: ?*TableIndexer, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.props.slice()) |node| try node.type.visit(visitor); if (self.indexer) |indexer| { try indexer.indexType.visit(visitor); try indexer.resultType.visit(visitor); } } } }; pub const TypeFunction = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_function, location: Location, attributes: Array(*Attr), generics: Array(*GenericType), genericPacks: Array(*GenericTypePack), argTypes: TypeList, argNames: Array(cpp_std.Optional(ArgumentName)), returnTypes: *TypePack, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try Visitor.visitTypeList(visitor, self.argTypes); try self.returnTypes.visit(visitor); } } pub fn isCheckedFunction(self: *TypeFunction) bool { for (self.attributes.slice()) |attr| { if (attr.type == .Checked) return true; } return false; } pub fn hasAttribute(self: *TypeFunction, attrType: Attr.Type) bool { for (self.attributes.slice()) |attr| { if (attr.type == attrType) return true; } return false; } }; pub const TypeTypeof = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_typeof, location: Location, expr: *Expr, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.expr.visit(visitor); } } }; pub const TypeOptional = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_optional, location: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const TypeUnion = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_union, location: Location, types: Array(*Type), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.types.slice()) |node| try node.visit(visitor); } } }; pub const TypeIntersection = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_intersection, location: Location, types: Array(*Type), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.types.slice()) |node| try node.visit(visitor); } } }; pub const ExprError = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .expr_error, location: Location, expressions: Array(*Expr), messageIndex: c_uint, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.expressions.slice()) |expression| try expression.visit(visitor); } } }; pub const StatError = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .stat_error, location: Location, MAYBE_hasSemicolon: bool = false, expressions: Array(*Expr), statements: Array(*Stat), messageIndex: c_uint, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.expressions.slice()) |expression| try expression.visit(visitor); for (self.statements.slice()) |statement| try statement.visit(visitor); } } }; pub const TypeError = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_error, location: Location, types: Array(*Type), isMissing: bool, messageIndex: c_uint, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.types.slice()) |node| try node.visit(visitor); } } }; pub const TypeSingletonBool = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_singleton_bool, location: Location, value: bool, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const TypeSingletonString = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_singleton_string, location: Location, value: Array(u8), pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const TypeGroup = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_group, location: Location, type: *Type, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { try self.type.visit(visitor); } } }; pub const TypePack = extern struct { vtable: *const anyopaque, classIndex: Node.Kind, location: Location, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { return Visitor.selfVisit(visitor, self); } }; pub const TypePackExplicit = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_pack_explicit, location: Location, typeList: TypeList, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) { for (self.typeList.types.slice()) |node| try node.visit(visitor); if (self.typeList.tailType) |node| try node.visit(visitor); } } }; pub const TypePackVariadic = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_pack_variadic, location: Location, variadicType: *Type, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { if (try Visitor.visit(visitor, self)) try self.variadicType.visit(visitor); } }; pub const TypePackGeneric = extern struct { vtable: *const anyopaque, classIndex: Node.Kind = .type_pack_generic, location: Location, genericName: Name, pub const is = IsFn; pub const as = AsCastFn; pub const asExpr = AsExprCastFn; pub const asStat = AsStatCastFn; pub const asType = AsTypeCastFn; pub fn visit(self: *@This(), visitor: anytype) !void { _ = try Visitor.visit(visitor, self); } }; pub const Visitor = struct { pub fn visitTypeList(self: anytype, this: TypeList) !void { for (this.types.slice()) |node| try node.visit(self); if (this.tailType) |node| try node.visit(self); } fn getVisitorStruct(comptime self: type) type { return switch (@typeInfo(self)) { .pointer => |ptr| ptr.child, .@"struct" => |s| s, else => |t| @compileError("Visitor type unsupported: " ++ @typeName(t)), }; } fn hasVistDecl(comptime self: type, comptime name: [:0]const u8) bool { return @hasDecl(getVisitorStruct(self), name); } fn callVisitorDecl(self: anytype, comptime name: [:0]const u8, this: anytype) !bool { const visitor_type = @TypeOf(self); const visitor_struct = getVisitorStruct(visitor_type); const visitor_fn = @field(visitor_struct, name); return switch (@typeInfo(visitor_type)) { .type => visitor_fn(@ptrCast(@alignCast(this))), .pointer, .@"struct" => visitor_fn(self, @ptrCast(@alignCast(this))), else => unreachable, }; } fn visitByName(self: anytype, comptime name: [:0]const u8, this: anytype) ?bool { const namespace = @typeName(Ast); const ast_name = name[namespace.len + 1 ..]; const fn_name = "visit" ++ ast_name; if (comptime hasVistDecl(@TypeOf(self), fn_name)) return callVisitorDecl(self, fn_name, this); return null; } fn getParent(comptime ast: type) type { const namespace = @typeName(Ast); const ast_name = @typeName(ast)[namespace.len + 1 ..]; if (std.mem.eql(u8, ast_name, "Attr") or std.mem.eql(u8, ast_name, "GenericType") or std.mem.eql(u8, ast_name, "GenericTypePack") or std.mem.eql(u8, ast_name, "Expr") or std.mem.eql(u8, ast_name, "Stat")) return Ast.Node else if (std.mem.startsWith(u8, ast_name, "Expr")) return Ast.Expr else if (std.mem.startsWith(u8, ast_name, "Stat")) return Ast.Stat else if (std.mem.startsWith(u8, ast_name, "TypePack")) return Ast.TypePack else if (std.mem.startsWith(u8, ast_name, "Type")) return Ast.Type; @compileError("Invalid Ast type"); } pub fn selfVisit(self: anytype, this: anytype) anyerror!void { switch (this.classIndex) { inline else => |kind| { const kind_type = kind.Type(); if (@hasDecl(kind_type, "visit")) try kind_type.visit(@ptrCast(@alignCast(this)), self); }, } } pub fn visit(self: anytype, this: anytype) anyerror!bool { const node_type = @typeInfo(@TypeOf(this)); const ast_type = node_type.pointer.child; comptime if (node_type != .pointer) @compileError("Invalid Ast type"); comptime if (!std.mem.startsWith(u8, @typeName(ast_type), @typeName(Ast))) @compileError("Invalid Ast type"); const namespace = @typeName(Ast); if (ast_type == Ast.Node) { if (comptime hasVistDecl(@TypeOf(self), "visit")) return callVisitorDecl(self, "visit", this); return true; } else if (ast_type == Ast.Type or ast_type == Ast.TypePack) { const ast_name = @typeName(ast_type)[namespace.len + 1 ..]; if (comptime hasVistDecl(@TypeOf(self), "visit" ++ ast_name)) return callVisitorDecl(self, "visit" ++ ast_name, this); return false; } else { const ast_name = @typeName(ast_type)[namespace.len + 1 ..]; const fn_name = "visit" ++ ast_name; if (comptime hasVistDecl(@TypeOf(self), fn_name)) return callVisitorDecl(self, fn_name, this); const parent = comptime getParent(ast_type); return Visitor.visit(self, @as(*parent, @ptrCast(@alignCast(this)))); } } }; test Node { const Lexer = @import("Lexer.zig"); const Parser = @import("Parser.zig"); const Allocator = @import("Allocator.zig"); { const allocator = Allocator.init(); defer allocator.deinit(); const table = Lexer.AstNameTable.init(allocator); defer table.deinit(); const source = \\local x = 1; \\local x = 2 \\local x = 3 \\ ; var parse_result = Parser.parse(source, table, allocator, .{}); defer parse_result.deinit(); const root = parse_result.root; try std.testing.expectEqual(Node.Kind.stat_block, root.classIndex); const stats = root.body.slice(); try std.testing.expectEqual(3, stats.len); for (stats, 1..) |node, order| { switch (node.classIndex) { .stat_local => { const local: *StatLocal = node.as(.stat_local).?; try std.testing.expectEqualStrings("x", std.mem.span(local.vars.slice()[0].name.value)); try std.testing.expect(@as(f64, @floatFromInt(order)) == local.values.slice()[0].as(.expr_constant_number).?.value); }, else => {}, } } } { const allocator = Allocator.init(); defer allocator.deinit(); const astNameTable = Lexer.AstNameTable.init(allocator); defer astNameTable.deinit(); const source = \\@native \\function test() \\end \\ ; const parseResult = Parser.parse(source, astNameTable, allocator, .{}); defer parseResult.deinit(); { const FunctionVisitor = struct { hasNativeFunction: bool = false, pub fn visitExprFunction(self: *@This(), node: *Ast.ExprFunction) !bool { errdefer unreachable; try node.body.visit(self); if (!self.hasNativeFunction and node.hasNativeAttribute()) self.hasNativeFunction = true; return false; } }; var visitor: FunctionVisitor = .{}; parseResult.root.visit(&visitor) catch unreachable; // no errors expected, since none of the visitor method does error try std.testing.expect(visitor.hasNativeFunction); } { const FunctionVisitor = struct { hasNativeFunction: bool = false, pub fn visitExprFunction(self: *@This(), node: *Ast.ExprFunction) !bool { try node.body.visit(self); if (!self.hasNativeFunction and node.hasNativeAttribute()) { self.hasNativeFunction = true; return error.Done; } return false; } }; var visitor: FunctionVisitor = .{}; parseResult.root.visit(&visitor) catch |err| switch (err) { error.Done => {}, // this error is defined in the visitor else => unreachable, }; try std.testing.expect(visitor.hasNativeFunction); } } } test "AstValuesCheck" { if (@import("builtin").cpu.arch.isWasm() or @import("builtin").os.tag == .windows) return error.SkipZigTest; const AstValues = struct { pub extern "c" const AstAttrIndex: u8; pub extern "c" const AstGenericTypeIndex: u8; pub extern "c" const AstGenericTypePackIndex: u8; pub extern "c" const AstExprGroupIndex: u8; pub extern "c" const AstExprConstantNilIndex: u8; pub extern "c" const AstExprConstantBoolIndex: u8; pub extern "c" const AstExprConstantNumberIndex: u8; pub extern "c" const AstExprConstantIntegerIndex: u8; pub extern "c" const AstExprConstantStringIndex: u8; pub extern "c" const AstExprLocalIndex: u8; pub extern "c" const AstExprGlobalIndex: u8; pub extern "c" const AstExprVarargsIndex: u8; pub extern "c" const AstExprCallIndex: u8; pub extern "c" const AstExprIndexNameIndex: u8; pub extern "c" const AstExprIndexExprIndex: u8; pub extern "c" const AstExprFunctionIndex: u8; pub extern "c" const AstExprTableIndex: u8; pub extern "c" const AstExprUnaryIndex: u8; pub extern "c" const AstExprBinaryIndex: u8; pub extern "c" const AstExprTypeAssertionIndex: u8; pub extern "c" const AstExprIfElseIndex: u8; pub extern "c" const AstExprInterpStringIndex: u8; pub extern "c" const AstExprInstantiateIndex: u8; pub extern "c" const AstStatBlockIndex: u8; pub extern "c" const AstStatIfIndex: u8; pub extern "c" const AstStatWhileIndex: u8; pub extern "c" const AstStatRepeatIndex: u8; pub extern "c" const AstStatBreakIndex: u8; pub extern "c" const AstStatContinueIndex: u8; pub extern "c" const AstStatReturnIndex: u8; pub extern "c" const AstStatExprIndex: u8; pub extern "c" const AstStatLocalIndex: u8; pub extern "c" const AstStatForIndex: u8; pub extern "c" const AstStatForInIndex: u8; pub extern "c" const AstStatAssignIndex: u8; pub extern "c" const AstStatCompoundAssignIndex: u8; pub extern "c" const AstStatFunctionIndex: u8; pub extern "c" const AstStatLocalFunctionIndex: u8; pub extern "c" const AstStatTypeAliasIndex: u8; pub extern "c" const AstStatTypeFunctionIndex: u8; pub extern "c" const AstStatDeclareFunctionIndex: u8; pub extern "c" const AstStatDeclareGlobalIndex: u8; pub extern "c" const AstStatClassIndex: u8; pub extern "c" const AstStatDeclareExternTypeIndex: u8; pub extern "c" const AstTypeReferenceIndex: u8; pub extern "c" const AstTypeTableIndex: u8; pub extern "c" const AstTypeFunctionIndex: u8; pub extern "c" const AstTypeTypeofIndex: u8; pub extern "c" const AstTypeOptionalIndex: u8; pub extern "c" const AstTypeUnionIndex: u8; pub extern "c" const AstTypeIntersectionIndex: u8; pub extern "c" const AstExprErrorIndex: u8; pub extern "c" const AstStatErrorIndex: u8; pub extern "c" const AstTypeErrorIndex: u8; pub extern "c" const AstTypeSingletonBoolIndex: u8; pub extern "c" const AstTypeSingletonStringIndex: u8; pub extern "c" const AstTypeGroupIndex: u8; pub extern "c" const AstTypePackExplicitIndex: u8; pub extern "c" const AstTypePackVariadicIndex: u8; pub extern "c" const AstTypePackGenericIndex: u8; pub extern "c" const AstAttrSize: usize; pub extern "c" const AstGenericTypeSize: usize; pub extern "c" const AstGenericTypePackSize: usize; pub extern "c" const AstExprGroupSize: usize; pub extern "c" const AstExprConstantNilSize: usize; pub extern "c" const AstExprConstantBoolSize: usize; pub extern "c" const AstExprConstantNumberSize: usize; pub extern "c" const AstExprConstantIntegerSize: usize; pub extern "c" const AstExprConstantStringSize: usize; pub extern "c" const AstExprLocalSize: usize; pub extern "c" const AstExprGlobalSize: usize; pub extern "c" const AstExprVarargsSize: usize; pub extern "c" const AstExprCallSize: usize; pub extern "c" const AstExprIndexNameSize: usize; pub extern "c" const AstExprIndexExprSize: usize; pub extern "c" const AstExprFunctionSize: usize; pub extern "c" const AstExprTableSize: usize; pub extern "c" const AstExprUnarySize: usize; pub extern "c" const AstExprBinarySize: usize; pub extern "c" const AstExprTypeAssertionSize: usize; pub extern "c" const AstExprIfElseSize: usize; pub extern "c" const AstExprInterpStringSize: usize; pub extern "c" const AstExprInstantiateSize: usize; pub extern "c" const AstStatBlockSize: usize; pub extern "c" const AstStatIfSize: usize; pub extern "c" const AstStatWhileSize: usize; pub extern "c" const AstStatRepeatSize: usize; pub extern "c" const AstStatBreakSize: usize; pub extern "c" const AstStatContinueSize: usize; pub extern "c" const AstStatReturnSize: usize; pub extern "c" const AstStatExprSize: usize; pub extern "c" const AstStatLocalSize: usize; pub extern "c" const AstStatForSize: usize; pub extern "c" const AstStatForInSize: usize; pub extern "c" const AstStatAssignSize: usize; pub extern "c" const AstStatCompoundAssignSize: usize; pub extern "c" const AstStatFunctionSize: usize; pub extern "c" const AstStatLocalFunctionSize: usize; pub extern "c" const AstStatTypeAliasSize: usize; pub extern "c" const AstStatTypeFunctionSize: usize; pub extern "c" const AstStatDeclareFunctionSize: usize; pub extern "c" const AstStatDeclareGlobalSize: usize; pub extern "c" const AstStatClassSize: usize; pub extern "c" const AstStatDeclareExternTypeSize: usize; pub extern "c" const AstTypeReferenceSize: usize; pub extern "c" const AstTypeTableSize: usize; pub extern "c" const AstTypeFunctionSize: usize; pub extern "c" const AstTypeTypeofSize: usize; pub extern "c" const AstTypeOptionalSize: usize; pub extern "c" const AstTypeUnionSize: usize; pub extern "c" const AstTypeIntersectionSize: usize; pub extern "c" const AstExprErrorSize: usize; pub extern "c" const AstStatErrorSize: usize; pub extern "c" const AstTypeErrorSize: usize; pub extern "c" const AstTypeSingletonBoolSize: usize; pub extern "c" const AstTypeSingletonStringSize: usize; pub extern "c" const AstTypeGroupSize: usize; pub extern "c" const AstTypePackExplicitSize: usize; pub extern "c" const AstTypePackVariadicSize: usize; pub extern "c" const AstTypePackGenericSize: usize; }; @setEvalBranchQuota(2000); inline for (@typeInfo(AstValues).@"struct".decls) |decl| { if (comptime std.mem.endsWith(u8, decl.name, "Index")) { const name = decl.name[3 .. decl.name.len - 5]; const ast_node_type = @field(Ast, name); const info = @typeInfo(ast_node_type).@"struct"; comptime var field: ?std.builtin.Type.StructField = null; inline for (info.fields) |f| { if (comptime std.mem.eql(u8, f.name, "classIndex")) { field = f; break; } } if (field == null) @compileError("classIndex field not found"); const default_value_ptr = field.?.default_value_ptr orelse @compileError("classIndex field does not have a default value"); const enum_value = @as(*const Ast.Node.Kind, @ptrCast(@alignCast(default_value_ptr))).*; std.testing.expectEqual(@field(AstValues, decl.name), @intFromEnum(enum_value)) catch |err| { std.debug.print("index error for {s}\n", .{name}); return err; }; } else if (comptime std.mem.endsWith(u8, decl.name, "Size")) { const name = decl.name[3 .. decl.name.len - 4]; const ast_node_type = @field(Ast, name); std.testing.expectEqual(@field(AstValues, decl.name), @sizeOf(ast_node_type)) catch |err| { std.debug.print("size error for {s}\n", .{name}); return err; }; } else @compileError("unknown exported constant"); } } // sources: // https://github.com/luau-lang/luau/blob/40d4815888f63362a6cb79b3e74c4aafa0b2cbf4/Ast/include/Luau/Ast.h // https://github.com/luau-lang/luau/blob/40d4815888f63362a6cb79b3e74c4aafa0b2cbf4/Ast/src/Ast.cpp