diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f06babf7..a1364df7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -24,14 +24,33 @@ jobs: version: master - name: Build test ffi lib run: zig build-lib -dynamic tests/utils/foreign.zig && mv libforeign.* tests/utils/ - - name: Run Tests - run: BUZZ_PATH=./dist zig build -Doptimize=ReleaseSafe -p dist test + + - name: Run tests Debug + run: zig build test + - name: Cleanup + run: rm -rf zig-out zig-cache + - name: Run tests Debug with JIT always on + run: zig build -Djit_always_on test + - name: Cleanup + run: rm -rf zig-out zig-cache + + - name: Run tests ReleaseSafe + run: zig build -Doptimize=ReleaseSafe test + - name: Cleanup + run: rm -rf zig-out zig-cache + - name: Run tests ReleaseSafe with JIT always on + run: zig build -Doptimize=ReleaseSafe -Djit_always_on test + - name: Cleanup + run: rm -rf zig-out zig-cache + + - name: Run tests ReleaseFast + run: zig build -Doptimize=ReleaseFast test - name: Cleanup - run: rm -rf zig-cache dist - - name: Run tests with JIT always on - run: BUZZ_PATH=./dist zig build -Djit_always_on -Doptimize=ReleaseSafe -p dist test + run: rm -rf zig-out zig-cache + - name: Run tests ReleaseFast with JIT always on + run: zig build -Doptimize=ReleaseFast -Djit_always_on test - name: Cleanup - run: rm -rf zig-cache dist + run: rm -rf zig-out zig-cache test-linux: runs-on: ubuntu-latest steps: @@ -47,14 +66,33 @@ jobs: version: master - name: Build test ffi lib run: zig build-lib -dynamic tests/utils/foreign.zig && mv libforeign.* tests/utils/ - - name: Run tests - run: BUZZ_PATH=./dist zig build -Doptimize=ReleaseSafe -p dist test + + - name: Run tests Debug + run: zig build test + - name: Cleanup + run: rm -rf zig-out zig-cache + - name: Run tests Debug with JIT always on + run: zig build -Djit_always_on test + - name: Cleanup + run: rm -rf zig-out zig-cache + + - name: Run tests ReleaseSafe + run: zig build -Doptimize=ReleaseSafe test + - name: Cleanup + run: rm -rf zig-out zig-cache + - name: Run tests ReleaseSafe with JIT always on + run: zig build -Doptimize=ReleaseSafe -Djit_always_on test + - name: Cleanup + run: rm -rf zig-out zig-cache + + - name: Run tests ReleaseFast + run: zig build -Doptimize=ReleaseFast test - name: Cleanup - run: rm -rf zig-cache dist - - name: Run tests with JIT always on - run: BUZZ_PATH=./dist zig build -Djit_always_on -Doptimize=ReleaseSafe -p dist test + run: rm -rf zig-out zig-cache + - name: Run tests ReleaseFast with JIT always on + run: zig build -Doptimize=ReleaseFast -Djit_always_on test - name: Cleanup - run: rm -rf zig-cache dist + run: rm -rf zig-out zig-cache lint: runs-on: macos-latest steps: diff --git a/build.zig b/build.zig index 5bae0d32..0c123dd4 100644 --- a/build.zig +++ b/build.zig @@ -1,6 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); -const Build = std.build; +const Build = std.Build; const BuzzDebugOptions = struct { debug: bool, @@ -10,7 +10,7 @@ const BuzzDebugOptions = struct { stop_on_report: bool, placeholders: bool, - pub fn step(self: BuzzDebugOptions, options: *std.build.OptionsStep) void { + pub fn step(self: BuzzDebugOptions, options: *Build.Step.Options) void { options.addOption(@TypeOf(self.debug), "debug", self.debug); options.addOption(@TypeOf(self.stack), "debug_stack", self.stack); options.addOption(@TypeOf(self.current_instruction), "debug_current_instruction", self.current_instruction); @@ -26,7 +26,7 @@ const BuzzJITOptions = struct { debug: bool, prof_threshold: f128 = 0.05, - pub fn step(self: BuzzJITOptions, options: *std.build.OptionsStep) void { + pub fn step(self: BuzzJITOptions, options: *Build.Step.Options) void { options.addOption(@TypeOf(self.debug), "jit_debug", self.debug); options.addOption(@TypeOf(self.always_on), "jit_always_on", self.always_on); options.addOption(@TypeOf(self.on), "jit", self.on); @@ -43,7 +43,7 @@ const BuzzGCOptions = struct { next_gc_ratio: usize, next_full_gc_ratio: usize, - pub fn step(self: BuzzGCOptions, options: *std.build.OptionsStep) void { + pub fn step(self: BuzzGCOptions, options: *Build.Step.Options) void { options.addOption(@TypeOf(self.debug), "gc_debug", self.debug); options.addOption(@TypeOf(self.debug_light), "gc_debug_light", self.debug_light); options.addOption(@TypeOf(self.debug_access), "gc_debug_access", self.debug_access); @@ -61,9 +61,9 @@ const BuzzBuildOptions = struct { debug: BuzzDebugOptions, gc: BuzzGCOptions, jit: BuzzJITOptions, - target: std.zig.CrossTarget, + target: Build.ResolvedTarget, - pub fn step(self: @This(), b: *Build) *std.build.OptionsStep { + pub fn step(self: @This(), b: *Build) *Build.Module { var options = b.addOptions(); options.addOption(@TypeOf(self.version), "version", self.version); options.addOption(@TypeOf(self.sha), "sha", self.sha); @@ -73,15 +73,11 @@ const BuzzBuildOptions = struct { self.gc.step(options); self.jit.step(options); - return options; + return options.createModule(); } - pub fn needLibC(self: @This()) bool { - // TODO: remove libc if possible - // mir can be built with musl libc - // mimalloc can be built with musl libc - // longjmp/setjmp need to be removed - return self.target.isLinux() or self.mimalloc; + pub fn needLibC(_: @This()) bool { + return true; } }; @@ -92,7 +88,7 @@ fn get_buzz_prefix(b: *Build) []const u8 { pub fn build(b: *Build) !void { // Check minimum zig version const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.12.0-dev.1253+b798aaf49") catch return; + const min_zig = std.SemanticVersion.parse("0.12.0-dev.2208+4debd4338") catch return; if (current_zig.order(min_zig).compare(.lt)) { @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); } @@ -240,6 +236,8 @@ pub fn build(b: *Build) !void { }, }; + const build_option_module = build_options.step(b); + var sys_libs = std.ArrayList([]const u8).init(b.allocator); defer sys_libs.deinit(); var includes = std.ArrayList([]const u8).init(b.allocator); @@ -296,7 +294,7 @@ pub fn build(b: *Build) !void { var exe = b.addExecutable(.{ .name = "buzz", - .root_source_file = Build.FileSource.relative("src/main.zig"), + .root_source_file = .{ .path = "src/main.zig" }, .target = target, .optimize = build_mode, }); @@ -323,13 +321,13 @@ pub fn build(b: *Build) !void { if (build_options.needLibC()) { exe.linkLibC(); } - exe.main_mod_path = .{ .path = "." }; + // exe.main_mod_path = .{ .path = "." }; - exe.addOptions("build_options", build_options.step(b)); + exe.root_module.addImport("build_options", build_option_module); var lib = b.addSharedLibrary(.{ .name = "buzz", - .root_source_file = Build.FileSource.relative("src/buzz_api.zig"), + .root_source_file = .{ .path = "src/buzz_api.zig" }, .target = target, .optimize = build_mode, }); @@ -346,14 +344,14 @@ pub fn build(b: *Build) !void { if (build_options.needLibC()) { lib.linkLibC(); } - lib.main_mod_path = .{ .path = "src" }; + // lib.main_mod_path = .{ .path = "src" }; - lib.addOptions("build_options", build_options.step(b)); + lib.root_module.addImport("build_options", build_option_module); lib.linkLibrary(lib_pcre2); if (lib_mimalloc) |mimalloc| { lib.linkLibrary(mimalloc); - if (lib.target.getOsTag() == .windows) { + if (lib.root_module.resolved_target.?.result.os.tag == .windows) { lib.linkSystemLibrary("bcrypt"); } } @@ -412,11 +410,11 @@ pub fn build(b: *Build) !void { // TODO: this section is slow. Modifying Buzz parser shouldn't trigger recompile of all buzz dynamic libraries - var libs = [_]*std.build.LibExeObjStep{undefined} ** lib_names.len; + var libs = [_]*std.Build.Step.Compile{undefined} ** lib_names.len; for (lib_paths, 0..) |lib_path, index| { var std_lib = b.addSharedLibrary(.{ .name = lib_names[index], - .root_source_file = Build.FileSource.relative(lib_path), + .root_source_file = .{ .path = lib_path }, .target = target, .optimize = build_mode, }); @@ -436,16 +434,16 @@ pub fn build(b: *Build) !void { if (build_options.needLibC()) { std_lib.linkLibC(); } - std_lib.main_mod_path = .{ .path = "src" }; + // std_lib.main_mod_path = .{ .path = "src" }; std_lib.linkLibrary(lib_pcre2); if (lib_mimalloc) |mimalloc| { std_lib.linkLibrary(mimalloc); - if (std_lib.target.getOsTag() == .windows) { + if (std_lib.root_module.resolved_target.?.result.os.tag == .windows) { std_lib.linkSystemLibrary("bcrypt"); } } std_lib.linkLibrary(lib); - std_lib.addOptions("build_options", build_options.step(b)); + std_lib.root_module.addImport("build_options", build_option_module); // Adds `$BUZZ_PATH/lib` and `/usr/local/lib/buzz` as search path for other shared lib referenced by this one (libbuzz.dylib most of the time) std_lib.addRPath( @@ -465,14 +463,14 @@ pub fn build(b: *Build) !void { for (all_lib_names) |name| { const step = b.addInstallLibFile( - std.build.FileSource.relative(b.fmt("src/lib/{s}.buzz", .{name})), + .{ .path = b.fmt("src/lib/{s}.buzz", .{name}) }, b.fmt("buzz/{s}.buzz", .{name}), ); install_step.dependOn(&step.step); } const tests = b.addTest(.{ - .root_source_file = Build.FileSource.relative("src/main.zig"), + .root_source_file = .{ .path = "src/main.zig" }, .target = target, .optimize = build_mode, }); @@ -491,11 +489,11 @@ pub fn build(b: *Build) !void { tests.linkLibrary(lib_pcre2); if (lib_mimalloc) |mimalloc| { tests.linkLibrary(mimalloc); - if (tests.target.getOsTag() == .windows) { + if (tests.root_module.resolved_target.?.result.os.tag == .windows) { tests.linkSystemLibrary("bcrypt"); } } - tests.addOptions("build_options", build_options.step(b)); + tests.root_module.addImport("build_options", build_option_module); const test_step = b.step("test", "Run all the tests"); const run_tests = b.addRunArtifact(tests); @@ -505,7 +503,7 @@ pub fn build(b: *Build) !void { test_step.dependOn(&run_tests.step); } -pub fn buildPcre2(b: *Build, target: std.zig.CrossTarget, optimize: std.builtin.OptimizeMode) !*Build.Step.Compile { +pub fn buildPcre2(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !*Build.Step.Compile { const copyFiles = b.addWriteFiles(); copyFiles.addCopyFileToSource( .{ .path = "vendors/pcre2/src/config.h.generic" }, @@ -571,7 +569,7 @@ pub fn buildPcre2(b: *Build, target: std.zig.CrossTarget, optimize: std.builtin. return lib; } -pub fn buildMimalloc(b: *Build, target: std.zig.CrossTarget, optimize: std.builtin.OptimizeMode) !*Build.Step.Compile { +pub fn buildMimalloc(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !*Build.Step.Compile { const lib = b.addStaticLibrary( .{ .name = "mimalloc", @@ -583,7 +581,7 @@ pub fn buildMimalloc(b: *Build, target: std.zig.CrossTarget, optimize: std.built lib.addIncludePath(.{ .path = "./vendors/mimalloc/include" }); lib.linkLibC(); - if (lib.target.getOsTag() == .macos) { + if (lib.root_module.resolved_target.?.result.os.tag == .macos) { var macOS_sdk_path = std.ArrayList(u8).init(b.allocator); try macOS_sdk_path.writer().print( "{s}/usr/include", @@ -630,7 +628,7 @@ pub fn buildMimalloc(b: *Build, target: std.zig.CrossTarget, optimize: std.built "./vendors/mimalloc/src/stats.c", "./vendors/mimalloc/src/prim/prim.c", }, - .flags = if (lib.optimize != .Debug) + .flags = if (lib.root_module.optimize != .Debug) &.{ "-DNDEBUG=1", "-DMI_SECURE=0", @@ -644,7 +642,7 @@ pub fn buildMimalloc(b: *Build, target: std.zig.CrossTarget, optimize: std.built return lib; } -pub fn buildLinenoise(b: *Build, target: std.zig.CrossTarget, optimize: std.builtin.OptimizeMode) !*Build.Step.Compile { +pub fn buildLinenoise(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) !*Build.Step.Compile { const lib = b.addStaticLibrary(.{ .name = "linenoise", .target = target, diff --git a/src/Ast.zig b/src/Ast.zig new file mode 100644 index 00000000..d4744c0a --- /dev/null +++ b/src/Ast.zig @@ -0,0 +1,1047 @@ +const std = @import("std"); +const obj = @import("obj.zig"); +const Token = @import("Token.zig"); +const Chunk = @import("Chunk.zig"); +const v = @import("value.zig"); +const Value = v.Value; +const FFI = @import("FFI.zig"); +const Parser = @import("Parser.zig"); +const GarbageCollector = @import("memory.zig").GarbageCollector; +// TODO: cleanup Error sets! +const Error = @import("Codegen.zig").Error; + +const Self = @This(); + +// Since the AST must live for the whole program lifetime (can be used by the JIT) +// there's no deinit functions. Everything should be allocated with an ArenaAllocator + +pub const TokenIndex = u32; +pub const TokenList = std.MultiArrayList(Token); +pub const NodeList = std.MultiArrayList(Node); + +allocator: std.mem.Allocator, +tokens: TokenList, +nodes: NodeList, +root: ?Node.Index = null, + +pub fn init(allocator: std.mem.Allocator) Self { + return .{ + .allocator = allocator, + .tokens = TokenList{}, + .nodes = NodeList{}, + }; +} + +pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + self.tokens.deinit(allocator); + self.nodes.deinit(allocator); +} + +pub inline fn appendNode(self: *Self, node: Node) !Node.Index { + try self.nodes.append(self.allocator, node); + + return @intCast(self.nodes.len - 1); +} + +pub inline fn appendToken(self: *Self, token: Token) !TokenIndex { + try self.tokens.append(self.allocator, token); + + return @intCast(self.tokens.len - 1); +} + +pub const Node = struct { + tag: Tag, + /// First token of this node + location: TokenIndex, + /// Last token of this node + end_location: TokenIndex, + /// Docblock if any + docblock: ?TokenIndex = null, + + /// If null, either its a statement or its a reference to something unknown that should ultimately raise a compile error + type_def: ?*obj.ObjTypeDef = null, + /// Wether optional jumps must be patch before generate this node bytecode + patch_opt_jumps: bool = false, + /// Does this node closes a scope + ends_scope: ?[]const Chunk.OpCode = null, + + /// Data related to this node + components: Components, + + /// To avoid generating a node const value multiple times + value: ?Value = null, + + pub const Index = u32; + + pub const Tag = enum(u8) { + AnonymousObjectType, + As, + AsyncCall, + Binary, + Block, + Boolean, + Break, + Call, + Continue, + Dot, + DoUntil, + Enum, + Export, + Expression, + FiberType, + Float, + For, + ForceUnwrap, + ForEach, + Function, + FunctionType, + FunDeclaration, + GenericResolve, + GenericResolveType, + GenericType, + Grouping, + If, + Import, + Integer, + Is, + List, + ListType, + Map, + MapType, + NamedVariable, + Null, + ObjectDeclaration, + ObjectInit, + Pattern, + ProtocolDeclaration, + Range, + Resolve, + Resume, + Return, + SimpleType, + String, + StringLiteral, + Subscript, + Throw, + Try, + TypeExpression, + TypeOfExpression, + Unary, + Unwrap, + UserType, + VarDeclaration, + Void, + While, + Yield, + Zdef, + }; + + pub const Components = union(Tag) { + AnonymousObjectType: AnonymousObjectType, + As: IsAs, + AsyncCall: Node.Index, + Binary: Binary, + Block: []Node.Index, + Boolean: bool, + Break: void, + Call: Call, + Continue: void, + Dot: Dot, + DoUntil: WhileDoUntil, + Enum: Enum, + Export: Export, + Expression: Node.Index, + FiberType: FiberType, + Float: v.Float, + For: For, + ForceUnwrap: Unwrap, + ForEach: ForEach, + Function: Function, + FunctionType: FunctionType, + FunDeclaration: FunDeclaration, + GenericResolve: Node.Index, + GenericResolveType: GenericResolveType, + GenericType: void, + Grouping: Node.Index, + If: If, + Import: Import, + Integer: v.Integer, + Is: IsAs, + List: List, + ListType: ListType, + Map: Map, + MapType: MapType, + NamedVariable: NamedVariable, + Null: void, + ObjectDeclaration: ObjectDeclaration, + ObjectInit: ObjectInit, + Pattern: *obj.ObjPattern, + ProtocolDeclaration: ProtocolDeclaration, + Range: Range, + Resolve: Node.Index, + Resume: Node.Index, + Return: Return, + // The type should be taken from the type_def field and not the token + SimpleType: void, + String: []Node.Index, + StringLiteral: *obj.ObjString, + Subscript: Subscript, + Throw: Throw, + Try: Try, + TypeExpression: Node.Index, + TypeOfExpression: Node.Index, + Unary: Unary, + Unwrap: Unwrap, + UserType: UserType, + VarDeclaration: VarDeclaration, + Void: void, + While: WhileDoUntil, + Yield: Node.Index, + Zdef: Zdef, + }; +}; + +pub const AnonymousObjectType = struct { + fields: []Field, + + pub const Field = struct { + name: TokenIndex, + type: Node.Index, + }; +}; + +pub const Binary = struct { + left: Node.Index, + right: Node.Index, + operator: Token.Type, +}; + +pub const Call = struct { + is_async: bool, + callee: Node.Index, + // We need this because in a dot.call, callee is dot and its type will be == to call return type + callee_type_def: *obj.ObjTypeDef, + arguments: []Argument, + catch_default: ?Node.Index, + + pub const Argument = struct { + name: ?TokenIndex, + value: Node.Index, + }; +}; + +pub const Dot = struct { + pub const MemberKind = enum { + Ref, + Value, + Call, + EnumCase, + }; + + pub const Member = union(MemberKind) { + Ref: void, + Value: Node.Index, + Call: Node.Index, + EnumCase: u32, + }; + + callee: Node.Index, + identifier: TokenIndex, + member_kind: MemberKind, + value_or_call_or_enum: Member, + generic_resolve: ?Node.Index, + member_type_def: *obj.ObjTypeDef, +}; + +pub const Enum = struct { + name: TokenIndex, + case_type: ?Node.Index, + slot: Slot, + cases: []Case, + + pub const Case = struct { + name: TokenIndex, + docblock: ?TokenIndex, + value: ?Node.Index, + }; +}; + +pub const Export = struct { + identifier: ?TokenIndex, + alias: ?TokenIndex, + declaration: ?Node.Index, +}; + +pub const FiberType = struct { + return_type: Node.Index, + yield_type: Node.Index, +}; + +pub const For = struct { + init_declarations: []Node.Index, + condition: Node.Index, + post_loop: []Node.Index, + body: Node.Index, +}; + +pub const ForEach = struct { + iterable: Node.Index, + key: Node.Index, + value: Node.Index, + body: Node.Index, + key_omitted: bool, +}; + +pub const Function = struct { + var next_id: usize = 0; + + pub fn nextId() usize { + Function.next_id += 1; + + return Function.next_id; + } + + id: usize, + body: ?Node.Index = null, + docblock: ?TokenIndex = null, + // TODO: Could be in FunctionType + test_message: ?TokenIndex = null, + // Should be .FunctionType + // Only function without a function_signature is a script + function_signature: ?Node.Index, + + upvalue_binding: std.AutoArrayHashMap(u8, bool), + + // If the function is a ScritEntryPoint + entry: ?Entry = null, + + // Set when the function is first generated + // The JIT compiler can then reference it when creating its closure + native: ?*obj.ObjNative = null, + function: ?*obj.ObjFunction = null, + + import_root: bool = false, + + pub const Entry = struct { + main_slot: ?usize = null, + main_location: ?TokenIndex = null, + test_slots: []usize, + test_locations: []TokenIndex, + exported_count: usize = 0, + }; +}; + +pub const FunctionType = struct { + name: ?TokenIndex, + return_type: ?Node.Index, + yield_type: ?Node.Index, + error_types: []Node.Index, + arguments: []Argument, + generic_types: []TokenIndex, + lambda: bool, + + pub const Argument = struct { + name: TokenIndex, + type: Node.Index, + default: ?Node.Index, + }; +}; + +pub const FunDeclaration = struct { + function: Node.Index, + + slot: Slot, + slot_type: SlotType, +}; + +pub const GenericResolveType = struct { + resolved_types: []Node.Index, +}; + +pub const If = struct { + condition: Node.Index, + unwrapped_identifier: ?TokenIndex, + casted_type: ?Node.Index, + body: Node.Index, + else_branch: ?Node.Index, + is_statement: bool, +}; + +pub const Import = struct { + imported_symbols: []TokenIndex, + prefix: ?TokenIndex, + path: TokenIndex, + import: ?Parser.ScriptImport, +}; + +pub const IsAs = struct { + left: Node.Index, + constant: Node.Index, +}; + +pub const List = struct { + explicit_item_type: ?TokenIndex, + items: []Node.Index, +}; + +pub const ListType = struct { + item_type: Node.Index, +}; + +pub const Map = struct { + explicit_key_type: ?Node.Index, + explicit_value_type: ?Node.Index, + + entries: []Entry, + + pub const Entry = struct { + key: Node.Index, + value: Node.Index, + }; +}; + +pub const MapType = struct { + key_type: Node.Index, + value_type: Node.Index, +}; + +pub const SlotType = enum(u8) { + Local, + UpValue, + Global, +}; + +pub const Slot = u32; + +pub const NamedVariable = struct { + identifier: TokenIndex, + value: ?Node.Index, + slot: Slot, + slot_type: SlotType, + slot_constant: bool, +}; + +pub const ObjectDeclaration = struct { + name: TokenIndex, + slot: Slot, + protocols: []Node.Index, + generics: []TokenIndex, + // List of either Function (methods) or VarDeclaration (properties) + members: []Member, + + pub const Member = struct { + name: TokenIndex, + docblock: ?TokenIndex, + method: bool, + method_or_default_value: ?Node.Index, + }; +}; + +pub const ObjectInit = struct { + object: ?Node.Index, // Should be a NamedVariableNode or GenericResolve + properties: []Property, + + pub const Property = struct { + name: TokenIndex, + value: Node.Index, + }; +}; + +pub const ProtocolDeclaration = struct { + name: TokenIndex, + slot: Slot, + methods: []Method, + + pub const Method = struct { + docblock: ?TokenIndex, + method: Node.Index, + }; +}; + +pub const Range = struct { + low: Node.Index, + high: Node.Index, +}; + +pub const Resume = struct {}; + +pub const Return = struct { + value: ?Node.Index, + unconditional: bool, +}; + +pub const Subscript = struct { + subscripted: Node.Index, + index: Node.Index, + value: ?Node.Index, +}; + +pub const Throw = struct { + expression: Node.Index, + unconditional: bool, +}; + +pub const Try = struct { + body: Node.Index, + clauses: []Clause, + unconditional_clause: ?Node.Index, + + pub const Clause = struct { + identifier: TokenIndex, + + type_def: Node.Index, + body: Node.Index, + }; +}; + +pub const Unary = struct { + operator: Token.Type, + expression: Node.Index, +}; + +pub const Unwrap = struct { + unwrapped: Node.Index, + original_type: *obj.ObjTypeDef, +}; + +pub const UserType = struct { + identifier: TokenIndex, + generic_resolve: ?Node.Index, +}; + +pub const VarDeclaration = struct { + name: TokenIndex, + value: ?Node.Index, + type: ?Node.Index, + constant: bool, + slot: Slot, + slot_type: SlotType, +}; + +pub const WhileDoUntil = struct { + condition: Node.Index, + body: Node.Index, +}; + +pub const Zdef = struct { + lib_name: TokenIndex, + source: TokenIndex, + elements: []ZdefElement, + + pub const ZdefElement = struct { + fn_ptr: ?*anyopaque = null, + obj_native: ?*obj.ObjNative = null, + // TODO: On the stack, do we free it at some point? + zdef: *const FFI.Zdef, + slot: Slot, + // TODO: add TokenIndex which should wrap portion of the zdef string relative to this element + }; +}; + +pub fn isConstant(self: Self, node: Node.Index) bool { + return switch (self.nodes.items(.tag)[node]) { + .AnonymousObjectType, + .FiberType, + .FunctionType, + .GenericResolveType, + .GenericType, + .ListType, + .MapType, + .SimpleType, + .UserType, + .Boolean, + .Integer, + .Float, + .Pattern, + .Null, + .StringLiteral, + .TypeExpression, + .Void, + => true, + .AsyncCall, + .Block, + .Break, + .Continue, + .Call, + .DoUntil, + .Enum, + .Export, + .For, + .ForEach, + .Function, + .FunDeclaration, + .Import, + .NamedVariable, + .ObjectDeclaration, + .ObjectInit, + .ProtocolDeclaration, + .Range, + .Resolve, + .Resume, + .Return, + .Try, + .Throw, + .VarDeclaration, + .While, + .Yield, + .Zdef, + => false, + .As => self.isConstant(self.nodes.items(.components)[node].As.left), + .Is => self.isConstant(self.nodes.items(.components)[node].Is.left), + .Binary => { + const components = self.nodes.items(.components)[node].Binary; + + return self.isConstant(components.left) and self.isConstant(components.right); + }, + .Dot => { + const type_def = self.nodes.items(.type_def)[self.nodes.items(.components)[node].Dot.callee].?; + + return type_def.def_type == .Enum and type_def.resolved_type.?.Enum.value != null; + }, + .Expression => self.isConstant(self.nodes.items(.components)[node].Expression), + .Grouping => self.isConstant(self.nodes.items(.components)[node].Grouping), + .ForceUnwrap => self.isConstant(self.nodes.items(.components)[node].ForceUnwrap.unwrapped), + .GenericResolve => self.isConstant(self.nodes.items(.components)[node].GenericResolve), + .If => { + const components = self.nodes.items(.components)[node].If; + + return !components.is_statement and self.isConstant(components.condition) and self.isConstant(components.body) and self.isConstant(components.else_branch.?); + }, + .List => { + const components = self.nodes.items(.components)[node].List; + const node_types = self.nodes.items(.tag); + + for (components.items) |item| { + if (node_types[item] == .List or node_types[item] == .Map or !self.isConstant(item)) { + return false; + } + } + + return true; + }, + .Map => { + const components = self.nodes.items(.components)[node].Map; + const node_types = self.nodes.items(.tag); + + for (components.entries) |entry| { + if (node_types[entry.key] == .List or node_types[entry.value] == .List or node_types[entry.key] == .Map or node_types[entry.key] == .Map or !self.isConstant(entry.key) or !self.isConstant(entry.value)) { + return false; + } + } + + return true; + }, + .String => { + const elements = self.nodes.items(.components)[node].String; + + for (elements) |element| { + if (!self.isConstant(element)) { + return false; + } + } + + return true; + }, + .Subscript => { + const components = self.nodes.items(.components)[node].Subscript; + + return self.isConstant(components.subscripted) and self.isConstant(components.index) and components.value == null; + }, + .TypeOfExpression => self.isConstant(self.nodes.items(.components)[node].TypeOfExpression), + .Unary => self.isConstant(self.nodes.items(.components)[node].Unary.expression), + .Unwrap => self.isConstant(self.nodes.items(.components)[node].Unwrap.unwrapped), + }; +} + +fn binaryValue(self: Self, node: Node.Index, gc: *GarbageCollector) !?Value { + const components = self.nodes.items(.components)[node].Binary; + + const left = try self.toValue(components.left, gc); + const left_integer = if (left.isInteger()) left.integer() else null; + const left_float = if (left.isFloat()) left.float() else null; + const right = try self.toValue(components.right, gc); + const right_integer = if (right.isInteger()) right.integer() else null; + const right_float = if (right.isFloat()) right.float() else null; + + switch (components.operator) { + .Ampersand => return Value.fromInteger(left_integer.? & right_integer.?), + .Bor => return Value.fromInteger(left_integer.? | right_integer.?), + .Xor => return Value.fromInteger(left_integer.? ^ right_integer.?), + .ShiftLeft => { + const b = right_integer.?; + + if (b < 0) { + if (b * -1 > std.math.maxInt(u6)) { + return Value.fromInteger(0); + } + + return Value.fromInteger( + left_integer.? >> @as(u5, @truncate(@as(u64, @intCast(b * -1)))), + ); + } else { + if (b > std.math.maxInt(u6)) { + return Value.fromInteger(0); + } + + return Value.fromInteger( + left_integer.? << @as(u5, @truncate(@as(u64, @intCast(b)))), + ); + } + }, + .ShiftRight => { + const b = right_integer.?; + + if (b < 0) { + if (b * -1 > std.math.maxInt(u6)) { + return Value.fromInteger(0); + } + + return Value.fromInteger( + left_integer.? << @as(u5, @truncate(@as(u64, @intCast(b * -1)))), + ); + } else { + if (b > std.math.maxInt(u6)) { + return Value.fromInteger(0); + } + + return Value.fromInteger( + left_integer.? >> @as(u5, @truncate(@as(u64, @intCast(b)))), + ); + } + }, + .QuestionQuestion => return if (left.isNull()) + right + else + left, + .Greater => { + if (left_float) |lf| { + if (right_float) |rf| { + return Value.fromBoolean(lf > rf); + } else { + return Value.fromBoolean(lf > @as(f64, @floatFromInt(right_integer.?))); + } + } else { + if (right_float) |rf| { + return Value.fromBoolean(@as(f64, @floatFromInt(left_integer.?)) > rf); + } else { + return Value.fromBoolean(left_integer.? > right_integer.?); + } + } + + return Value.fromBoolean(left_float orelse left_integer.? > right_float orelse right_integer.?); + }, + .Less => { + if (left_float) |lf| { + if (right_float) |rf| { + return Value.fromBoolean(lf < rf); + } else { + return Value.fromBoolean(lf < @as(f64, @floatFromInt(right_integer.?))); + } + } else { + if (right_float) |rf| { + return Value.fromBoolean(@as(f64, @floatFromInt(left_integer.?)) < rf); + } else { + return Value.fromBoolean(left_integer.? < right_integer.?); + } + } + + return Value.fromBoolean(left_float orelse left_integer.? < right_float orelse right_integer.?); + }, + .GreaterEqual => { + if (left_float) |lf| { + if (right_float) |rf| { + return Value.fromBoolean(lf >= rf); + } else { + return Value.fromBoolean(lf >= @as(f64, @floatFromInt(right_integer.?))); + } + } else { + if (right_float) |rf| { + return Value.fromBoolean(@as(f64, @floatFromInt(left_integer.?)) >= rf); + } else { + return Value.fromBoolean(left_integer.? >= right_integer.?); + } + } + + return Value.fromBoolean(left_float orelse left_integer.? >= right_float orelse right_integer.?); + }, + .LessEqual => { + if (left_float) |lf| { + if (right_float) |rf| { + return Value.fromBoolean(lf <= rf); + } else { + return Value.fromBoolean(lf <= @as(f64, @floatFromInt(right_integer.?))); + } + } else { + if (right_float) |rf| { + return Value.fromBoolean(@as(f64, @floatFromInt(left_integer.?)) <= rf); + } else { + return Value.fromBoolean(left_integer.? <= right_integer.?); + } + } + + return Value.fromBoolean(left_float orelse left_integer.? <= right_float orelse right_integer.?); + }, + .BangEqual => return Value.fromBoolean(!left.eql(right)), + .EqualEqual => return Value.fromBoolean(left.eql(right)), + .Plus => { + const right_string = if (right.isObj()) obj.ObjString.cast(right.obj()) else null; + const left_string = if (left.isObj()) obj.ObjString.cast(left.obj()) else null; + + const right_list = if (right.isObj()) obj.ObjList.cast(right.obj()) else null; + const left_list = if (left.isObj()) obj.ObjList.cast(left.obj()) else null; + + const right_map = if (right.isObj()) obj.ObjMap.cast(right.obj()) else null; + const left_map = if (left.isObj()) obj.ObjMap.cast(left.obj()) else null; + + if (right_string) |rs| { + var new_string = std.ArrayList(u8).init(gc.allocator); + try new_string.appendSlice(left_string.?.string); + try new_string.appendSlice(rs.string); + new_string.shrinkAndFree(new_string.items.len); + + return (try gc.copyString(new_string.items)).toValue(); + } else if (right_float) |rf| { + return Value.fromFloat(rf + left_float.?); + } else if (right_integer) |ri| { + return Value.fromInteger(ri +% left_integer.?); + } else if (right_list) |rl| { + var new_list = std.ArrayList(Value).init(gc.allocator); + try new_list.appendSlice(left_list.?.items.items); + try new_list.appendSlice(rl.items.items); + + return (try gc.allocateObject( + obj.ObjList, + .{ + .type_def = left_list.?.type_def, + .methods = left_list.?.methods, + .items = new_list, + }, + )).toValue(); + } else { + var new_map = try left_map.?.map.clone(); + var it = right_map.?.map.iterator(); + while (it.next()) |entry| { + try new_map.put(entry.key_ptr.*, entry.value_ptr.*); + } + + return (try gc.allocateObject( + obj.ObjMap, + .{ + .type_def = left_map.?.type_def, + .methods = left_map.?.methods, + .map = new_map, + }, + )).toValue(); + } + }, + .Minus => { + if (right_float) |rf| { + return Value.fromFloat(rf - left_float.?); + } + + return Value.fromInteger(right_integer.? -% left_integer.?); + }, + .Star => { + if (right_float) |rf| { + return Value.fromFloat(rf * left_float.?); + } + + return Value.fromInteger(right_integer.? *% left_integer.?); + }, + .Slash => { + if (right_float) |rf| { + return Value.fromFloat(left_float.? / rf); + } + + return Value.fromInteger(@divTrunc(left_integer.?, right_integer.?)); + }, + .Percent => { + if (right_float) |rf| { + return Value.fromFloat(@mod(left_float.?, rf)); + } + + return Value.fromInteger(@mod(left_integer.?, right_integer.?)); + }, + .And => return Value.fromBoolean(left.boolean() and right.boolean()), + .Or => return Value.fromBoolean(left.boolean() or right.boolean()), + else => unreachable, + } +} + +pub fn toValue(self: Self, node: Node.Index, gc: *GarbageCollector) Error!Value { + const value = &self.nodes.items(.value)[node]; + + if (value.* == null and self.isConstant(node)) { + value.* = switch (self.nodes.items(.tag)[node]) { + .AnonymousObjectType, + .FiberType, + .FunctionType, + .GenericResolveType, + .GenericType, + .ListType, + .MapType, + .SimpleType, + .UserType, + => self.nodes.items(.type_def)[node].?.toValue(), + .StringLiteral => self.nodes.items(.components)[node].StringLiteral.toValue(), + .TypeOfExpression => (try (try self.toValue( + self.nodes.items(.components)[node].TypeOfExpression, + gc, + )).typeOf(gc)).toValue(), + .TypeExpression => self.nodes.items(.type_def)[self.nodes.items(.components)[node].TypeExpression].?.toValue(), + .Pattern => self.nodes.items(.components)[node].Pattern.toValue(), + .Void => Value.Void, + .Null => Value.Null, + .Float => Value.fromFloat(self.nodes.items(.components)[node].Float), + .Integer => Value.fromInteger(self.nodes.items(.components)[node].Integer), + .Boolean => Value.fromBoolean(self.nodes.items(.components)[node].Boolean), + .As => try self.toValue(self.nodes.items(.components)[node].As.left, gc), + .Is => is: { + const components = self.nodes.items(.components)[node].Is; + break :is Value.fromBoolean( + (try self.toValue(components.constant, gc)) + .is(try self.toValue(components.left, gc)), + ); + }, + .Binary => try self.binaryValue(node, gc), + .Dot => dot: { + // Only Enum.case can be constant + const components = self.nodes.items(.components)[node].Dot; + const type_def = self.nodes.items(.type_def)[components.callee].?; + + break :dot (try gc.allocateObject( + obj.ObjEnumInstance, + .{ + .enum_ref = type_def.resolved_type.?.Enum.value.?, + .case = @intCast(components.value_or_call_or_enum.EnumCase), + }, + )).toValue(); + }, + .Expression => try self.toValue(self.nodes.items(.components)[node].Expression, gc), + .Grouping => try self.toValue(self.nodes.items(.components)[node].Grouping, gc), + .ForceUnwrap => fc: { + const unwrapped = try self.toValue(self.nodes.items(.components)[node].ForceUnwrap.unwrapped, gc); + + if (unwrapped.isNull()) { + return Error.UnwrappedNull; + } + + break :fc unwrapped; + }, + .GenericResolve => try self.toValue(self.nodes.items(.components)[node].GenericResolve, gc), + .If => @"if": { + const components = self.nodes.items(.components)[node].If; + break :@"if" if ((try self.toValue(components.condition, gc)).boolean()) + try self.toValue(components.body, gc) + else + try self.toValue(components.else_branch.?, gc); + }, + .List => list: { + const components = self.nodes.items(.components)[node].List; + const type_def = self.nodes.items(.type_def)[node]; + + std.debug.assert(type_def != null and type_def.?.def_type != .Placeholder); + + var list = try gc.allocateObject( + obj.ObjList, + obj.ObjList.init(gc.allocator, type_def.?), + ); + + for (components.items) |item| { + try list.items.append(try self.toValue(item, gc)); + } + + break :list list.toValue(); + }, + .Map => map: { + const components = self.nodes.items(.components)[node].Map; + const type_def = self.nodes.items(.type_def)[node]; + + std.debug.assert(type_def != null and type_def.?.def_type != .Placeholder); + + var map = try gc.allocateObject( + obj.ObjMap, + obj.ObjMap.init(gc.allocator, type_def.?), + ); + + for (components.entries) |entry| { + try map.map.put( + try self.toValue(entry.key, gc), + try self.toValue(entry.value, gc), + ); + } + + break :map map.toValue(); + }, + .String => string: { + const elements = self.nodes.items(.components)[node].String; + + var string = std.ArrayList(u8).init(gc.allocator); + const writer = &string.writer(); + for (elements) |element| { + try (try self.toValue(element, gc)).toString(writer); + } + + string.shrinkAndFree(string.items.len); + break :string (try gc.copyString(string.items)).toValue(); + }, + .Subscript => subscript: { + const components = self.nodes.items(.components)[node].Subscript; + + const subscriptable = (try self.toValue(components.subscripted, gc)).obj(); + const key = try self.toValue(components.index, gc); + + switch (subscriptable.obj_type) { + .List => { + const list = obj.ObjList.cast(subscriptable).?; + const index: usize = @intCast(key.integer()); + + if (index < 0 or index >= list.items.items.len) { + return Error.OutOfBound; + } + + break :subscript list.items.items[index]; + }, + .Map => { + const map = obj.ObjMap.cast(subscriptable).?; + + break :subscript map.map.get(key) orelse Value.Null; + }, + .String => { + const str = obj.ObjString.cast(subscriptable).?; + const index: usize = @intCast(key.integer()); + + if (index < 0 or index >= str.string.len) { + return Error.OutOfBound; + } + + break :subscript (try gc.copyString( + &([_]u8{str.string[index]}), + )).toValue(); + }, + else => unreachable, + } + + break :subscript self.nodes.items(.components)[node].Subscript.toValue(); + }, + .Unary => unary: { + const components = self.nodes.items(.components)[node].Unary; + const val = try self.toValue(components.expression, gc); + + break :unary switch (components.operator) { + .Bnot => Value.fromInteger(~val.integer()), + .Bang => Value.fromBoolean(!val.boolean()), + .Minus => if (val.isInteger()) + Value.fromInteger(-%val.integer()) + else + Value.fromFloat(-val.float()), + else => unreachable, + }; + }, + .Unwrap => try self.toValue(self.nodes.items(.components)[node].Unwrap.unwrapped, gc), + else => null, + }; + } + + return value.*.?; +} diff --git a/src/chunk.zig b/src/Chunk.zig similarity index 62% rename from src/chunk.zig rename to src/Chunk.zig index 81d88211..52d6ca87 100644 --- a/src/chunk.zig +++ b/src/Chunk.zig @@ -1,10 +1,9 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const _value = @import("value.zig"); -const _vm = @import("vm.zig"); -const VM = _vm.VM; -const Value = _value.Value; -const Token = @import("token.zig").Token; +const Ast = @import("Ast.zig"); +const Value = @import("value.zig").Value; +const VM = @import("vm.zig").VM; +const Token = @import("Token.zig"); pub const OpCode = enum(u8) { OP_CONSTANT, @@ -125,42 +124,42 @@ pub const OpCode = enum(u8) { }; /// A chunk of code to execute -pub const Chunk = struct { - const Self = @This(); - - pub const max_constants: u24 = 16777215; - - /// List of opcodes to execute - code: std.ArrayList(u32), - /// List of lines - lines: std.ArrayList(Token), - /// List of constants defined in this chunk - constants: std.ArrayList(Value), - - pub fn init(allocator: Allocator) Self { - return Self{ - .code = std.ArrayList(u32).init(allocator), - .constants = std.ArrayList(Value).init(allocator), - .lines = std.ArrayList(Token).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.code.deinit(); - self.constants.deinit(); - self.lines.deinit(); - } - - pub fn write(self: *Self, code: u32, where: Token) !void { - try self.code.append(code); - try self.lines.append(where); - } - - pub fn addConstant(self: *Self, vm: ?*VM, value: Value) !u24 { - if (vm) |uvm| uvm.push(value); - try self.constants.append(value); - if (vm) |uvm| _ = uvm.pop(); - - return @as(u24, @intCast(self.constants.items.len - 1)); - } -}; +const Self = @This(); + +pub const max_constants: u24 = 16777215; + +ast: Ast, +/// List of opcodes to execute +code: std.ArrayList(u32), +/// List of lines +lines: std.ArrayList(Ast.TokenIndex), +/// List of constants defined in this chunk +constants: std.ArrayList(Value), + +pub fn init(allocator: Allocator, ast: Ast) Self { + return Self{ + .ast = ast, + .code = std.ArrayList(u32).init(allocator), + .constants = std.ArrayList(Value).init(allocator), + .lines = std.ArrayList(Ast.TokenIndex).init(allocator), + }; +} + +pub fn deinit(self: *Self) void { + self.code.deinit(); + self.constants.deinit(); + self.lines.deinit(); +} + +pub fn write(self: *Self, code: u32, where: Ast.TokenIndex) !void { + try self.code.append(code); + try self.lines.append(where); +} + +pub fn addConstant(self: *Self, vm: ?*VM, value: Value) !u24 { + if (vm) |uvm| uvm.push(value); + try self.constants.append(value); + if (vm) |uvm| _ = uvm.pop(); + + return @as(u24, @intCast(self.constants.items.len - 1)); +} diff --git a/src/Codegen.zig b/src/Codegen.zig new file mode 100644 index 00000000..c6950c24 --- /dev/null +++ b/src/Codegen.zig @@ -0,0 +1,3876 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const Chunk = @import("Chunk.zig"); +const Ast = @import("Ast.zig"); +const obj = @import("obj.zig"); +const vm = @import("vm.zig"); +const RunFlavor = vm.RunFlavor; +const Value = @import("value.zig").Value; +const Parser = @import("Parser.zig"); +const Token = @import("Token.zig"); +const GarbageCollector = @import("memory.zig").GarbageCollector; +const Reporter = @import("Reporter.zig"); +const BuildOptions = @import("build_options"); +const JIT = @import("Jit.zig"); +const disassembler = @import("disassembler.zig"); + +const Self = @This(); + +pub const Error = error{ + CantCompile, + UnwrappedNull, + OutOfBound, +} || std.mem.Allocator.Error || std.fmt.BufPrintError; + +pub const Frame = struct { + enclosing: ?*Frame = null, + function_node: Ast.Node.Index, + function: ?*obj.ObjFunction = null, + return_counts: bool = false, + return_emitted: bool = false, + + try_should_handle: ?std.AutoHashMap(*obj.ObjTypeDef, Ast.TokenIndex) = null, +}; + +const NodeGen = *const fn ( + self: *Self, + node: Ast.Node.Index, + breaks: ?*std.ArrayList(usize), +) Error!?*obj.ObjFunction; + +fn noGen(_: *Self, _: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + return null; +} + +current: ?*Frame = null, +ast: Ast, +gc: *GarbageCollector, +flavor: RunFlavor, +// Jump to patch at end of current expression with a optional unwrapping in the middle of it +opt_jumps: ?std.ArrayList(usize) = null, +// Used to generate error messages +parser: *Parser, +jit: ?*JIT, + +reporter: Reporter, + +const generators = [_]NodeGen{ + &noGen, // AnonymousObjectType, + generateAs, // As, + generateAsyncCall, // AsyncCall, + generateBinary, // Binary, + generateBlock, // Block, + generateBoolean, // Boolean, + generateBreak, // Break, + generateCall, // Call, + generateContinue, // Continue, + generateDot, // Dot, + generateDoUntil, // DoUntil, + generateEnum, // Enum, + generateExport, // Export, + generateExpression, // Expression, + noGen, // FiberType, + generateFloat, // Float, + generateFor, // For, + generateForceUnwrap, // ForceUnwrap, + generateForEach, // ForEach, + generateFunction, // Function, + noGen, // FunctionType, + generateFunDeclaration, // FunDeclaration, + generateGenericResolve, // GenericResolve, + noGen, // GenericResolveType, + noGen, // GenericType, + generateGrouping, // Grouping, + generateIf, // If, + generateImport, // Import, + generateInteger, // Integer, + generateIs, // Is, + generateList, // List, + noGen, // ListType, + generateMap, // Map, + noGen, // MapType, + generateNamedVariable, // NamedVariable, + generateNull, // Null, + generateObjectDeclaration, // ObjectDeclaration, + generateObjectInit, // ObjectInit, + generatePattern, // Pattern, + generateProtocolDeclaration, // ProtocolDeclaration, + generateRange, // Range, + generateResolve, // Resolve, + generateResume, // Resume, + generateReturn, // Return, + noGen, // SimpleType, + generateString, // String, + generateStringLiteral, // StringLiteral, + generateSubscript, // Subscript, + generateThrow, // Throw, + generateTry, // Try, + generateTypeExpression, // TypeExpression, + generateTypeOfExpression, // TypeOfExpression, + generateUnary, // Unary, + generateUnwrap, // Unwrap, + noGen, // UserType, + generateVarDeclaration, // VarDeclaration, + generateVoid, // Void, + generateWhile, // While, + generateYield, // Yield, + generateZdef, // Zdef, +}; + +pub fn init( + gc: *GarbageCollector, + parser: *Parser, + flavor: RunFlavor, + jit: ?*JIT, +) Self { + return .{ + .ast = undefined, + .gc = gc, + .parser = parser, + .flavor = flavor, + .reporter = Reporter{ + .allocator = gc.allocator, + .error_prefix = "Compile", + }, + .jit = jit, + }; +} + +pub fn deinit(_: *Self) void {} + +pub inline fn currentCode(self: *Self) usize { + return self.current.?.function.?.chunk.code.items.len; +} + +pub fn generate(self: *Self, ast: Ast) anyerror!?*obj.ObjFunction { + self.ast = ast; + self.reporter.had_error = false; + self.reporter.panic_mode = false; + + // if (BuildOptions.debug) { + // var out = std.ArrayList(u8).init(self.gc.allocator); + // defer out.deinit(); + + // try root.node.toJson(&root.node, &out.writer()); + + // try std.io.getStdOut().writer().print("\n{s}", .{out.items}); + // } + + const function = self.generateNode(self.ast.root.?, null); + + return if (self.reporter.had_error) null else function; +} + +pub fn emit(self: *Self, location: Ast.TokenIndex, code: u32) !void { + try self.current.?.function.?.chunk.write(code, location); +} + +pub fn emitTwo(self: *Self, location: Ast.TokenIndex, a: u8, b: u24) !void { + try self.emit(location, (@as(u32, @intCast(a)) << 24) | @as(u32, @intCast(b))); +} + +// OP_ | arg +pub fn emitCodeArg(self: *Self, location: Ast.TokenIndex, code: Chunk.OpCode, arg: u24) !void { + try self.emit( + location, + (@as(u32, @intCast(@intFromEnum(code))) << 24) | @as(u32, @intCast(arg)), + ); +} + +// OP_ | a | b +pub fn emitCodeArgs(self: *Self, location: Ast.TokenIndex, code: Chunk.OpCode, a: u8, b: u16) !void { + try self.emit( + location, + (@as(u32, @intCast(@intFromEnum(code))) << 24) | (@as(u32, @intCast(a)) << 16) | (@as(u32, @intCast(b))), + ); +} + +pub fn emitOpCode(self: *Self, location: Ast.TokenIndex, code: Chunk.OpCode) !void { + try self.emit(location, @as(u32, @intCast(@intFromEnum(code))) << 24); +} + +pub fn emitLoop(self: *Self, location: Ast.TokenIndex, loop_start: usize) !void { + const offset: usize = self.currentCode() - loop_start + 1; + if (offset > 16777215) { + self.reportError(.loop_body_too_large, "Loop body too large."); + } + + try self.emitCodeArg(location, .OP_LOOP, @as(u24, @intCast(offset))); +} + +pub fn emitJump(self: *Self, location: Ast.TokenIndex, instruction: Chunk.OpCode) !usize { + try self.emitCodeArg(location, instruction, 0xffffff); + + return self.currentCode() - 1; +} + +pub fn patchJumpOrLoop(self: *Self, offset: usize, loop_start: ?usize) !void { + const original: u32 = self.current.?.function.?.chunk.code.items[offset]; + const instruction: u8 = @intCast(original >> 24); + const code: Chunk.OpCode = @enumFromInt(instruction); + + if (code == .OP_LOOP) { // Patching a continue statement + std.debug.assert(loop_start != null); + const loop_offset: usize = offset - loop_start.? + 1; + if (loop_offset > 16777215) { + self.reportError(.loop_body_too_large, "Loop body too large."); + } + + self.current.?.function.?.chunk.code.items[offset] = + (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(loop_offset)); + } else { // Patching a break statement + self.patchJump(offset); + } +} + +pub fn patchJump(self: *Self, offset: usize) void { + std.debug.assert(offset < self.currentCode()); + + const jump: usize = self.currentCode() - offset - 1; + + if (jump > 16777215) { + self.reportError(.jump_too_large, "Jump too large."); + } + + const original: u32 = self.current.?.function.?.chunk.code.items[offset]; + const instruction: u8 = @intCast(original >> 24); + + self.current.?.function.?.chunk.code.items[offset] = + (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(jump)); +} + +pub fn patchTry(self: *Self, offset: usize) void { + std.debug.assert(offset < self.currentCode()); + + const jump: usize = self.currentCode(); + + if (jump > 16777215) { + self.reportError( + .block_too_large, + "Try block too large.", + ); + } + + const original: u32 = self.current.?.function.?.chunk.code.items[offset]; + const instruction: u8 = @intCast(original >> 24); + + self.current.?.function.?.chunk.code.items[offset] = + (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(jump)); +} + +pub fn emitList( + self: *Self, + location: Ast.TokenIndex, +) !usize { + try self.emitCodeArg(location, .OP_LIST, 0xffffff); + + return self.currentCode() - 1; +} + +pub fn patchList(self: *Self, offset: usize, constant: u24) !void { + const original: u32 = self.current.?.function.?.chunk.code.items[offset]; + const instruction: u8 = @intCast(original >> 24); + + self.current.?.function.?.chunk.code.items[offset] = + (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(constant)); +} + +pub fn emitMap(self: *Self, location: Ast.TokenIndex) !usize { + try self.emitCodeArg(location, .OP_MAP, 0xffffff); + + return self.currentCode() - 1; +} + +pub fn patchMap(self: *Self, offset: usize, map_type_constant: u24) !void { + const original: u32 = self.current.?.function.?.chunk.code.items[offset]; + const instruction: u8 = @intCast(original >> 24); + + self.current.?.function.?.chunk.code.items[offset] = + (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(map_type_constant)); +} + +pub fn emitReturn(self: *Self, location: Ast.TokenIndex) !void { + try self.emitOpCode(location, .OP_VOID); + try self.emitOpCode(location, .OP_RETURN); +} + +pub fn emitConstant(self: *Self, location: Ast.TokenIndex, value: Value) !void { + try self.emitCodeArg(location, .OP_CONSTANT, try self.makeConstant(value)); +} + +pub fn makeConstant(self: *Self, value: Value) !u24 { + const constant: u24 = try self.current.?.function.?.chunk.addConstant(null, value); + if (constant > Chunk.max_constants) { + self.reportError("Too many constants in one chunk."); + return 0; + } + + return constant; +} + +pub fn identifierConstant(self: *Self, name: []const u8) !u24 { + return try self.makeConstant( + Value.fromObj((try self.gc.copyString(name)).toObj()), + ); +} + +// Unlocated error, should not be used +fn reportError(self: *Self, error_type: Reporter.Error, message: []const u8) void { + if (self.reporter.panic_mode) { + return; + } + + self.reporter.report( + error_type, + Token{ + .tag = .Error, + .source = "", + .script_name = "", + .lexeme = "", + .line = 0, + .column = 0, + }, + message, + ); +} + +fn synchronize(self: *Self, node: Ast.Node.Index) bool { + if (self.reporter.panic_mode) { + switch (self.ast.nodes.items(.tag)[node]) { + .ObjectDeclaration, + .Enum, + .FunDeclaration, + .If, + .While, + .DoUntil, + .For, + .ForEach, + .Return, + .VarDeclaration, + .Throw, + .Break, + .Continue, + .Export, + .Import, + => { + self.reporter.panic_mode = false; + return false; + }, + else => {}, + } + + return true; + } + + return false; +} + +fn patchOptJumps(self: *Self, node: Ast.Node.Index) !void { + const location = self.ast.nodes.items(.location)[node]; + + if (self.ast.nodes.items(.patch_opt_jumps)[node]) { + std.debug.assert(self.opt_jumps != null); + + // Hope over OP_POP if actual value + const njump: usize = try self.emitJump(location, .OP_JUMP); + + for (self.opt_jumps.?.items) |jump| { + self.patchJump(jump); + } + // If aborted by a null optional, will result in null on the stack + try self.emitOpCode(location, .OP_POP); + + self.patchJump(njump); + + self.opt_jumps.?.deinit(); + self.opt_jumps = null; + } +} + +fn endScope(self: *Self, node: Ast.Node.Index) Error!void { + const location = self.ast.nodes.items(.location)[node]; + + if (self.ast.nodes.items(.ends_scope)[node]) |closing| { + for (closing) |op| { + try self.emitOpCode(location, op); + } + } +} + +inline fn generateNode(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + if (self.synchronize(node)) { + return null; + } + + return try Self.generators[@intFromEnum(self.ast.nodes.items(.tag)[node])](self, node, breaks); +} + +fn nodeValue(self: *Self, node: Ast.Node.Index) Error!?Value { + const value = &self.ast.nodes.items(.value)[node]; + + if (value.* == null) { + if (self.ast.isConstant(node)) { + value.* = try self.ast.toValue(node, self.gc); + } + } + + return value.*; +} + +fn generateAs(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const node_location = locations[node]; + const components = self.ast.nodes.items(.components)[node].As; + + const constant = try self.ast.toValue(components.constant, self.gc); + + std.debug.assert(constant.isObj() and constant.obj().obj_type == .Type); + + if (obj.ObjTypeDef.cast(constant.obj()).?.def_type == .Placeholder) { + self.reporter.reportPlaceholder( + self.ast, + obj.ObjTypeDef.cast(constant.obj()).?.resolved_type.?.Placeholder, + ); + } + + _ = try self.generateNode(components.left, breaks); + + try self.emitOpCode(locations[components.left], .OP_COPY); + try self.emitCodeArg(node_location, .OP_CONSTANT, try self.makeConstant(constant)); + try self.emitOpCode(node_location, .OP_IS); + try self.emitOpCode(node_location, .OP_NOT); + const jump = try self.emitJump(node_location, .OP_JUMP_IF_FALSE); + try self.emitOpCode(node_location, .OP_POP); + try self.emitOpCode(node_location, .OP_POP); + try self.emitOpCode(node_location, .OP_NULL); + const jump_over = try self.emitJump(node_location, .OP_JUMP); + self.patchJump(jump); + try self.emitOpCode(node_location, .OP_POP); + self.patchJump(jump_over); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateAsyncCall(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const type_defs = self.ast.nodes.items(.type_def); + const node_location = locations[node]; + const type_def = type_defs[node].?; + const call_node = self.ast.nodes.items(.components)[node].AsyncCall; + + // Push fiber type as constant (we only need it if the fiber is printed out) + // Should not interfere with local counts since OP_FIBER will consume it right away + try self.emitConstant( + node_location, + type_def.toValue(), + ); + + _ = try self.generateNode(call_node, breaks); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateBinary(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].Binary; + + const locations = self.ast.nodes.items(.location); + const type_defs = self.ast.nodes.items(.type_def); + const left_type = type_defs[components.left].?; + const right_type = type_defs[components.right].?; + + if (left_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, left_type.resolved_type.?.Placeholder); + } + + if (right_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, right_type.resolved_type.?.Placeholder); + } + + switch (components.operator) { + .QuestionQuestion, + .Ampersand, + .Bor, + .Xor, + .ShiftLeft, + .ShiftRight, + .Plus, + .Minus, + .Star, + .Slash, + .Percent, + .And, + .Or, + => { + if (!left_type.eql(right_type)) { + self.reporter.reportTypeCheck( + .binary_operand_type, + self.ast.tokens.get(locations[components.left]), + left_type, + self.ast.tokens.get(locations[components.right]), + right_type, + "Type mismatch", + ); + } + }, + + .Greater, + .Less, + .GreaterEqual, + .LessEqual, + .BangEqual, + .EqualEqual, + => { + // We allow comparison between float and int so raise error if type != and one operand is not a number + if (!left_type.eql(right_type) and ((left_type.def_type != .Integer and left_type.def_type != .Float) or (right_type.def_type != .Integer and right_type.def_type != .Float))) { + self.reporter.reportTypeCheck( + .comparison_operand_type, + self.ast.tokens.get(locations[components.left]), + left_type, + self.ast.tokens.get(locations[components.right]), + right_type, + "Type mismatch", + ); + } + }, + + else => unreachable, + } + + switch (components.operator) { + .QuestionQuestion => { + if (!left_type.optional) { + self.reporter.reportErrorAt( + .optional, + self.ast.tokens.get(locations[components.left]), + "Not an optional", + ); + } + + _ = try self.generateNode(components.left, breaks); + + const end_jump: usize = try self.emitJump(locations[node], .OP_JUMP_IF_NOT_NULL); + try self.emitOpCode(locations[node], .OP_POP); + + _ = try self.generateNode(components.right, breaks); + + self.patchJump(end_jump); + }, + .Ampersand => { + // Checking only left operand since we asserted earlier that both operand have the same type + if (left_type.def_type != .Integer) { + self.reporter.reportErrorAt( + .bitwise_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_BAND); + }, + .Bor => { + // Checking only left operand since we asserted earlier that both operand have the same type + if (left_type.def_type != .Integer) { + self.reporter.reportErrorAt( + .bitwise_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_BOR); + }, + .Xor => { + // Checking only left operand since we asserted earlier that both operand have the same type + if (left_type.def_type != .Integer) { + self.reporter.reportErrorAt( + .bitwise_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_XOR); + }, + .ShiftLeft => { + // Checking only left operand since we asserted earlier that both operand have the same type + if (left_type.def_type != .Integer) { + self.reporter.reportErrorAt( + .bitwise_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_SHL); + }, + .ShiftRight => { + // Checking only left operand since we asserted earlier that both operand have the same type + if (left_type.def_type != .Integer) { + self.reporter.reportErrorAt( + .bitwise_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_SHR); + }, + .Greater => { + // Checking only left operand since we asserted earlier that both operand have the same type + if (left_type.def_type != .Integer and left_type.def_type != .Float) { + self.reporter.reportErrorAt( + .comparison_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int` or `float`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_GREATER); + }, + .Less => { + if (left_type.def_type != .Integer and left_type.def_type != .Float) { + self.reporter.reportErrorAt( + .comparison_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int` or `float`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_LESS); + }, + .GreaterEqual => { + if (left_type.def_type != .Integer and left_type.def_type != .Float) { + self.reporter.reportErrorAt( + .comparison_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int` or `float`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_LESS); + try self.emitOpCode(locations[node], .OP_NOT); + }, + .LessEqual => { + if (left_type.def_type != .Integer and left_type.def_type != .Float) { + self.reporter.reportErrorAt( + .comparison_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int` or `float`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_GREATER); + try self.emitOpCode(locations[node], .OP_NOT); + }, + .BangEqual => { + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_EQUAL); + try self.emitOpCode(locations[node], .OP_NOT); + }, + .EqualEqual => { + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_EQUAL); + }, + .Plus => { + // zig fmt: off + if (left_type.def_type != .Integer + and left_type.def_type != .Float + and left_type.def_type != .String + and left_type.def_type != .List + and left_type.def_type != .Map) { + self.reporter.reportErrorAt( + .arithmetic_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected a `int`, `float`, `str`, list or map.", + ); + } + // zig fmt: on + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], switch (left_type.def_type) { + .String => .OP_ADD_STRING, + .List => .OP_ADD_LIST, + .Map => .OP_ADD_MAP, + .Integer => .OP_ADD_I, + .Float => .OP_ADD_F, + else => unreachable, + }); + }, + .Minus => { + if (left_type.def_type != .Integer and left_type.def_type != .Float) { + self.reporter.reportErrorAt( + .arithmetic_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int` or `float`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_SUBTRACT); + }, + .Star => { + if (left_type.def_type != .Integer and left_type.def_type != .Float) { + self.reporter.reportErrorAt( + .arithmetic_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int` or `float`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_MULTIPLY); + }, + .Slash => { + if (left_type.def_type != .Integer and left_type.def_type != .Float) { + self.reporter.reportErrorAt( + .arithmetic_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int` or `float`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_DIVIDE); + }, + .Percent => { + if (left_type.def_type != .Integer and left_type.def_type != .Float) { + self.reporter.reportErrorAt( + .arithmetic_operand_type, + self.ast.tokens.get(locations[components.left]), + "Expected `int` or `float`.", + ); + } + + _ = try self.generateNode(components.left, breaks); + _ = try self.generateNode(components.right, breaks); + try self.emitOpCode(locations[node], .OP_MOD); + }, + .And => { + if (left_type.def_type != .Bool) { + self.reporter.reportErrorAt( + .logical_operand_type, + self.ast.tokens.get(locations[node]), + "`and` expects operands to be `bool`", + ); + } + + _ = try self.generateNode(components.left, breaks); + + const end_jump: usize = try self.emitJump(locations[node], .OP_JUMP_IF_FALSE); + try self.emitOpCode(locations[node], .OP_POP); + + _ = try self.generateNode(components.right, breaks); + + self.patchJump(end_jump); + }, + .Or => { + if (left_type.def_type != .Bool) { + self.reporter.reportErrorAt( + .logical_operand_type, + self.ast.tokens.get(locations[node]), + "`and` expects operands to be `bool`", + ); + } + + _ = try self.generateNode(components.left, breaks); + + const else_jump: usize = try self.emitJump(locations[node], .OP_JUMP_IF_FALSE); + const end_jump: usize = try self.emitJump(locations[node], .OP_JUMP); + + self.patchJump(else_jump); + try self.emitOpCode(locations[node], .OP_POP); + + _ = try self.generateNode(components.right, breaks); + + self.patchJump(end_jump); + }, + else => unreachable, + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateBlock(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + for (self.ast.nodes.items(.components)[node].Block) |statement| { + _ = try self.generateNode(statement, breaks); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateBoolean(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + try self.emitOpCode( + self.ast.nodes.items(.location)[node], + if (self.ast.nodes.items(.components)[node].Boolean) .OP_TRUE else .OP_FALSE, + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateBreak(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + // Close scope(s), then jump + try self.endScope(node); + try breaks.?.append( + try self.emitJump( + self.ast.nodes.items(.location)[node], + .OP_JUMP, + ), + ); + + try self.patchOptJumps(node); + + return null; +} + +fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const type_defs = self.ast.nodes.items(.type_def); + const locations = self.ast.nodes.items(.location); + const node_components = self.ast.nodes.items(.components); + const lexemes = self.ast.tokens.items(.lexeme); + + const components = node_components[node].Call; + + const callee_type_def = type_defs[components.callee].?; + if (callee_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, callee_type_def.resolved_type.?.Placeholder); + } + + // This is not a call but an Enum(value) + if (callee_type_def.def_type == .Enum) { + if (components.is_async) { + self.reporter.reportErrorAt( + .fiber_call_not_allowed, + self.ast.tokens.get(locations[components.callee]), + "Can't be wrapped in a fiber", + ); + } + + if (components.catch_default != null) { + self.reporter.reportErrorAt( + .no_error, + self.ast.tokens.get(locations[components.callee]), + "Doesn't raise any error", + ); + } + + if (components.arguments.len != 1) { + self.reporter.reportErrorAt( + .enum_argument, + self.ast.tokens.get(locations[components.callee]), + "Enum instanciation requires only value argument", + ); + } + + const value = components.arguments[0].value; + + if (type_defs[value].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[value].?.resolved_type.?.Placeholder); + } + + _ = try self.generateNode(components.callee, breaks); + _ = try self.generateNode(value, breaks); + try self.emitOpCode(locations[value], .OP_GET_ENUM_CASE_FROM_VALUE); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; + } + + // Find out if call is invoke or regular call + var invoked = false; + var invoked_on: ?obj.ObjTypeDef.Type = null; + + if (self.ast.nodes.items(.tag)[components.callee] == .Dot) { + const dot = node_components[components.callee].Dot; + const field_accessed = type_defs[dot.callee].?; + + if (field_accessed.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, field_accessed.resolved_type.?.Placeholder); + } + + invoked = field_accessed.def_type != .Object; + invoked_on = field_accessed.def_type; + } + + if (!invoked and invoked_on == null) { + _ = try self.generateNode(components.callee, breaks); + } + + const callee_type = switch (self.ast.nodes.items(.tag)[components.callee]) { + .Dot => node_components[components.callee].Dot.member_type_def, + else => type_defs[components.callee], + }; + + if (callee_type == null) { + self.reporter.reportErrorAt( + .undefined, + self.ast.tokens.get(locations[components.callee]), + "Callee is not defined", + ); + } else if (callee_type.?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, callee_type.?.resolved_type.?.Placeholder); + + // We know nothing about the function being called, no need to go any further + return null; + } else if (callee_type.?.def_type != .Function) { + self.reporter.reportErrorAt( + .callable, + self.ast.tokens.get(locations[node]), + "Can't be called", + ); + + return null; + } else if (callee_type.?.optional) { + self.reporter.reportErrorAt( + .callable, + self.ast.tokens.get(locations[node]), + "Function maybe null and can't be called", + ); + } + + const yield_type = callee_type.?.resolved_type.?.Function.yield_type; + + // Function being called and current function should have matching yield type unless the current function is an entrypoint + if (!components.is_async) { + const current_function_typedef = type_defs[self.current.?.function_node].?.resolved_type.?.Function; + const current_function_type = current_function_typedef.function_type; + const current_function_yield_type = current_function_typedef.yield_type; + switch (current_function_type) { + // Event though a function can call a yieldable function without wraping it in a fiber, the function itself could be called in a fiber + .Function, .Method, .Anonymous => { + if (!current_function_yield_type.eql(yield_type)) { + self.reporter.reportTypeCheck( + .yield_type, + self.ast.tokens.get(locations[self.current.?.function_node]), + current_function_yield_type, + self.ast.tokens.get(locations[node]), + yield_type, + "Bad function yield type", + ); + } + }, + else => {}, + } + } + + // Arguments + const args = callee_type.?.resolved_type.?.Function.parameters; + const defaults = callee_type.?.resolved_type.?.Function.defaults; + const arg_keys = args.keys(); + const arg_count = arg_keys.len; + + var missing_arguments = std.StringArrayHashMap(usize).init(self.gc.allocator); + defer missing_arguments.deinit(); + for (arg_keys, 0..) |arg_name, pindex| { + try missing_arguments.put(arg_name.string, pindex); + } + + if (components.arguments.len > args.count()) { + self.reporter.reportErrorAt( + .call_arguments, + self.ast.tokens.get(locations[node]), + "Too many arguments.", + ); + } + + // First push on the stack arguments has they are parsed + var needs_reorder = false; + for (components.arguments, 0..) |argument, index| { + const argument_type_def = type_defs[argument.value].?; + const arg_key = if (argument.name) |arg_name| + try self.gc.copyString(lexemes[arg_name]) + else + null; + const actual_arg_key = if (index == 0 and arg_key == null) + arg_keys[0] + else + arg_key.?; + const def_arg_type = args.get(actual_arg_key); + + const ref_index = args.getIndex(actual_arg_key); + if (index != ref_index) { + needs_reorder = true; + } + + // Type check the argument + if (def_arg_type) |arg_type| { + if (argument_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, argument_type_def.resolved_type.?.Placeholder); + } else if (!arg_type.eql(argument_type_def)) { + self.reporter.reportTypeCheck( + .call_argument_type, + self.ast.tokens.get(locations[components.callee]), + arg_type, + self.ast.tokens.get(locations[argument.value]), + argument_type_def, + "Bad argument type", + ); + } + + _ = missing_arguments.orderedRemove(actual_arg_key.string); + } else { + self.reporter.reportErrorFmt( + .call_arguments, + self.ast.tokens.get(locations[argument.value]), + "Argument `{s}` does not exists.", + .{if (arg_key) |key| key.string else "unknown"}, + ); + } + + _ = try self.generateNode(argument.value, breaks); + } + + // Argument order reference + var arguments_order_ref = std.ArrayList([]const u8).init(self.gc.allocator); + defer arguments_order_ref.deinit(); + for (components.arguments) |arg| { + try arguments_order_ref.append( + if (arg.name) |name| + lexemes[name] + else + "$", + ); + } + + // Push default arguments + if (missing_arguments.count() > 0) { + var tmp_missing_arguments = try missing_arguments.clone(); + defer tmp_missing_arguments.deinit(); + const missing_keys = tmp_missing_arguments.keys(); + for (missing_keys) |missing_key| { + if (defaults.get(try self.gc.copyString(missing_key))) |default| { + // TODO: like ObjTypeDef, avoid generating constants multiple time for the same value + try self.emitConstant(locations[node], default); + try self.emitOpCode(locations[node], .OP_CLONE); + + try arguments_order_ref.append(missing_key); + _ = missing_arguments.orderedRemove(missing_key); + needs_reorder = true; + } + } + } + + if (missing_arguments.count() > 0) { + var missing = std.ArrayList(u8).init(self.gc.allocator); + const missing_writer = missing.writer(); + for (missing_arguments.keys(), 0..) |key, i| { + try missing_writer.print( + "{s}{s}", + .{ + key, + if (i < missing_arguments.keys().len - 1) + ", " + else + "", + }, + ); + } + defer missing.deinit(); + self.reporter.reportErrorFmt( + .call_arguments, + self.ast.tokens.get(locations[node]), + "Missing argument{s}: {s}", + .{ + if (missing_arguments.count() > 1) + "s" + else + "", + missing.items, + }, + ); + } + + // Reorder arguments + if (needs_reorder) { + // Until ordered + while (true) { + var ordered = true; + for (arguments_order_ref.items, 0..) |arg_key, index| { + const actual_arg_key = if (index == 0 and std.mem.eql(u8, arg_key, "$")) + args.keys()[0].string + else + arg_key; + const correct_index = args.getIndex(try self.gc.copyString(actual_arg_key)).?; + + if (correct_index != index) { + ordered = false; + + // TODO: both OP_SWAP args could fit in a 32 bit instruction + try self.emitCodeArg(locations[node], .OP_SWAP, @intCast(arg_count - index - 1)); + // to where it should be + try self.emit(locations[node], @intCast(arg_count - correct_index - 1)); + + // Switch it in the reference + const temp = arguments_order_ref.items[index]; + arguments_order_ref.items[index] = arguments_order_ref.items[correct_index]; + arguments_order_ref.items[correct_index] = temp; + + // Stop (so we can take the swap into account) and try again + break; + } + } + + if (ordered) break; + } + } + + // Catch clause + const error_types = callee_type.?.resolved_type.?.Function.error_types; + if (components.catch_default) |catch_default| { + const catch_default_type_def = type_defs[catch_default].?; + if (error_types == null or error_types.?.len == 0) { + self.reporter.reportErrorAt( + .no_error, + self.ast.tokens.get(locations[node]), + "Function doesn't raise any error", + ); + } else if (error_types != null) { + if (catch_default_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, catch_default_type_def.resolved_type.?.Placeholder); + } else { + const node_type_def = type_defs[node].?; + // Expression + if (!node_type_def.eql(catch_default_type_def) and !(try node_type_def.cloneOptional(&self.gc.type_registry)).eql(catch_default_type_def)) { + self.reporter.reportTypeCheck( + .inline_catch_type, + self.ast.tokens.get(locations[components.callee]), + node_type_def, + self.ast.tokens.get(locations[catch_default]), + catch_default_type_def, + "Bad inline catch value type", + ); + } + } + + _ = try self.generateNode(catch_default, breaks); + } + } else if (error_types) |errors| { + if (self.current.?.enclosing != null and self.current.?.function.?.type_def.resolved_type.?.Function.function_type != .Test) { + var handles_any = false; + var not_handled = std.ArrayList(*obj.ObjTypeDef).init(self.gc.allocator); + defer not_handled.deinit(); + for (errors) |error_type| { + if (error_type.def_type == .Void) { + continue; + } + + var handled = false; + + if (self.current.?.function.?.type_def.resolved_type.?.Function.error_types) |handled_types| { + for (handled_types) |handled_type| { + if (error_type.eql(handled_type)) { + handled = true; + break; + } + + if (handled_type.def_type == .Any) { + handles_any = true; + break; + } + } + } + + if (!handled) { + if (self.current.?.try_should_handle != null) { + try self.current.?.try_should_handle.?.put(error_type, locations[components.callee]); + } else { + try not_handled.append(error_type); + } + } + + if (handles_any) { + not_handled.clearAndFree(); + break; + } + } + + for (not_handled.items) |error_type| { + const error_str = try error_type.toStringAlloc(self.gc.allocator); + defer error_str.deinit(); + + self.reporter.reportErrorFmt( + .error_not_handled, + self.ast.tokens.get(locations[node]), + "Error `{s}` is not handled", + .{error_str.items}, + ); + } + } + } + + // This is an async call, create a fiber + if (components.is_async) { + if (!invoked) { + const call_arg_count: u8 = if (!invoked) + @as(u8, @intCast(arg_count)) + else if (invoked_on != null and invoked_on.? != .ObjectInstance and invoked_on.? != .ProtocolInstance) + @as(u8, @intCast(arg_count)) + 1 + else + @as(u8, @intCast(arg_count)); + + try self.emitCodeArgs( + locations[node], + .OP_FIBER, + call_arg_count, + if (components.catch_default != null) 1 else 0, + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; + } else { + if (invoked) { + try self.emitCodeArg( + locations[node], + .OP_INVOKE_FIBER, + try self.identifierConstant( + lexemes[node_components[components.callee].Dot.identifier], + ), + ); + } + + try self.emitTwo( + locations[node], + if (invoked_on != null and invoked_on.? != .ObjectInstance and invoked_on.? != .ProtocolInstance) + @as(u8, @intCast(arg_count)) + 1 + else + @as(u8, @intCast(components.arguments.len)), + if (components.catch_default != null) 1 else 0, + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; + } + } + + // Normal call/invoke + if (invoked) { + // TODO: can it be invoked without callee being a DotNode? + try self.emitCodeArg( + locations[node], + switch (type_defs[node_components[components.callee].Dot.callee].?.def_type) { + .ObjectInstance, .ProtocolInstance => .OP_INSTANCE_INVOKE, + .String => .OP_STRING_INVOKE, + .Pattern => .OP_PATTERN_INVOKE, + .Fiber => .OP_FIBER_INVOKE, + .List => .OP_LIST_INVOKE, + .Map => .OP_MAP_INVOKE, + else => unexpected: { + std.debug.assert(self.reporter.had_error); + break :unexpected .OP_INSTANCE_INVOKE; + }, + }, + try self.identifierConstant(lexemes[node_components[components.callee].Dot.identifier]), + ); + } + + if (!invoked) { + try self.emitCodeArgs( + locations[node], + .OP_CALL, + @intCast(arguments_order_ref.items.len), + if (components.catch_default != null) 1 else 0, + ); + } else { + try self.emitTwo( + locations[node], + if (invoked_on != null and invoked_on.? != .ObjectInstance and invoked_on.? != .ProtocolInstance) + @as(u8, @intCast(arg_count)) + 1 + else + @as(u8, @intCast(arg_count)), + if (components.catch_default != null) 1 else 0, + ); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateContinue(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + // Close scope(s), then jump + try self.endScope(node); + try breaks.?.append( + try self.emitJump( + self.ast.nodes.items(.location)[node], + .OP_LOOP, + ), + ); + + try self.patchOptJumps(node); + + return null; +} + +fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const node_components = self.ast.nodes.items(.components); + const type_defs = self.ast.nodes.items(.type_def); + const locations = self.ast.nodes.items(.location); + + const components = node_components[node].Dot; + const identifier_lexeme = self.ast.tokens.items(.lexeme)[components.identifier]; + + _ = try self.generateNode(components.callee, breaks); + + const callee_type = type_defs[components.callee].?; + + if (callee_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, callee_type.resolved_type.?.Placeholder); + } + + switch (callee_type.def_type) { + .ObjectInstance, + .Object, + .ProtocolInstance, + .Enum, + .EnumInstance, + .List, + .Map, + .String, + .Pattern, + .Fiber, + .ForeignContainer, + => {}, + else => self.reporter.reportErrorAt( + .field_access, + self.ast.tokens.get(locations[node]), + "Doesn't have field access", + ), + } + + if (callee_type.optional) { + self.reporter.reportErrorAt( + .field_access, + self.ast.tokens.get(locations[node]), + "Optional doesn't have field access", + ); + } + + const get_code: ?Chunk.OpCode = switch (callee_type.def_type) { + .Object => .OP_GET_OBJECT_PROPERTY, + .ObjectInstance, .ProtocolInstance => .OP_GET_INSTANCE_PROPERTY, + .ForeignContainer => .OP_GET_FCONTAINER_INSTANCE_PROPERTY, + .List => .OP_GET_LIST_PROPERTY, + .Map => .OP_GET_MAP_PROPERTY, + .String => .OP_GET_STRING_PROPERTY, + .Pattern => .OP_GET_PATTERN_PROPERTY, + .Fiber => .OP_GET_FIBER_PROPERTY, + else => null, + }; + + switch (callee_type.def_type) { + .Fiber, .Pattern, .String => { + if (components.member_kind == .Call) { + try self.emitOpCode(locations[node], .OP_COPY); + _ = try self.generateNode(components.value_or_call_or_enum.Call, breaks); + } else { // Expression + std.debug.assert(components.member_kind != .Value); + try self.emitCodeArg( + locations[node], + get_code.?, + try self.identifierConstant(identifier_lexeme), + ); + } + }, + .ForeignContainer, .ObjectInstance, .Object => { + switch (components.member_kind) { + .Value => { + const value = components.value_or_call_or_enum.Value; + const value_type_def = type_defs[value].?; + if (value_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, value_type_def.resolved_type.?.Placeholder); + } + + _ = try self.generateNode(value, breaks); + + try self.emitCodeArg( + locations[node], + switch (callee_type.def_type) { + .ObjectInstance => .OP_SET_INSTANCE_PROPERTY, + .ForeignContainer => .OP_SET_FCONTAINER_INSTANCE_PROPERTY, + else => .OP_SET_OBJECT_PROPERTY, + }, + try self.identifierConstant(identifier_lexeme), + ); + }, + .Call => { + if (callee_type.def_type == .ForeignContainer) { + self.reporter.reportErrorAt( + .callable, + self.ast.tokens.get(locations[components.callee]), + "Not callable", + ); + } + + // Static call + if (callee_type.def_type == .Object) { + try self.emitCodeArg( + locations[node], + get_code.?, + try self.identifierConstant(identifier_lexeme), + ); + } + + _ = try self.generateNode(components.value_or_call_or_enum.Call, breaks); + }, + .Ref => try self.emitCodeArg( + locations[node], + get_code.?, + try self.identifierConstant(identifier_lexeme), + ), + else => unreachable, + } + }, + .ProtocolInstance => { + if (components.member_kind == .Call) { + _ = try self.generateNode(components.value_or_call_or_enum.Call, breaks); + } else { + std.debug.assert(components.member_kind == .Value); + try self.emitCodeArg( + locations[node], + get_code.?, + try self.identifierConstant(identifier_lexeme), + ); + } + }, + .Enum => { + try self.emitCodeArg( + locations[node], + .OP_GET_ENUM_CASE, + @intCast(components.value_or_call_or_enum.EnumCase), + ); + }, + .EnumInstance => { + std.debug.assert(std.mem.eql(u8, identifier_lexeme, "value")); + + try self.emitOpCode(locations[node], .OP_GET_ENUM_CASE_VALUE); + }, + .List, .Map => { + if (components.member_kind == .Call) { + try self.emitOpCode(locations[node], .OP_COPY); + + _ = try self.generateNode(components.value_or_call_or_enum.Call, breaks); + } else { + std.debug.assert(components.member_kind != .Value); + try self.emitCodeArg( + locations[node], + get_code.?, + try self.identifierConstant(identifier_lexeme), + ); + } + }, + else => std.debug.assert(self.reporter.had_error), + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateDoUntil(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const type_defs = self.ast.nodes.items(.type_def); + const node_components = self.ast.nodes.items(.components); + const components = node_components[node].DoUntil; + + const loop_start = self.currentCode(); + + var breaks = std.ArrayList(usize).init(self.gc.allocator); + defer breaks.deinit(); + + _ = try self.generateNode(components.body, &breaks); + + const condition_type_def = type_defs[components.condition].?; + + if (condition_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, condition_type_def.resolved_type.?.Placeholder); + } + + if (condition_type_def.def_type != .Bool) { + self.reporter.reportErrorAt( + .do_condition_type, + self.ast.tokens.get(locations[components.condition]), + "`do` condition must be bool", + ); + } + + _ = try self.generateNode(components.condition, &breaks); + + try self.emitOpCode(locations[node], .OP_NOT); + const exit_jump = try self.emitJump(locations[node], .OP_JUMP_IF_FALSE); + try self.emitOpCode(locations[node], .OP_POP); + + try self.emitLoop(locations[node], loop_start); + self.patchJump(exit_jump); + + try self.emitOpCode(locations[node], .OP_POP); // Pop condition + + // Patch breaks + for (breaks.items) |jump| { + try self.patchJumpOrLoop(jump, loop_start); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateEnum(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const type_defs = self.ast.nodes.items(.type_def); + const node_components = self.ast.nodes.items(.components); + const components = node_components[node].Enum; + + const enum_type = type_defs[node].?.resolved_type.?.Enum.enum_type; + if (enum_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, enum_type.resolved_type.?.Placeholder); + + return null; + } + + switch (enum_type.def_type) { + .String, .Integer, .Float => {}, + else => { + self.reporter.reportErrorAt( + .syntax, + self.ast.tokens.get(locations[node]), + "Type not allowed as enum value", + ); + return null; + }, + } + + for (components.cases) |case| { + const case_type_def = if (case.value) |value| + type_defs[value].? + else + null; + + if (case_type_def) |case_type| { + if (case_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, case_type.resolved_type.?.Placeholder); + } else if (!((try enum_type.toInstance(self.gc.allocator, &self.gc.type_registry))).eql(case_type)) { + self.reporter.reportTypeCheck( + .enum_case_type, + self.ast.tokens.get(locations[node]), + (try enum_type.toInstance(self.gc.allocator, &self.gc.type_registry)), + self.ast.tokens.get(locations[case.value.?]), + case_type, + "Bad enum case type", + ); + } + } + } + + try self.emitCodeArg( + locations[node], + .OP_CONSTANT, + try self.makeConstant( + try self.ast.toValue(node, self.gc), + ), + ); + try self.emitCodeArg(locations[node], .OP_DEFINE_GLOBAL, @intCast(components.slot)); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateExport(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].Export; + + if (components.declaration) |decl| { + _ = try self.generateNode(decl, breaks); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateExpression(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const components = self.ast.nodes.items(.components); + const expr = components[node].Expression; + const expr_node_type = self.ast.nodes.items(.tag)[expr]; + const expr_type_def = self.ast.nodes.items(.type_def)[expr]; + + _ = try self.generateNode(expr, breaks); + + try self.emitOpCode(locations[node], .OP_POP); + + // zig fmt: off + const lone_expr = (expr_node_type != .NamedVariable or components[expr].NamedVariable.value == null) + and (expr_node_type != .Subscript or components[expr].Subscript.value == null) + and (expr_node_type != .Dot or components[expr].Dot.member_kind != .Value) + and expr_type_def != null + and expr_type_def.?.def_type != .Void; + // zig fmt: on + + if (self.flavor != .Repl and lone_expr and expr_type_def.?.def_type != .Placeholder) { + const type_def_str = expr_type_def.?.toStringAlloc(self.gc.allocator) catch unreachable; + defer type_def_str.deinit(); + + self.reporter.warnFmt( + .discarded_value, + self.ast.tokens.get(locations[node]), + "Discarded value of type `{s}`", + .{ + type_def_str.items, + }, + ); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateFloat(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + try self.emitConstant( + self.ast.nodes.items(.location)[node], + try self.ast.toValue(node, self.gc), + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateFor(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const type_defs = self.ast.nodes.items(.type_def); + const node_components = self.ast.nodes.items(.components); + + const components = node_components[node].For; + if (self.ast.isConstant(components.condition) and !(try self.ast.toValue(components.condition, self.gc)).boolean()) { + try self.patchOptJumps(node); + + return null; + } + + for (components.init_declarations) |decl| { + _ = try self.generateNode(decl, breaks); + } + + const loop_start = self.currentCode(); + + const condition_type_def = type_defs[components.condition].?; + if (condition_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, condition_type_def.resolved_type.?.Placeholder); + } + + if (condition_type_def.def_type != .Bool) { + self.reporter.reportErrorAt( + .for_condition_type, + self.ast.tokens.get(locations[components.condition]), + "`for` condition must be bool", + ); + } + + _ = try self.generateNode(components.condition, breaks); + + const exit_jump: usize = try self.emitJump(locations[node], .OP_JUMP_IF_FALSE); + try self.emitOpCode(locations[node], .OP_POP); // Pop condition + + // Jump over expressions which will be executed at end of loop + const body_jump = try self.emitJump(locations[node], .OP_JUMP); + + const expr_loop: usize = self.currentCode(); + for (components.post_loop) |expr| { + const expr_type_def = type_defs[expr].?; + if (expr_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, expr_type_def.resolved_type.?.Placeholder); + } + + _ = try self.generateNode(expr, breaks); + try self.emitOpCode(locations[expr], .OP_POP); + } + + try self.emitLoop(locations[node], loop_start); + + self.patchJump(body_jump); + + var lbreaks: std.ArrayList(usize) = std.ArrayList(usize).init(self.gc.allocator); + defer lbreaks.deinit(); + + _ = try self.generateNode(components.body, &lbreaks); + + try self.emitLoop(locations[node], expr_loop); + + self.patchJump(exit_jump); + + try self.emitOpCode(locations[node], .OP_POP); // Pop condition + + // Patch breaks + for (lbreaks.items) |jump| { + try self.patchJumpOrLoop(jump, loop_start); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateForceUnwrap(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const components = self.ast.nodes.items(.components)[node].ForceUnwrap; + + if (components.original_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, components.original_type.resolved_type.?.Placeholder); + + return null; + } + + if (!components.original_type.optional) { + self.reporter.reportErrorAt( + .optional, + self.ast.tokens.get(locations[components.unwrapped]), + "Not an optional", + ); + } + + _ = try self.generateNode(components.unwrapped, breaks); + + try self.emitOpCode(locations[node], .OP_UNWRAP); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const node_components = self.ast.nodes.items(.components); + const locations = self.ast.nodes.items(.location); + const type_defs = self.ast.nodes.items(.type_def); + const components = node_components[node].ForEach; + + // Type checking + const iterable_type_def = type_defs[components.iterable].?; + var key_type_def = type_defs[components.key].?; + const value_type_def = type_defs[components.value].?; + if (iterable_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, iterable_type_def.resolved_type.?.Placeholder); + } else { + if (!components.key_omitted) { + if (key_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, key_type_def.resolved_type.?.Placeholder); + } + + switch (iterable_type_def.def_type) { + .String, .List => { + if (key_type_def.def_type != .Integer) { + self.reporter.reportErrorAt( + .foreach_key_type, + self.ast.tokens.get(locations[components.key]), + "Expected `int`.", + ); + } + }, + .Map => { + if (!iterable_type_def.resolved_type.?.Map.key_type.strictEql(key_type_def)) { + self.reporter.reportTypeCheck( + .foreach_key_type, + self.ast.tokens.get(locations[components.iterable]), + iterable_type_def.resolved_type.?.Map.key_type, + self.ast.tokens.get(locations[components.key]), + key_type_def, + "Bad key type", + ); + } + }, + .Enum => self.reporter.reportErrorAt( + .foreach_key_type, + self.ast.tokens.get(locations[components.key]), + "No key available when iterating over enum.", + ), + else => self.reporter.reportErrorAt( + .foreach_iterable, + self.ast.tokens.get(locations[components.iterable]), + "Not iterable.", + ), + } + } else { + // Key was omitted, put the correct type in the key var declation to avoid raising errors + switch (iterable_type_def.def_type) { + .Map => key_type_def = iterable_type_def.resolved_type.?.Map.key_type, + .String, .List => key_type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }), + else => {}, + } + } + + if (value_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, value_type_def.resolved_type.?.Placeholder); + } + + switch (iterable_type_def.def_type) { + .Map => { + if (!iterable_type_def.resolved_type.?.Map.value_type.strictEql(value_type_def)) { + self.reporter.reportTypeCheck( + .foreach_value_type, + self.ast.tokens.get(locations[components.iterable]), + iterable_type_def.resolved_type.?.Map.value_type, + self.ast.tokens.get(locations[components.value]), + value_type_def, + "Bad value type", + ); + } + }, + .List => { + if (!iterable_type_def.resolved_type.?.List.item_type.strictEql(value_type_def)) { + self.reporter.reportTypeCheck( + .foreach_value_type, + self.ast.tokens.get(locations[components.iterable]), + iterable_type_def.resolved_type.?.List.item_type, + self.ast.tokens.get(locations[components.value]), + value_type_def, + "Bad value type", + ); + } + }, + .String => { + if (value_type_def.def_type != .String) { + self.reporter.reportErrorAt( + .foreach_value_type, + self.ast.tokens.get(locations[components.value]), + "Expected `str`.", + ); + } + }, + .Enum => { + const iterable_type = try iterable_type_def.toInstance(self.gc.allocator, &self.gc.type_registry); + if (!iterable_type.strictEql(value_type_def)) { + self.reporter.reportTypeCheck( + .foreach_value_type, + self.ast.tokens.get(locations[components.iterable]), + iterable_type, + self.ast.tokens.get(locations[components.value]), + value_type_def, + "Bad value type", + ); + } + }, + .Fiber => { + const iterable_type = try iterable_type_def.resolved_type.?.Fiber.yield_type.toInstance( + self.gc.allocator, + &self.gc.type_registry, + ); + if (!iterable_type.strictEql(value_type_def)) { + self.reporter.reportTypeCheck( + .foreach_value_type, + self.ast.tokens.get(locations[components.iterable]), + iterable_type, + self.ast.tokens.get(locations[components.value]), + value_type_def, + "Bad value type", + ); + } + }, + else => self.reporter.reportErrorAt( + .foreach_iterable, + self.ast.tokens.get(locations[components.iterable]), + "Not iterable.", + ), + } + } + + // If iterable constant and empty, skip the node + if (self.ast.isConstant(components.iterable)) { + const iterable = (try self.ast.toValue(components.iterable, self.gc)).obj(); + + if (switch (iterable.obj_type) { + .List => obj.ObjList.cast(iterable).?.items.items.len == 0, + .Map => obj.ObjMap.cast(iterable).?.map.count() == 0, + .String => obj.ObjString.cast(iterable).?.string.len == 0, + .Enum => obj.ObjEnum.cast(iterable).?.cases.len == 0, + else => self.reporter.had_error, + }) { + try self.patchOptJumps(node); + return null; + } + } + + _ = try self.generateNode(components.key, breaks); + _ = try self.generateNode(components.value, breaks); + _ = try self.generateNode(components.iterable, breaks); + + const loop_start: usize = self.currentCode(); + + // Calls `next` and update key and value locals + try self.emitOpCode( + locations[node], + switch (iterable_type_def.def_type) { + .String => .OP_STRING_FOREACH, + .List => .OP_LIST_FOREACH, + .Enum => .OP_ENUM_FOREACH, + .Map => .OP_MAP_FOREACH, + .Fiber => .OP_FIBER_FOREACH, + else => unexpected: { + std.debug.assert(self.reporter.had_error); + break :unexpected .OP_STRING_FOREACH; + }, + }, + ); + + // If next key is null, exit loop + try self.emitCodeArg( + locations[node], + .OP_GET_LOCAL, + @as( + u24, + @intCast( + switch (iterable_type_def.def_type) { + .String, .List, .Map => node_components[components.key].VarDeclaration.slot, + else => node_components[components.value].VarDeclaration.slot, + }, + ), + ), + ); + try self.emitOpCode(locations[node], .OP_NULL); + try self.emitOpCode(locations[node], .OP_EQUAL); + try self.emitOpCode(locations[node], .OP_NOT); + const exit_jump: usize = try self.emitJump(locations[node], .OP_JUMP_IF_FALSE); + try self.emitOpCode(locations[node], .OP_POP); // Pop condition result + + var lbreaks: std.ArrayList(usize) = std.ArrayList(usize).init(self.gc.allocator); + defer lbreaks.deinit(); + + _ = try self.generateNode(components.body, &lbreaks); + + try self.emitLoop(locations[node], loop_start); + + // Patch condition jump + self.patchJump(exit_jump); + + try self.emitOpCode(locations[node], .OP_POP); // Pop condition result + + // Patch breaks + for (lbreaks.items) |jump| { + try self.patchJumpOrLoop(jump, loop_start); + } + + try self.patchOptJumps(node); + // Should have key, [value,] iterable to pop + std.debug.assert( + self.ast.nodes.items(.ends_scope)[node] != null and self.ast.nodes.items(.ends_scope)[node].?.len == 3, + ); + try self.endScope(node); + + return null; +} + +fn generateFunction(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const node_components = self.ast.nodes.items(.components); + const type_defs = self.ast.nodes.items(.type_def); + const locations = self.ast.nodes.items(.location); + const components = node_components[node].Function; + const function_signature = if (components.function_signature) |fs| + node_components[fs].FunctionType + else + null; + const node_type_def = type_defs[node].?; + const function_type = node_type_def.resolved_type.?.Function.function_type; + + // If function is a test block and we're not testing/checking/etc. don't waste time generating the node + if (self.flavor == .Run and function_type == .Test) { + return null; + } + + const enclosing = self.current; + self.current = try self.gc.allocator.create(Frame); + self.current.?.* = .{ + .enclosing = enclosing, + .function_node = node, + }; + + var function = try obj.ObjFunction.init( + self.gc.allocator, + self.ast, + node, + node_type_def.resolved_type.?.Function.name, + ); + + function.type_def = node_type_def; + + // Check that default arguments are constant values + switch (function_type) { + .Function, .Method, .Anonymous, .Extern => { + for (self.ast.nodes.items(.components)[components.function_signature.?].FunctionType.arguments) |argument| { + if (argument.default) |default| { + if (!self.ast.isConstant(default)) { + self.reporter.reportErrorAt( + .constant_default, + self.ast.tokens.get(locations[default]), + "Default parameters must be constant values.", + ); + } else { + try node_type_def.resolved_type.?.Function.defaults.put( + try self.gc.copyString(self.ast.tokens.items(.lexeme)[argument.name]), + try self.ast.toValue(default, self.gc), + ); + } + } + } + }, + else => {}, + } + + // Check for any remaining placeholders in function signature + if (function.type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, function.type_def.resolved_type.?.Placeholder); + } else { + const function_def = function.type_def.resolved_type.?.Function; + + if (function_def.return_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, function_def.return_type.resolved_type.?.Placeholder); + } + + if (function_def.yield_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, function_def.yield_type.resolved_type.?.Placeholder); + } + + var it = function_def.parameters.iterator(); + while (it.next()) |kv| { + if (kv.value_ptr.*.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, kv.value_ptr.*.resolved_type.?.Placeholder); + } + } + + if (function_def.error_types) |error_types| { + for (error_types) |error_type| { + if (error_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, error_type.resolved_type.?.Placeholder); + } + } + } + } + + // First chunk constant is the empty string + _ = try function.chunk.addConstant( + null, + Value.fromObj((try self.gc.copyString("")).toObj()), + ); + + self.current.?.function = try self.gc.allocateObject(obj.ObjFunction, function); + + // Generate function's body + if (components.body) |body| { + _ = try self.generateNode(body, breaks); + + if (function_signature != null and function_signature.?.lambda) { + try self.emitOpCode(locations[body], .OP_RETURN); + self.current.?.return_emitted = true; + } + } + + if (function_type != .Extern) { + // If .Script, search for exported globals and return them in a map + if (function_type == .Script or function_type == .ScriptEntryPoint) { + // If top level, search `main` or `test` function(s) and call them + // Then put any exported globals on the stack + if (self.flavor != .Test and function_type == .ScriptEntryPoint) { + if (components.entry.?.main_slot) |main_slot| { + try self.emitCodeArg(components.entry.?.main_location.?, .OP_GET_GLOBAL, @intCast(main_slot)); + try self.emitCodeArg(components.entry.?.main_location.?, .OP_GET_LOCAL, 0); // cli args are always local 0 + try self.emitCodeArgs(components.entry.?.main_location.?, .OP_CALL, 1, 0); + } + } else if (self.flavor == .Test) { + // Create an entry point wich runs all `test` + for (components.entry.?.test_slots, 0..) |slot, index| { + try self.emitCodeArg(components.entry.?.test_locations[index], .OP_GET_GLOBAL, @intCast(slot)); + try self.emitCodeArgs(components.entry.?.test_locations[index], .OP_CALL, 0, 0); + } + } + + // If we're being imported, put all globals on the stack + if (components.import_root) { + if (components.entry.?.exported_count > 16777215) { + self.reporter.reportErrorAt( + .export_count, + self.ast.tokens.get(locations[node]), + "Can't export more than 16777215 values.", + ); + } + + var index: usize = 0; + while (index < components.entry.?.exported_count) : (index += 1) { + try self.emitCodeArg(locations[node], .OP_GET_GLOBAL, @intCast(index)); + } + + try self.emitCodeArg(locations[node], .OP_EXPORT, @intCast(components.entry.?.exported_count)); + } else { + try self.emitOpCode(locations[node], .OP_VOID); + try self.emitOpCode(locations[node], .OP_RETURN); + self.current.?.return_emitted = true; + } + // zig fmt: off + } else if (function_type == .Repl + and components.body != null + and self.ast.nodes.items(.tag)[components.body.?] == .Block + and node_components[components.body.?].Block.len > 0 + and self.ast.nodes.items(.tag)[node_components[components.body.?].Block[node_components[components.body.?].Block.len - 1]] == .Expression) { + // zig fmt: on + // Repl and last expression is a lone statement, remove OP_POP, add OP_RETURN + std.debug.assert(vm.VM.getCode(self.current.?.function.?.chunk.code.pop()) == .OP_POP); + _ = self.current.?.function.?.chunk.lines.pop(); + + try self.emitReturn(locations[node]); + } else if (self.current.?.function.?.type_def.resolved_type.?.Function.return_type.def_type == .Void and !self.current.?.return_emitted) { + // TODO: detect if some branches of the function body miss a return statement + try self.emitReturn(locations[node]); + } else if (!self.current.?.return_emitted) { + self.reporter.reportErrorAt( + .missing_return, + self.ast.tokens.get(locations[node]), + "Missing return statement", + ); + } + } + + const frame = self.current.?; + const current_function = frame.function.?; + current_function.upvalue_count = @intCast(components.upvalue_binding.count()); + + if (BuildOptions.debug) { + try disassembler.disassembleChunk(¤t_function.chunk, current_function.name.string); + std.debug.print("\n\n", .{}); + } + + self.current = frame.enclosing; + + if (function_type != .ScriptEntryPoint and function_type != .Repl) { + // `extern` functions don't have upvalues + if (function_type == .Extern) { + try self.emitCodeArg( + locations[node], + .OP_CONSTANT, + try self.makeConstant(components.native.?.toValue()), + ); + } else { + try self.emitCodeArg( + locations[node], + .OP_CLOSURE, + try self.makeConstant(current_function.toValue()), + ); + + var it = components.upvalue_binding.iterator(); + while (it.next()) |kv| { + try self.emit(locations[node], if (kv.value_ptr.*) 1 else 0); + try self.emit(locations[node], kv.key_ptr.*); + } + } + } + + try self.patchOptJumps(node); + try self.endScope(node); + + node_components[node].Function.function = current_function; + + return current_function; +} + +fn generateFunDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const node_components = self.ast.nodes.items(.components); + const components = node_components[node].FunDeclaration; + + _ = try self.generateNode(components.function, breaks); + + if (components.slot_type == .Global) { + _ = try self.emitCodeArg( + self.ast.nodes.items(.location)[node], + .OP_DEFINE_GLOBAL, + @intCast(components.slot), + ); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateGenericResolve(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const type_def = self.ast.nodes.items(.type_def)[node].?; + const expr = self.ast.nodes.items(.components)[node].GenericResolve; + const node_location = self.ast.nodes.items(.location)[node]; + + switch (type_def.def_type) { + .Function => { + const function_type = type_def.resolved_type.?.Function; + + if (function_type.generic_types.count() > 0 and (function_type.resolved_generics == null or function_type.resolved_generics.?.len < function_type.generic_types.count())) { + self.reporter.reportErrorFmt( + .generic_type, + self.ast.tokens.get(node_location), + "Missing generic types. Expected {} got {}.", + .{ + function_type.generic_types.count(), + if (function_type.resolved_generics == null) + 0 + else + function_type.resolved_generics.?.len, + }, + ); + } else if (function_type.resolved_generics != null and function_type.resolved_generics.?.len > function_type.generic_types.count()) { + self.reporter.reportErrorFmt( + .generic_type, + self.ast.tokens.get(node_location), + "Too many generic types. Expected {} got {}.", + .{ + function_type.generic_types.count(), + if (function_type.resolved_generics == null) + 0 + else + function_type.resolved_generics.?.len, + }, + ); + } + }, + .Object => { + const object_type = type_def.resolved_type.?.Object; + + if (object_type.generic_types.count() > 0 and (object_type.resolved_generics == null or object_type.resolved_generics.?.len < object_type.generic_types.count())) { + self.reporter.reportErrorFmt( + .generic_type, + self.ast.tokens.get(node_location), + "Missing generic types. Expected {} got {}.", + .{ + object_type.generic_types.count(), + if (object_type.resolved_generics == null) + 0 + else + object_type.resolved_generics.?.len, + }, + ); + } else if (object_type.resolved_generics != null and object_type.resolved_generics.?.len > object_type.generic_types.count()) { + self.reporter.reportErrorFmt( + .generic_type, + self.ast.tokens.get(node_location), + "Too many generic types. Expected {} got {}.", + .{ + object_type.generic_types.count(), + if (object_type.resolved_generics == null) + 0 + else + object_type.resolved_generics.?.len, + }, + ); + } + }, + else => { + const type_def_str = type_def.toStringAlloc(self.gc.allocator) catch unreachable; + defer type_def_str.deinit(); + + self.reporter.reportErrorFmt( + .generic_type, + self.ast.tokens.get(node_location), + "Type `{s}` does not support generic types", + .{ + type_def_str.items, + }, + ); + }, + } + + _ = try self.generateNode(expr, breaks); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateGrouping(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components); + const expr = components[node].Grouping; + + _ = try self.generateNode(expr, breaks); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateIf(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const type_defs = self.ast.nodes.items(.type_def); + const locations = self.ast.nodes.items(.location); + const node_components = self.ast.nodes.items(.components); + const components = node_components[node].If; + const location = locations[node]; + + // Type checking + if (type_defs[components.condition].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[components.condition].?.resolved_type.?.Placeholder); + } + + if (!components.is_statement) { + if (type_defs[components.body].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[components.body].?.resolved_type.?.Placeholder); + } + + if (type_defs[components.else_branch.?].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[components.else_branch.?].?.resolved_type.?.Placeholder); + } + + // Both should have same type + if (!type_defs[node].?.eql(type_defs[components.body].?)) { + self.reporter.reportTypeCheck( + .inline_if_body_type, + self.ast.tokens.get(location), + type_defs[node].?, + self.ast.tokens.get(locations[components.body]), + type_defs[components.body].?, + "Inline if body type not matching", + ); + } + + if (!type_defs[node].?.eql(type_defs[components.else_branch.?].?)) { + self.reporter.reportTypeCheck( + .inline_if_else_type, + self.ast.tokens.get(location), + type_defs[node].?, + self.ast.tokens.get(locations[components.else_branch.?]), + type_defs[components.else_branch.?].?, + "Inline if else type not matching", + ); + } + } + + if (components.unwrapped_identifier != null) { + if (!type_defs[components.condition].?.optional) { + self.reporter.reportErrorAt( + .optional, + self.ast.tokens.get(locations[components.condition]), + "Expected optional", + ); + } + } else if (components.casted_type == null) { + if (type_defs[components.condition].?.def_type != .Bool) { + self.reporter.reportErrorAt( + .if_condition_type, + self.ast.tokens.get(locations[components.condition]), + "`if` condition must be bool", + ); + } + } + + // If condition is a constant expression, no need to generate branches + if (self.ast.isConstant(components.condition) and components.unwrapped_identifier == null and components.casted_type == null) { + const condition = try self.ast.toValue(components.condition, self.gc); + + if (condition.boolean()) { + _ = try self.generateNode(components.body, breaks); + } else if (components.else_branch) |else_branch| { + _ = try self.generateNode(else_branch, breaks); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; + } + + _ = try self.generateNode(components.condition, breaks); + const condition_location = locations[components.condition]; + if (components.unwrapped_identifier != null) { + try self.emitOpCode(condition_location, .OP_COPY); + try self.emitOpCode(condition_location, .OP_NULL); + try self.emitOpCode(condition_location, .OP_EQUAL); + try self.emitOpCode(condition_location, .OP_NOT); + } else if (components.casted_type) |casted_type| { + try self.emitOpCode(condition_location, .OP_COPY); + try self.emitConstant(condition_location, type_defs[casted_type].?.toValue()); + try self.emitOpCode(condition_location, .OP_IS); + } + + const else_jump: usize = try self.emitJump(location, .OP_JUMP_IF_FALSE); + try self.emitOpCode(location, .OP_POP); + + _ = try self.generateNode(components.body, breaks); + + const out_jump: usize = try self.emitJump(location, .OP_JUMP); + + self.patchJump(else_jump); + if (components.unwrapped_identifier != null or components.casted_type != null) { + // Since we did not enter the if block, we did not pop the unwrapped local + try self.emitOpCode(location, .OP_POP); + } + try self.emitOpCode(location, .OP_POP); + + if (components.else_branch) |else_branch| { + _ = try self.generateNode(else_branch, breaks); + } + + self.patchJump(out_jump); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateImport(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].Import; + const location = self.ast.nodes.items(.location)[node]; + + if (components.import) |import| { + try self.emitConstant(location, import.absolute_path.toValue()); + + _ = try self.generateNode(import.function, breaks); + + // FIXME: avoid generating the same import function more than once! + try self.emitOpCode(location, .OP_IMPORT); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateInteger(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + try self.emitConstant( + self.ast.nodes.items(.location)[node], + try self.ast.toValue(node, self.gc), + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateIs(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].Is; + const location = self.ast.nodes.items(.location)[node]; + const constant = try self.ast.toValue(components.constant, self.gc); + + std.debug.assert(constant.isObj()); + std.debug.assert(constant.obj().obj_type == .Type); + + if (obj.ObjTypeDef.cast(constant.obj()).?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, obj.ObjTypeDef.cast(constant.obj()).?.resolved_type.?.Placeholder); + } + + _ = try self.generateNode(components.left, breaks); + + try self.emitCodeArg( + location, + .OP_CONSTANT, + try self.makeConstant(constant), + ); + + try self.emitOpCode(location, .OP_IS); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateList(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const components = self.ast.nodes.items(.components)[node].List; + const type_defs = self.ast.nodes.items(.type_def); + + const item_type = type_defs[node].?.resolved_type.?.List.item_type; + const list_offset = try self.emitList(locations[node]); + + for (components.items) |item| { + if (item_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[item].?.resolved_type.?.Placeholder); + } else if (!item_type.eql(type_defs[item].?)) { + self.reporter.reportTypeCheck( + .list_item_type, + self.ast.tokens.get(locations[node]), + item_type, + self.ast.tokens.get(locations[item]), + type_defs[item].?, + "Bad list type", + ); + } else { + _ = try self.generateNode(item, breaks); + + try self.emitOpCode(locations[item], .OP_LIST_APPEND); + } + } + + const list_type_constant = try self.makeConstant(Value.fromObj(type_defs[node].?.toObj())); + try self.patchList(list_offset, list_type_constant); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateMap(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const components = self.ast.nodes.items(.components)[node].Map; + const type_defs = self.ast.nodes.items(.type_def); + + const key_type = if (components.explicit_key_type) |kt| + type_defs[kt] + else + null; + + const value_type = if (components.explicit_value_type) |vt| + type_defs[vt] + else + null; + + const map_offset = try self.emitMap(locations[node]); + + for (components.entries) |entry| { + _ = try self.generateNode(entry.key, breaks); + _ = try self.generateNode(entry.value, breaks); + + try self.emitOpCode(locations[node], .OP_SET_MAP); + + if (type_defs[entry.key].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[entry.key].?.resolved_type.?.Placeholder); + } + + if (type_defs[entry.value].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[entry.value].?.resolved_type.?.Placeholder); + } + + if (key_type != null and !key_type.?.eql(type_defs[entry.key].?)) { + self.reporter.reportTypeCheck( + .map_key_type, + self.ast.tokens.get(locations[node]), + key_type.?, + self.ast.tokens.get(locations[entry.key]), + type_defs[entry.key].?, + "Bad key type", + ); + } + + if (value_type != null and !value_type.?.eql(type_defs[entry.value].?)) { + self.reporter.reportTypeCheck( + .map_value_type, + self.ast.tokens.get(locations[node]), + value_type.?, + self.ast.tokens.get(locations[entry.value]), + type_defs[entry.value].?, + "Bad value type", + ); + } + } + + const map_type_constant = try self.makeConstant(Value.fromObj(type_defs[node].?.toObj())); + try self.patchMap(map_offset, map_type_constant); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateNamedVariable(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].NamedVariable; + const locations = self.ast.nodes.items(.location); + const type_defs = self.ast.nodes.items(.type_def); + + var get_op: Chunk.OpCode = undefined; + var set_op: Chunk.OpCode = undefined; + + switch (components.slot_type) { + .Local => { + get_op = .OP_GET_LOCAL; + set_op = .OP_SET_LOCAL; + }, + .Global => { + get_op = .OP_GET_GLOBAL; + set_op = .OP_SET_GLOBAL; + }, + .UpValue => { + get_op = .OP_GET_UPVALUE; + set_op = .OP_SET_UPVALUE; + }, + } + + if (components.value) |value| { + // Type checking + if (type_defs[node].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[node].?.resolved_type.?.Placeholder); + } + + if (!type_defs[node].?.eql(type_defs[value].?)) { + self.reporter.reportTypeCheck( + .assignment_value_type, + self.ast.tokens.get(locations[node]), + type_defs[node].?, + self.ast.tokens.get(locations[value]), + type_defs[value].?, + "Bad value type", + ); + } + + _ = try self.generateNode(value, breaks); + + try self.emitCodeArg( + locations[node], + set_op, + @intCast(components.slot), + ); + } else { + try self.emitCodeArg( + locations[node], + get_op, + @intCast(components.slot), + ); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateNull(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + try self.emitOpCode(self.ast.nodes.items(.location)[node], .OP_NULL); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const type_defs = self.ast.nodes.items(.type_def); + const lexemes = self.ast.tokens.items(.lexeme); + const components = self.ast.nodes.items(.components)[node].ObjectDeclaration; + const location = locations[node]; + + const object_type = type_defs[node].?; + const object_def = object_type.resolved_type.?.Object; + + // Check object conforms to declared protocols + var protocol_it = object_def.conforms_to.iterator(); + while (protocol_it.next()) |kv| { + const protocol_type_def = kv.key_ptr.*; + + if (protocol_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, protocol_type_def.resolved_type.?.Placeholder); + } else { + const protocol_def = protocol_type_def.resolved_type.?.Protocol; + + var method_it = protocol_def.methods.iterator(); + while (method_it.next()) |mkv| { + var found = false; + for (components.members) |member| { + if (member.method and std.mem.eql(u8, self.ast.tokens.items(.lexeme)[member.name], mkv.key_ptr.*)) { + found = true; + if (type_defs[member.method_or_default_value.?].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder( + self.ast, + type_defs[member.method_or_default_value.?].?.resolved_type.?.Placeholder, + ); + } else if (!mkv.value_ptr.*.eql(type_defs[member.method_or_default_value.?].?)) { + self.reporter.reportTypeCheck( + .protocol_conforming, + protocol_def.location, + mkv.value_ptr.*, + self.ast.tokens.get(locations[member.method_or_default_value.?]), + type_defs[member.method_or_default_value.?].?, + "Method not conforming to protocol", + ); + } + break; + } + } + + if (!found) { + self.reporter.reportWithOrigin( + .protocol_conforming, + self.ast.tokens.get(location), + protocol_def.methods_locations.get(mkv.value_ptr.*.resolved_type.?.Function.name.string).?, + "Object declared as conforming to protocol `{s}` but doesn't implement method `{s}`", + .{ + protocol_def.name.string, + mkv.value_ptr.*.resolved_type.?.Function.name.string, + }, + null, + ); + } + } + } + } + + const name_constant = try self.makeConstant(object_def.name.toValue()); + const object_type_constant = try self.makeConstant(object_type.toValue()); + + // Put object on the stack and define global with it + try self.emitCodeArg(location, .OP_OBJECT, name_constant); + try self.emit(location, @intCast(object_type_constant)); + try self.emitCodeArg(location, .OP_DEFINE_GLOBAL, @intCast(components.slot)); + + // Put the object on the stack to set its fields + try self.emitCodeArg(location, .OP_GET_GLOBAL, @intCast(components.slot)); + + for (components.members) |member| { + const member_name = lexemes[member.name]; + const member_name_constant = try self.identifierConstant(member_name); + + if (member.method) { + const member_type_def = object_def.methods.get(member_name) orelse object_def.static_fields.get(member_name).?; + + if (member_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, member_type_def.resolved_type.?.Placeholder); + } + + // Enforce "collect" method signature + if (std.mem.eql(u8, member_name, "collect")) { + const collect_def = member_type_def.resolved_type.?.Function; + + // zig fmt: off + if (collect_def.parameters.count() > 0 + or collect_def.return_type.def_type != .Void + or collect_def.yield_type.def_type != .Void + or collect_def.error_types != null) { + // zig fmt: on + const collect_def_str = member_type_def.toStringAlloc(self.gc.allocator) catch @panic("Out of memory"); + defer collect_def_str.deinit(); + self.reporter.reportErrorFmt( + .collect_signature, + self.ast.tokens.get(locations[member.method_or_default_value.?]), + "Expected `collect` method to be `fun collect() > void` got {s}", + .{ + collect_def_str.items, + }, + ); + } + } else if (std.mem.eql(u8, member_name, "toString")) { // Enforce "toString" method signature + const tostring_def = member_type_def.resolved_type.?.Function; + + // zig fmt: off + if (tostring_def.parameters.count() > 0 + or tostring_def.return_type.def_type != .String + or tostring_def.yield_type.def_type != .Void + or tostring_def.error_types != null + or tostring_def.generic_types.count() > 0) { + // zig fmt: on + const tostring_def_str = member_type_def.toStringAlloc(self.gc.allocator) catch @panic("Out of memory"); + defer tostring_def_str.deinit(); + self.reporter.reportErrorFmt( + .tostring_signature, + self.ast.tokens.get(locations[member.method_or_default_value.?]), + "Expected `toString` method to be `fun toString() > str` got {s}", + .{ + tostring_def_str.items, + }, + ); + } + } + + const is_static = object_def.static_fields.get(member_name) != null; + + _ = try self.generateNode(member.method_or_default_value.?, breaks); + try self.emitCodeArg( + location, + if (is_static) .OP_PROPERTY else .OP_METHOD, + member_name_constant, + ); + } else { + // Properties + const property_type = object_def.fields.get(member_name) orelse object_def.static_fields.get(member_name); + const is_static = object_def.static_fields.get(member_name) != null; + + std.debug.assert(property_type != null); + + // Create property default value + if (member.method_or_default_value) |default| { + const default_type_def = type_defs[default].?; + if (default_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, default_type_def.resolved_type.?.Placeholder); + } else if (!property_type.?.eql(default_type_def)) { + self.reporter.reportTypeCheck( + .property_default_value, + object_def.location, + property_type.?, + self.ast.tokens.get(locations[default]), + default_type_def, + "Wrong property default value type", + ); + } + + if (is_static) { + try self.emitOpCode(location, .OP_COPY); + } + + _ = try self.generateNode(default, breaks); + + // Create property default value + if (is_static) { + try self.emitCodeArg(location, .OP_SET_OBJECT_PROPERTY, member_name_constant); + try self.emitOpCode(location, .OP_POP); + } else { + try self.emitCodeArg(location, .OP_PROPERTY, member_name_constant); + } + } + } + } + + // Pop object + try self.emitOpCode(location, .OP_POP); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const type_defs = self.ast.nodes.items(.type_def); + const lexemes = self.ast.tokens.items(.lexeme); + const components = self.ast.nodes.items(.components)[node].ObjectInit; + const location = locations[node]; + const node_type_def = type_defs[node].?; + + if (components.object != null and type_defs[components.object.?].?.def_type == .Object) { + _ = try self.generateNode(components.object.?, breaks); + } else if (node_type_def.def_type == .ObjectInstance) { + try self.emitOpCode(location, .OP_NULL); + } + + try self.emitCodeArg( + location, + .OP_CONSTANT, + try self.makeConstant(node_type_def.toValue()), + ); + + if (node_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, node_type_def.resolved_type.?.Placeholder); + } else if (node_type_def.def_type != .ObjectInstance and node_type_def.def_type != .ForeignContainer) { + self.reporter.reportErrorAt( + .expected_object, + self.ast.tokens.get(location), + "Expected object or foreign struct.", + ); + } + + try self.emitOpCode( + location, + if (node_type_def.def_type == .ObjectInstance) + .OP_INSTANCE + else + .OP_FCONTAINER_INSTANCE, + ); + + const fields = if (node_type_def.def_type == .ObjectInstance) + node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields + else + node_type_def.resolved_type.?.ForeignContainer.buzz_type; + + const object_location = if (node_type_def.def_type == .ObjectInstance) + node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.location + else + node_type_def.resolved_type.?.ForeignContainer.location; + + const fields_location = if (node_type_def.def_type == .ObjectInstance) + node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields_locations + else + null; + + // Keep track of what's been initialized or not by this statement + var init_properties = std.StringHashMap(void).init(self.gc.allocator); + defer init_properties.deinit(); + + for (components.properties) |property| { + const property_name = lexemes[property.name]; + const property_name_constant = try self.identifierConstant(property_name); + const value_type_def = type_defs[property.value].?; + + if (fields.get(property_name)) |prop| { + try self.emitCodeArg(location, .OP_COPY, 0); // Will be popped by OP_SET_PROPERTY + + if (value_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, value_type_def.resolved_type.?.Placeholder); + } else if (!prop.eql(value_type_def)) { + if (BuildOptions.debug_placeholders) { + std.debug.print( + "prop {}({}), value {}({})\n", + .{ + @intFromPtr(prop.resolved_type.?.ObjectInstance), + prop.optional, + @intFromPtr(value_type_def.resolved_type.?.ObjectInstance), + value_type_def.optional, + }, + ); + } + self.reporter.reportTypeCheck( + .property_type, + if (fields_location) |floc| + floc.get(property_name) + else + object_location, + prop, + self.ast.tokens.get(locations[property.value]), + value_type_def, + "Wrong property type", + ); + } + + _ = try self.generateNode(property.value, breaks); + + try init_properties.put(property_name, {}); + + try self.emitCodeArg( + location, + if (node_type_def.def_type == .ObjectInstance) + .OP_SET_INSTANCE_PROPERTY + else + .OP_SET_FCONTAINER_INSTANCE_PROPERTY, + property_name_constant, + ); + try self.emitOpCode(location, .OP_POP); // Pop property value + } else { + self.reporter.reportWithOrigin( + .property_does_not_exists, + self.ast.tokens.get(location), + object_location, + "Property `{s}` does not exists", + .{property_name}, + null, + ); + } + } + + // Did we initialized all properties without a default value? + // If union we're statisfied with only on field initialized + if (node_type_def.def_type != .ForeignContainer or node_type_def.resolved_type.?.ForeignContainer.zig_type != .Union or init_properties.count() == 0) { + const fields_defaults = if (node_type_def.def_type == .ObjectInstance) + node_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields_defaults + else + null; + + var it = fields.iterator(); + while (it.next()) |kv| { + // If ommitted in initialization and doesn't have default value + if (init_properties.get(kv.key_ptr.*) == null and (fields_defaults == null or fields_defaults.?.get(kv.key_ptr.*) == null)) { + self.reporter.reportErrorFmt( + .property_not_initialized, + self.ast.tokens.get(location), + "Property `{s}` was not initialized and has no default value", + .{kv.key_ptr.*}, + ); + } + } + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generatePattern(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + try self.emitConstant( + self.ast.nodes.items(.location)[node], + try self.ast.toValue(node, self.gc), + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateProtocolDeclaration(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const location = self.ast.nodes.items(.location)[node]; + const components = self.ast.nodes.items(.components)[node].ProtocolDeclaration; + const type_def = self.ast.nodes.items(.type_def)[node].?; + + try self.emitConstant(location, type_def.toValue()); + try self.emitCodeArg( + location, + .OP_DEFINE_GLOBAL, + @intCast(components.slot), + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateRange(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const type_defs = self.ast.nodes.items(.type_def); + const components = self.ast.nodes.items(.components)[node].Range; + const locations = self.ast.nodes.items(.location); + + // Type checking + if (type_defs[components.low].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[components.low].?.resolved_type.?.Placeholder); + } + + if (type_defs[components.high].?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_defs[components.high].?.resolved_type.?.Placeholder); + } + + if (type_defs[components.low].?.def_type != .Integer) { + self.reporter.reportTypeCheck( + .range_type, + null, + try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Integer, + }, + ), + self.ast.tokens.get(locations[components.low]), + type_defs[components.low].?, + "Bad low range limit type", + ); + } + + if (type_defs[components.high].?.def_type != .Integer) { + self.reporter.reportTypeCheck( + .range_type, + null, + try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Integer, + }, + ), + self.ast.tokens.get(locations[components.high]), + type_defs[components.high].?, + "Bad high range limit type", + ); + } + + _ = try self.generateNode(components.low, breaks); + _ = try self.generateNode(components.high, breaks); + + try self.emitOpCode(locations[node], .OP_RANGE); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateResolve(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const fiber = self.ast.nodes.items(.components)[node].Resolve; + const fiber_type_def = self.ast.nodes.items(.type_def)[fiber].?; + const locations = self.ast.nodes.items(.location); + + if (fiber_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, fiber_type_def.resolved_type.?.Placeholder); + + return null; + } + + if (fiber_type_def.def_type != .Fiber) { + self.reporter.reportErrorAt( + .fiber, + self.ast.tokens.get(locations[fiber]), + "Not a fiber", + ); + } + + _ = try self.generateNode(fiber, breaks); + + try self.emitOpCode(locations[node], .OP_RESOLVE); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateResume(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const fiber = self.ast.nodes.items(.components)[node].Resume; + const fiber_type_def = self.ast.nodes.items(.type_def)[fiber].?; + const locations = self.ast.nodes.items(.location); + + if (fiber_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, fiber_type_def.resolved_type.?.Placeholder); + + return null; + } + + if (fiber_type_def.def_type != .Fiber) { + self.reporter.reportErrorAt( + .fiber, + self.ast.tokens.get(locations[fiber]), + "Not a fiber", + ); + } + + _ = try self.generateNode(fiber, breaks); + + try self.emitOpCode(locations[node], .OP_RESUME); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateReturn(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].Return; + const type_defs = self.ast.nodes.items(.type_def); + const locations = self.ast.nodes.items(.location); + + if (components.unconditional) { + self.current.?.return_emitted = true; + } + + if (components.value) |value| { + const value_type_def = type_defs[value]; + if (value_type_def == null) { + self.reporter.reportErrorAt( + .undefined, + self.ast.tokens.get(locations[value]), + "Unknown type.", + ); + } else if (value_type_def.?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, value_type_def.?.resolved_type.?.Placeholder); + } else if (!self.current.?.function.?.type_def.resolved_type.?.Function.return_type.eql(value_type_def.?)) { + self.reporter.reportTypeCheck( + .return_type, + self.ast.tokens.get(locations[self.current.?.function_node]), + self.current.?.function.?.type_def.resolved_type.?.Function.return_type, + self.ast.tokens.get(locations[value]), + value_type_def.?, + "Return value", + ); + } + + _ = try self.generateNode(value, breaks); + } else { + try self.emitOpCode(locations[node], .OP_VOID); + } + + try self.emitOpCode(locations[node], .OP_RETURN); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateString(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const location = self.ast.nodes.items(.location)[node]; + const type_defs = self.ast.nodes.items(.type_def); + const elements = self.ast.nodes.items(.components)[node].String; + + if (elements.len == 0) { + // Push the empty string which is always the constant 0 + try self.emitCodeArg(location, .OP_CONSTANT, 0); + + try self.endScope(node); + + return null; + } + + for (elements, 0..) |element, index| { + const element_type_def = type_defs[element].?; + + if (element_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, element_type_def.resolved_type.?.Placeholder); + + continue; + } + + _ = try self.generateNode(element, breaks); + if (element_type_def.def_type != .String or element_type_def.optional) { + try self.emitOpCode(location, .OP_TO_STRING); + } + + if (index >= 1) { + try self.emitOpCode(location, .OP_ADD_STRING); + } + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateStringLiteral(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + try self.emitConstant( + self.ast.nodes.items(.location)[node], + self.ast.nodes.items(.components)[node].StringLiteral.toValue(), + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateSubscript(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const location = locations[node]; + const type_defs = self.ast.nodes.items(.type_def); + const components = self.ast.nodes.items(.components)[node].Subscript; + + _ = try self.generateNode(components.subscripted, breaks); + + const subscripted_type_def = type_defs[components.subscripted].?; + const index_type_def = type_defs[components.index].?; + const value_type_def = if (components.value) |value| type_defs[value] else null; + + if (subscripted_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, subscripted_type_def.resolved_type.?.Placeholder); + } + + if (index_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, index_type_def.resolved_type.?.Placeholder); + } + + if (components.value != null and value_type_def.?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, value_type_def.?.resolved_type.?.Placeholder); + } + + var get_code: Chunk.OpCode = .OP_GET_LIST_SUBSCRIPT; + var set_code: Chunk.OpCode = .OP_SET_LIST_SUBSCRIPT; + switch (subscripted_type_def.def_type) { + .String => { + if (index_type_def.def_type != .Integer) { + self.reporter.reportErrorAt( + .subscript_key_type, + self.ast.tokens.get(locations[components.index]), + "Expected `int` index.", + ); + } + + get_code = .OP_GET_STRING_SUBSCRIPT; + + std.debug.assert(components.value == null); + }, + .List => { + if (index_type_def.def_type != .Integer) { + self.reporter.reportErrorAt( + .subscript_key_type, + self.ast.tokens.get(locations[components.index]), + "Expected `int` index.", + ); + } + + if (components.value) |value| { + if (!subscripted_type_def.resolved_type.?.List.item_type.eql(value_type_def.?)) { + self.reporter.reportTypeCheck( + .subscript_value_type, + self.ast.tokens.get(locations[components.subscripted]), + subscripted_type_def.resolved_type.?.List.item_type, + self.ast.tokens.get(locations[value]), + value_type_def.?, + "Bad value type", + ); + } + } + }, + .Map => { + if (!subscripted_type_def.resolved_type.?.Map.key_type.eql(index_type_def)) { + self.reporter.reportTypeCheck( + .subscript_key_type, + self.ast.tokens.get(locations[components.subscripted]), + subscripted_type_def.resolved_type.?.Map.key_type, + self.ast.tokens.get(locations[components.index]), + index_type_def, + "Bad key type", + ); + } + + if (components.value) |value| { + if (!subscripted_type_def.resolved_type.?.Map.value_type.eql(value_type_def.?)) { + self.reporter.reportTypeCheck( + .subscript_value_type, + self.ast.tokens.get(locations[components.subscripted]), + subscripted_type_def.resolved_type.?.Map.value_type, + self.ast.tokens.get(locations[value]), + value_type_def.?, + "Bad value type", + ); + } + } + + get_code = .OP_GET_MAP_SUBSCRIPT; + set_code = .OP_SET_MAP_SUBSCRIPT; + }, + else => self.reporter.reportErrorAt( + .subscriptable, + self.ast.tokens.get(location), + "Not subscriptable.", + ), + } + + _ = try self.generateNode(components.index, breaks); + + if (components.value) |value| { + _ = try self.generateNode(value, breaks); + + try self.emitOpCode(location, set_code); + } else { + try self.emitOpCode(location, get_code); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateTry(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].Try; + const type_defs = self.ast.nodes.items(.type_def); + const locations = self.ast.nodes.items(.location); + const location = locations[node]; + + self.current.?.try_should_handle = std.AutoHashMap(*obj.ObjTypeDef, Ast.TokenIndex).init(self.gc.allocator); + defer { + self.current.?.try_should_handle.?.deinit(); + self.current.?.try_should_handle = null; + } + + // OP_TRY notifies runtime that we're handling error at offset + const try_jump = try self.emitJump(location, .OP_TRY); + + _ = try self.generateNode(components.body, breaks); + + // Jump reached if no error was raised + const no_error_jump = try self.emitJump( + self.ast.nodes.items(.end_location)[components.body], + .OP_JUMP, + ); + + var exit_jumps = std.ArrayList(usize).init(self.gc.allocator); + defer exit_jumps.deinit(); + + self.patchTry(try_jump); + var has_unconditional = components.unconditional_clause != null; + for (components.clauses) |clause| { + const error_type = type_defs[clause.type_def].?; + + if (error_type.eql((try self.gc.type_registry.getTypeDef(.{ .def_type = .Any })))) { + has_unconditional = true; + } + + // We assume the error is on top of the stack + try self.emitOpCode(clause.identifier, .OP_COPY); // Copy error value since its argument to the catch clause + try self.emitConstant(clause.identifier, error_type.toValue()); + try self.emitOpCode(clause.identifier, .OP_IS); + // If error type does not match, jump to next catch clause + const next_clause_jump: usize = try self.emitJump(location, .OP_JUMP_IF_FALSE); + // Pop `is` result + try self.emitOpCode(clause.identifier, .OP_POP); + + // Clause block will pop error value since its declared as a local in it + // We don't catch things is the catch clause + const previous = self.current.?.try_should_handle; + self.current.?.try_should_handle = null; + _ = try self.generateNode(clause.body, breaks); + self.current.?.try_should_handle = previous; + + // After handling the error, jump over next clauses + try exit_jumps.append(try self.emitJump(location, .OP_JUMP)); + + self.patchJump(next_clause_jump); + // Pop `is` result + try self.emitOpCode(clause.identifier, .OP_POP); + } + + if (components.unconditional_clause) |unconditional_clause| { + // pop error because its not a local of this clause + try self.emitOpCode(locations[unconditional_clause], .OP_POP); + // We don't catch things is the catch clause + const previous = self.current.?.try_should_handle; + self.current.?.try_should_handle = null; + _ = try self.generateNode(unconditional_clause, breaks); + self.current.?.try_should_handle = previous; + + try exit_jumps.append(try self.emitJump(location, .OP_JUMP)); + } + + // Tell runtime we're not in a try block anymore + try self.emitOpCode(location, .OP_TRY_END); + // Uncaught error, throw the error again + try self.emitOpCode(location, .OP_THROW); + + // Patch exit jumps + for (exit_jumps.items) |exit_jump| { + self.patchJump(exit_jump); + } + + self.patchJump(no_error_jump); + + // OP_TRY_END notifies runtime that we're not in a try block anymore + try self.emitOpCode(location, .OP_TRY_END); + + // Did we handle all errors not specified in current function signature? + if (!has_unconditional) { + var it = self.current.?.try_should_handle.?.iterator(); + while (it.next()) |kv| { + var clause: ?Ast.Try.Clause = null; + for (components.clauses) |cls| { + if (type_defs[cls.type_def] == kv.key_ptr.*) { + clause = cls; + break; + } + } + + if (clause == null) { + const err_str = try kv.key_ptr.*.toStringAlloc(self.gc.allocator); + defer err_str.deinit(); + + self.reporter.reportWithOrigin( + .error_not_handled, + self.ast.tokens.get(location), + self.ast.tokens.get(kv.value_ptr.*), + "Error type `{s}` not handled", + .{err_str.items}, + "can occur here", + ); + } + } + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateThrow(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].Throw; + const type_defs = self.ast.nodes.items(.type_def); + const location = self.ast.nodes.items(.location)[node]; + + if (components.unconditional) { + self.current.?.return_emitted = true; + } + + const expression_type_def = type_defs[components.expression].?; + if (expression_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, expression_type_def.resolved_type.?.Placeholder); + } else { + const current_error_types = self.current.?.function.?.type_def.resolved_type.?.Function.error_types; + + var found_match = false; + if (current_error_types != null) { + for (current_error_types.?) |error_type| { + if (error_type.eql(expression_type_def)) { + found_match = true; + break; + } + } + } + + if (!found_match) { + if (self.current.?.try_should_handle != null) { + // In a try catch remember to check that we handle that error when finishing parsing the try-catch + try self.current.?.try_should_handle.?.put(expression_type_def, location); + } else { + // Not in a try-catch and function signature does not expect this error type + const error_str = try type_defs[components.expression].?.toStringAlloc(self.gc.allocator); + defer error_str.deinit(); + + self.reporter.reportErrorFmt( + .unexpected_error_type, + self.ast.tokens.get(location), + "Error type `{s}` not expected", + .{error_str.items}, + ); + } + } + } + + _ = try self.generateNode(components.expression, breaks); + + try self.emitOpCode(location, .OP_THROW); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateTypeExpression(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const node_components = self.ast.nodes.items(.components); + const type_defs = self.ast.nodes.items(.type_def); + + try self.emitConstant( + self.ast.nodes.items(.location)[node], + type_defs[node_components[node].TypeExpression].?.toValue(), + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateTypeOfExpression(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + _ = try self.generateNode(self.ast.nodes.items(.components)[node].TypeOfExpression, breaks); + + try self.emitOpCode( + self.ast.nodes.items(.location)[node], + .OP_TYPEOF, + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateUnary(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].Unary; + const location = self.ast.nodes.items(.location)[node]; + const expression_location = self.ast.nodes.items(.location)[components.expression]; + const expression_type_def = self.ast.nodes.items(.type_def)[components.expression].?; + + if (expression_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, expression_type_def.resolved_type.?.Placeholder); + + return null; + } + + _ = try self.generateNode(components.expression, breaks); + + switch (components.operator) { + .Bnot => { + if (expression_type_def.def_type != .Integer) { + self.reporter.reportErrorFmt( + .bitwise_operand_type, + self.ast.tokens.get(expression_location), + "Expected type `int`, got `{s}`", + .{(try expression_type_def.toStringAlloc(self.gc.allocator)).items}, + ); + } + + try self.emitOpCode(location, .OP_BNOT); + }, + .Bang => { + if (expression_type_def.def_type != .Bool) { + self.reporter.reportErrorFmt( + .bitwise_operand_type, + self.ast.tokens.get(expression_location), + "Expected type `bool`, got `{s}`", + .{(try expression_type_def.toStringAlloc(self.gc.allocator)).items}, + ); + } + + try self.emitOpCode(location, .OP_NOT); + }, + .Minus => { + if (expression_type_def.def_type != .Integer and expression_type_def.def_type != .Float) { + self.reporter.reportErrorFmt( + .arithmetic_operand_type, + self.ast.tokens.get(expression_location), + "Expected type `int` or `float`, got `{s}`", + .{(try expression_type_def.toStringAlloc(self.gc.allocator)).items}, + ); + } + + try self.emitOpCode(location, .OP_NEGATE); + }, + else => unreachable, + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateUnwrap(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const locations = self.ast.nodes.items(.location); + const location = locations[node]; + const components = self.ast.nodes.items(.components)[node].Unwrap; + + if (components.original_type.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, components.original_type.resolved_type.?.Placeholder); + + return null; + } + + if (!components.original_type.optional) { + self.reporter.reportErrorAt( + .optional, + self.ast.tokens.get(locations[components.unwrapped]), + "Not an optional", + ); + } + + _ = try self.generateNode(components.unwrapped, breaks); + + try self.emitOpCode(location, .OP_COPY); + try self.emitOpCode(location, .OP_NULL); + try self.emitOpCode(location, .OP_EQUAL); + try self.emitOpCode(location, .OP_NOT); + + const jump: usize = try self.emitJump(location, .OP_JUMP_IF_FALSE); + + if (self.opt_jumps == null) { + self.opt_jumps = std.ArrayList(usize).init(self.gc.allocator); + } + try self.opt_jumps.?.append(jump); + + try self.emitOpCode(location, .OP_POP); // Pop test result + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateVarDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].VarDeclaration; + const type_defs = self.ast.nodes.items(.type_def); + const type_def = type_defs[node].?; + const value_type_def = if (components.value) |value| + self.ast.nodes.items(.type_def)[value] + else + null; + const locations = self.ast.nodes.items(.location); + const location = locations[node]; + + if (components.value) |value| { + _ = try self.generateNode(value, breaks); + + if (value_type_def.?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, value_type_def.?.resolved_type.?.Placeholder); + } else if (type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_def.resolved_type.?.Placeholder); + } else if (!(try type_def.toInstance(self.gc.allocator, &self.gc.type_registry)).eql(value_type_def.?) and !(try (try type_def.toInstance(self.gc.allocator, &self.gc.type_registry)).cloneNonOptional(&self.gc.type_registry)).eql(value_type_def.?)) { + self.reporter.reportTypeCheck( + .assignment_value_type, + self.ast.tokens.get(location), + try type_def.toInstance(self.gc.allocator, &self.gc.type_registry), + self.ast.tokens.get(locations[value]), + value_type_def.?, + "Wrong variable type", + ); + } + } else { + try self.emitOpCode(location, .OP_NULL); + } + + if (components.slot_type == .Global) { + try self.emitCodeArg(location, .OP_DEFINE_GLOBAL, @intCast(components.slot)); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateVoid(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + try self.emitOpCode( + self.ast.nodes.items(.location)[node], + .OP_VOID, + ); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateWhile(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].While; + const type_defs = self.ast.nodes.items(.type_def); + const locations = self.ast.nodes.items(.location); + const location = locations[node]; + const condition_type_def = type_defs[components.condition].?; + + // If condition constant and false, skip the node + if (self.ast.isConstant(components.condition) and !(try self.ast.toValue(components.condition, self.gc)).boolean()) { + try self.patchOptJumps(node); + try self.endScope(node); + + return null; + } + + const loop_start: usize = self.currentCode(); + + if (condition_type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, condition_type_def.resolved_type.?.Placeholder); + } + + if (condition_type_def.def_type != .Bool) { + self.reporter.reportErrorAt( + .while_condition_type, + self.ast.tokens.get(locations[components.condition]), + "`while` condition must be bool", + ); + } + + _ = try self.generateNode(components.condition, breaks); + + const exit_jump = try self.emitJump(location, .OP_JUMP_IF_FALSE); + try self.emitOpCode(location, .OP_POP); + + var while_breaks = std.ArrayList(usize).init(self.gc.allocator); + defer while_breaks.deinit(); + + _ = try self.generateNode(components.body, &while_breaks); + + try self.emitLoop(location, loop_start); + self.patchJump(exit_jump); + + try self.emitOpCode(location, .OP_POP); // Pop condition (is not necessary if broke out of the loop) + + // Patch breaks + for (while_breaks.items) |jump| { + try self.patchJumpOrLoop(jump, loop_start); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateYield(self: *Self, node: Ast.Node.Index, breaks: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const expression = self.ast.nodes.items(.components)[node].Yield; + const type_defs = self.ast.nodes.items(.type_def); + const type_def = type_defs[node]; + const locations = self.ast.nodes.items(.location); + const location = locations[node]; + + const current_function_typedef = type_defs[self.current.?.function_node].?.resolved_type.?.Function; + const current_function_type = current_function_typedef.function_type; + switch (current_function_type) { + .Script, + .ScriptEntryPoint, + .Repl, + .EntryPoint, + .Test, + .Extern, + => self.reporter.reportErrorAt( + .yield_not_allowed, + self.ast.tokens.get(location), + "Can't yield here", + ), + else => {}, + } + + if (type_def == null) { + self.reporter.reportErrorAt( + .unknown, + self.ast.tokens.get(location), + "Unknown type.", + ); + } else if (type_def.?.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, type_def.?.resolved_type.?.Placeholder); + } else if (!self.current.?.function.?.type_def.resolved_type.?.Function.yield_type.eql(type_def.?)) { + self.reporter.reportTypeCheck( + .yield_type, + self.ast.tokens.get(locations[self.current.?.function_node]), + self.current.?.function.?.type_def.resolved_type.?.Function.yield_type, + self.ast.tokens.get(location), + type_def.?, + "Bad yield value", + ); + } + + _ = try self.generateNode(expression, breaks); + + try self.emitOpCode(location, .OP_YIELD); + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} + +fn generateZdef(self: *Self, node: Ast.Node.Index, _: ?*std.ArrayList(usize)) Error!?*obj.ObjFunction { + const components = self.ast.nodes.items(.components)[node].Zdef; + const location = self.ast.nodes.items(.location)[node]; + + for (components.elements) |*element| { + // Generate ObjNative wrapper of actual zdef + switch (element.zdef.type_def.def_type) { + .Function => { + if (element.obj_native == null) { + var timer = std.time.Timer.start() catch unreachable; + + element.obj_native = try self.jit.?.compileZdef(self.ast, element.*); + + self.jit.?.jit_time += timer.read(); + + try self.emitConstant(location, element.obj_native.?.toValue()); + } + }, + .ForeignContainer => { + var timer = std.time.Timer.start() catch unreachable; + + try self.jit.?.compileZdefContainer(self.ast, element.*); + + self.jit.?.jit_time += timer.read(); + + try self.emitConstant(location, element.zdef.type_def.toValue()); + }, + else => unreachable, + } + try self.emitCodeArg(location, .OP_DEFINE_GLOBAL, @intCast(element.slot)); + } + + try self.patchOptJumps(node); + try self.endScope(node); + + return null; +} diff --git a/src/ffi.zig b/src/FFI.zig similarity index 97% rename from src/ffi.zig rename to src/FFI.zig index 87ef72a4..44541038 100644 --- a/src/ffi.zig +++ b/src/FFI.zig @@ -1,13 +1,14 @@ const std = @import("std"); const Ast = std.zig.Ast; +const BuzzAst = @import("Ast.zig"); const o = @import("obj.zig"); -const t = @import("token.zig"); +const Token = @import("Token.zig"); const m = @import("memory.zig"); const v = @import("value.zig"); -const p = @import("parser.zig"); +const Parser = @import("Parser.zig"); const ZigType = @import("zigtypes.zig").Type; -const Reporter = @import("reporter.zig"); +const Reporter = @import("Reporter.zig"); const Self = @This(); @@ -182,9 +183,10 @@ pub const Zdef = struct { pub const State = struct { script: []const u8, - source: t.Token, + source: Token, ast: Ast, - parser: ?*p.Parser, + buzz_ast: ?BuzzAst = null, + parser: ?*Parser, parsing_type_expr: bool = false, structs: std.StringHashMap(*Zdef), }; @@ -219,9 +221,9 @@ pub fn parseTypeExpr(self: *Self, ztype: []const u8) !?*Zdef { full.writer().print("const zig_type: {s};", .{ztype}) catch @panic("Out of memory"); - var zdef = try self.parse( + const zdef = try self.parse( null, - t.Token.identifier(full.items), + Token.identifier(full.items), true, ); @@ -235,7 +237,7 @@ pub fn parseTypeExpr(self: *Self, ztype: []const u8) !?*Zdef { return if (zdef) |z| z[0] else null; } -pub fn parse(self: *Self, parser: ?*p.Parser, source: t.Token, parsing_type_expr: bool) !?[]*Zdef { +pub fn parse(self: *Self, parser: ?*Parser, source: Token, parsing_type_expr: bool) !?[]*Zdef { // TODO: maybe an Arena allocator for those kinds of things that can live for the whole process lifetime const duped = self.gc.allocator.dupeZ(u8, source.literal_string.?) catch @panic("Out of memory"); // defer self.gc.allocator.free(duped); @@ -248,6 +250,7 @@ pub fn parse(self: *Self, parser: ?*p.Parser, source: t.Token, parsing_type_expr .parsing_type_expr = parsing_type_expr, .source = source, .parser = parser, + .buzz_ast = if (parser) |p| p.ast else null, .ast = Ast.parse( self.gc.allocator, duped, @@ -360,7 +363,7 @@ fn getZdef(self: *Self, decl_index: Ast.Node.Index) !?*Zdef { const zdef = try self.getZdef(decl.data.lhs); if (zdef) |uzdef| { - var opt_zdef = try self.gc.allocator.create(Zdef); + const opt_zdef = try self.gc.allocator.create(Zdef); opt_zdef.* = Zdef{ .zig_type = .{ .Optional = .{ @@ -649,7 +652,7 @@ fn identifier(self: *Self, decl_index: Ast.Node.Index) anyerror!*Zdef { null; if ((type_def == null or zig_type == null) and self.state.?.parser != null) { - const global_idx = try self.state.?.parser.?.resolveGlobal(null, t.Token.identifier(id)); + const global_idx = try self.state.?.parser.?.resolveGlobal(null, id); const global = if (global_idx) |idx| self.state.?.parser.?.globals.items[idx] else @@ -701,7 +704,7 @@ fn ptrType(self: *Self, tag: Ast.Node.Tag, decl_index: Ast.Node.Index) anyerror! // Is it a null terminated string? // zig fmt: off - var zdef = try self.gc.allocator.create(Zdef); + const zdef = try self.gc.allocator.create(Zdef); zdef.* = if (ptr_type.const_token != null and child_type.zig_type == .Int and child_type.zig_type.Int.bits == 8 @@ -847,12 +850,12 @@ fn fnProto(self: *Self, tag: Ast.Node.Tag, decl_index: Ast.Node.Index) anyerror! parameters_zig_types.shrinkAndFree(parameters_zig_types.items.len); zig_fn_type.params = parameters_zig_types.items; - var type_def = o.ObjTypeDef{ + const type_def = o.ObjTypeDef{ .def_type = .Function, .resolved_type = .{ .Function = function_def }, }; - var zdef = try self.gc.allocator.create(Zdef); + const zdef = try self.gc.allocator.create(Zdef); zdef.* = .{ .zig_type = ZigType{ .Fn = zig_fn_type }, .type_def = try self.gc.type_registry.getTypeDef(type_def), diff --git a/src/mirjit.zig b/src/Jit.zig similarity index 66% rename from src/mirjit.zig rename to src/Jit.zig index 380bcb40..e95a0124 100644 --- a/src/mirjit.zig +++ b/src/Jit.zig @@ -1,19 +1,17 @@ const std = @import("std"); -const assert = std.debug.assert; +const Ast = @import("Ast.zig"); +const o = @import("obj.zig"); +const Value = @import("value.zig").Value; +const m = @import("mir.zig"); const builtin = @import("builtin"); const BuildOptions = @import("build_options"); -const jmp = @import("jmp.zig").jmp; - -const ZigType = @import("zigtypes.zig").Type; const r = @import("vm.zig"); const VM = r.VM; -const m = @import("mir.zig"); -const n = @import("node.zig"); -const o = @import("obj.zig"); -const v = @import("value.zig"); +const ZigType = @import("zigtypes.zig").Type; +const ExternApi = @import("jit_extern_api.zig").ExternApi; const api = @import("lib/buzz_api.zig"); -pub const Error = error{CantCompile} || VM.Error; +pub const Error = error{ CantCompile, UnwrappedNull, OutOfBound } || std.mem.Allocator.Error || std.fmt.BufPrintError; const OptJump = struct { current_insn: std.ArrayList(m.MIR_insn_t), @@ -33,7 +31,8 @@ const GenState = struct { // Frame related stuff, since we compile one function at a time, we don't stack frames while compiling - function_node: *n.FunctionNode, + ast: Ast, + function_node: Ast.Node.Index, return_counts: bool = false, return_emitted: bool = false, @@ -76,7 +75,7 @@ compiled_closures: std.AutoHashMap(*o.ObjClosure, void), blacklisted_closures: std.AutoHashMap(*o.ObjClosure, void), // MIR doesn't allow generating multiple functions at once, so we keep a set of function to compile // Once compiled, the value is set to an array of the native and raw native func_items -functions_queue: std.AutoHashMap(*n.FunctionNode, ?[2]m.MIR_item_t), +functions_queue: std.AutoHashMap(Ast.Node.Index, ?[2]m.MIR_item_t), // ObjClosures for which we later compiled the function and need to set it's native and native_raw fields objclosures_queue: std.AutoHashMap(*o.ObjClosure, void), // External api to link @@ -94,7 +93,7 @@ pub fn init(vm: *VM) Self { .ctx = m.MIR_init(), .compiled_closures = std.AutoHashMap(*o.ObjClosure, void).init(vm.gc.allocator), .blacklisted_closures = std.AutoHashMap(*o.ObjClosure, void).init(vm.gc.allocator), - .functions_queue = std.AutoHashMap(*n.FunctionNode, ?[2]m.MIR_item_t).init(vm.gc.allocator), + .functions_queue = std.AutoHashMap(Ast.Node.Index, ?[2]m.MIR_item_t).init(vm.gc.allocator), .objclosures_queue = std.AutoHashMap(*o.ObjClosure, void).init(vm.gc.allocator), .required_ext_api = std.AutoHashMap(ExternApi, void).init(vm.gc.allocator), .modules = std.ArrayList(m.MIR_module_t).init(vm.gc.allocator), @@ -104,107 +103,35 @@ pub fn init(vm: *VM) Self { pub fn deinit(self: *Self) void { self.compiled_closures.deinit(); self.blacklisted_closures.deinit(); - // assert(self.functions_queue.count() == 0); + // std.debug.assert(self.functions_queue.count() == 0); self.functions_queue.deinit(); - // assert(self.objclosures_queue.count() == 0); + // std.debug.assert(self.objclosures_queue.count() == 0); self.objclosures_queue.deinit(); self.modules.deinit(); self.required_ext_api.deinit(); m.MIR_finish(self.ctx); } -fn buildFunction(self: *Self, closure: ?*o.ObjClosure, function_node: *n.FunctionNode) Error!void { - const qualified_name = try self.getFunctionQualifiedName( - function_node, - false, - ); - defer qualified_name.deinit(); - const raw_qualified_name = try self.getFunctionQualifiedName( - function_node, - true, - ); - defer raw_qualified_name.deinit(); - - const module = m.MIR_new_module(self.ctx, @ptrCast(qualified_name.items.ptr)); - defer m.MIR_finish_module(self.ctx); - - try self.modules.append(module); - - self.state = .{ - .module = module, - .prototypes = std.AutoHashMap(ExternApi, m.MIR_item_t).init(self.vm.gc.allocator), - .function_node = function_node, - .registers = std.AutoHashMap([*:0]const u8, usize).init(self.vm.gc.allocator), - .closure = closure orelse self.state.?.closure, - }; - - if (closure) |uclosure| { - try self.compiled_closures.put(uclosure, {}); - - if (BuildOptions.jit_debug) { - std.debug.print( - "Compiling function `{s}` because it was called {}/{} times\n", - .{ - qualified_name.items, - uclosure.function.call_count, - self.call_count, - }, - ); - } - } else { - if (BuildOptions.jit_debug) { - std.debug.print( - "Compiling closure `{s}`\n", - .{ - qualified_name.items, - }, - ); - } - } - - _ = self.generateNode(function_node.toNode()) catch |err| { - if (err == Error.CantCompile) { - if (BuildOptions.jit_debug) { - std.debug.print("Not compiling `{s}`, likely because it uses a fiber\n", .{qualified_name.items}); - } - - m.MIR_finish_func(self.ctx); - - _ = self.functions_queue.remove(function_node); - if (closure) |uclosure| { - _ = self.objclosures_queue.remove(uclosure); - try self.blacklisted_closures.put(uclosure, {}); - } - } - - return err; - }; - - // Export generated function so it can be linked - _ = m.MIR_new_export(self.ctx, @ptrCast(raw_qualified_name.items.ptr)); - _ = m.MIR_new_export(self.ctx, @ptrCast(qualified_name.items.ptr)); - - if (BuildOptions.jit_debug) { - var debug_path = std.ArrayList(u8).init(self.vm.gc.allocator); - defer debug_path.deinit(); - debug_path.writer().print("./dist/gen/{s}.mod.mir\u{0}", .{qualified_name.items}) catch unreachable; - - const debug_file = std.c.fopen(@ptrCast(debug_path.items.ptr), "w").?; - defer _ = std.c.fclose(debug_file); +// Ensure queues are empty for future use +fn reset(self: *Self) void { + self.functions_queue.clearAndFree(); + self.objclosures_queue.clearAndFree(); + self.required_ext_api.clearAndFree(); + self.modules.clearAndFree(); - m.MIR_output_module(self.ctx, debug_file, module); - } + self.state.?.deinit(); + self.state = null; } -pub fn compileFunction(self: *Self, closure: *o.ObjClosure) Error!void { +pub fn compileFunction(self: *Self, ast: Ast, closure: *o.ObjClosure) Error!void { const function = closure.function; - const function_node: *n.FunctionNode = @ptrCast(@alignCast(function.node)); + const function_node = function.node; // Remember we need to set this functions fields try self.objclosures_queue.put(closure, {}); // Build the function - try self.buildFunction(closure, function_node); + try self.buildFunction(ast, closure, function_node); // Did we encountered other functions that need to be compiled? var it = self.functions_queue.iterator(); @@ -216,12 +143,12 @@ pub fn compileFunction(self: *Self, closure: *o.ObjClosure) Error!void { var it2 = self.objclosures_queue.iterator(); var sub_closure: ?*o.ObjClosure = null; while (it2.next()) |kv2| { - if (kv2.key_ptr.*.function.node == @as(*anyopaque, @ptrCast(node))) { + if (kv2.key_ptr.*.function.node == node) { sub_closure = kv2.key_ptr.*; break; } } - try self.buildFunction(sub_closure, node); + try self.buildFunction(ast, sub_closure, node); // Building a new function might have added functions in the queue, so we reset the iterator it = self.functions_queue.iterator(); @@ -265,7 +192,7 @@ pub fn compileFunction(self: *Self, closure: *o.ObjClosure) Error!void { // Find out if we need to set it in a ObjFunction var it3 = self.objclosures_queue.iterator(); while (it3.next()) |kv2| { - if (kv2.key_ptr.*.function.node == @as(*anyopaque, @ptrCast(node))) { + if (kv2.key_ptr.*.function.node == node) { kv2.key_ptr.*.function.native = native; kv2.key_ptr.*.function.native_raw = native_raw; break; @@ -276,1102 +203,1169 @@ pub fn compileFunction(self: *Self, closure: *o.ObjClosure) Error!void { self.reset(); } -// Ensure queues are empty for future use -fn reset(self: *Self) void { - self.functions_queue.clearAndFree(); - self.objclosures_queue.clearAndFree(); - self.required_ext_api.clearAndFree(); - self.modules.clearAndFree(); - - self.state.?.deinit(); - self.state = null; -} - -pub fn compileZdefContainer(self: *Self, zdef_element: *const n.ZdefNode.ZdefElement) Error!void { - var wrapper_name = std.ArrayList(u8).init(self.vm.gc.allocator); - defer wrapper_name.deinit(); - - try wrapper_name.writer().print("zdef_{s}\x00", .{zdef_element.zdef.name}); - - const module = m.MIR_new_module(self.ctx, @ptrCast(wrapper_name.items.ptr)); - defer m.MIR_finish_module(self.ctx); - - if (BuildOptions.jit_debug) { - std.debug.print( - "Compiling zdef struct getters/setters for `{s}` of type `{s}`\n", - .{ - zdef_element.zdef.name, - (zdef_element.zdef.type_def.toStringAlloc(self.vm.gc.allocator) catch unreachable).items, - }, - ); - } - - // FIXME: Not everything applies to a zdef, maybe split the two states? +fn buildFunction(self: *Self, ast: Ast, closure: ?*o.ObjClosure, function_node: Ast.Node.Index) Error!void { self.state = .{ - .module = module, + .ast = ast, + .module = undefined, .prototypes = std.AutoHashMap(ExternApi, m.MIR_item_t).init(self.vm.gc.allocator), - .function_node = undefined, + .function_node = function_node, .registers = std.AutoHashMap([*:0]const u8, usize).init(self.vm.gc.allocator), - .closure = undefined, + .closure = closure orelse self.state.?.closure, }; - defer self.reset(); - const foreign_def = zdef_element.zdef.type_def.resolved_type.?.ForeignContainer; + const qualified_name = try self.getFunctionQualifiedName( + function_node, + false, + ); + defer qualified_name.deinit(); + const raw_qualified_name = try self.getFunctionQualifiedName( + function_node, + true, + ); + defer raw_qualified_name.deinit(); - var getters = std.ArrayList(m.MIR_item_t).init(self.vm.gc.allocator); - defer getters.deinit(); - var setters = std.ArrayList(m.MIR_item_t).init(self.vm.gc.allocator); - defer setters.deinit(); + const module = m.MIR_new_module(self.ctx, @ptrCast(qualified_name.items.ptr)); + defer m.MIR_finish_module(self.ctx); - switch (foreign_def.zig_type) { - .Struct => { - for (foreign_def.zig_type.Struct.fields) |field| { - var container_field = foreign_def.fields.getEntry(field.name).?; + try self.modules.append(module); - try getters.append( - try self.buildZdefContainerGetter( - container_field.value_ptr.*.offset, - foreign_def.name.string, - field.name, - foreign_def.buzz_type.get(field.name).?, - field.type, - ), - ); + self.state.?.module = module; - try setters.append( - try self.buildZdefContainerSetter( - container_field.value_ptr.*.offset, - foreign_def.name.string, - field.name, - foreign_def.buzz_type.get(field.name).?, - field.type, - ), - ); - } - }, + if (closure) |uclosure| { + try self.compiled_closures.put(uclosure, {}); - .Union => { - for (foreign_def.zig_type.Union.fields) |field| { - var container_field = foreign_def.fields.getEntry(field.name).?; - _ = container_field; + if (BuildOptions.jit_debug) { + std.debug.print( + "Compiling function `{s}` because it was called {}/{} times\n", + .{ + qualified_name.items, + uclosure.function.call_count, + self.call_count, + }, + ); + } + } else { + if (BuildOptions.jit_debug) { + std.debug.print( + "Compiling closure `{s}`\n", + .{ + qualified_name.items, + }, + ); + } + } - try getters.append( - try self.buildZdefUnionGetter( - foreign_def.name.string, - field.name, - foreign_def.buzz_type.get(field.name).?, - field.type, - ), - ); + _ = self.generateNode(function_node) catch |err| { + if (err == Error.CantCompile) { + if (BuildOptions.jit_debug) { + std.debug.print("Not compiling `{s}`, likely because it uses a fiber\n", .{qualified_name.items}); + } - try setters.append( - try self.buildZdefUnionSetter( - foreign_def.name.string, - field.name, - foreign_def.buzz_type.get(field.name).?, - field.type, - ), - ); + m.MIR_finish_func(self.ctx); + + _ = self.functions_queue.remove(function_node); + if (closure) |uclosure| { + _ = self.objclosures_queue.remove(uclosure); + try self.blacklisted_closures.put(uclosure, {}); } - }, + } - else => unreachable, - } + return err; + }; + + // Export generated function so it can be linked + _ = m.MIR_new_export(self.ctx, @ptrCast(raw_qualified_name.items.ptr)); + _ = m.MIR_new_export(self.ctx, @ptrCast(qualified_name.items.ptr)); if (BuildOptions.jit_debug) { var debug_path = std.ArrayList(u8).init(self.vm.gc.allocator); defer debug_path.deinit(); - debug_path.writer().print("./dist/gen/zdef-{s}.mod.mir\u{0}", .{wrapper_name.items}) catch unreachable; + _ = std.mem.replace(u8, qualified_name.items, "/", ".", qualified_name.items); + debug_path.writer().print( + "./dist/gen/{s}.mod.mir\u{0}", + .{ + qualified_name.items, + }, + ) catch unreachable; const debug_file = std.c.fopen(@ptrCast(debug_path.items.ptr), "w").?; defer _ = std.c.fclose(debug_file); m.MIR_output_module(self.ctx, debug_file, module); } +} - m.MIR_load_module(self.ctx, module); - - // Load external functions - var it_ext = self.required_ext_api.iterator(); - while (it_ext.next()) |kv| { - switch (kv.key_ptr.*) { - // TODO: don't mix those with actual api functions - .rawfn, .nativefn => {}, - else => m.MIR_load_external( - self.ctx, - kv.key_ptr.*.name(), - kv.key_ptr.*.ptr(), - ), - } - } - - // Link everything together - m.MIR_link(self.ctx, m.MIR_set_lazy_gen_interface, null); +fn generateNode(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components); + const tag = self.state.?.ast.nodes.items(.tag)[node]; + const constant = self.state.?.ast.nodes.items(.value)[node] orelse if (self.state.?.ast.isConstant(node)) + try self.state.?.ast.toValue(node, self.vm.gc) + else + null; - m.MIR_gen_init(self.ctx, 1); - defer m.MIR_gen_finish(self.ctx); + var value = if (constant != null) + m.MIR_new_uint_op(self.ctx, constant.?.val) + else switch (tag) { + .Boolean => m.MIR_new_uint_op( + self.ctx, + Value.fromBoolean(components[node].Boolean).val, + ), + .Float => m.MIR_new_double_op( + self.ctx, + components[node].Float, + ), + .Integer => m.MIR_new_uint_op( + self.ctx, + Value.fromInteger(components[node].Integer).val, + ), + .StringLiteral => m.MIR_new_uint_op( + self.ctx, + components[node].StringLiteral.toValue().val, + ), + .Null => m.MIR_new_uint_op( + self.ctx, + Value.Null.val, + ), + .Void => m.MIR_new_uint_op( + self.ctx, + Value.Void.val, + ), + .String => try self.generateString(node), + .Expression => try self.generateNode(components[node].Expression), + .GenericResolve => try self.generateNode(components[node].GenericResolve), + .Grouping => try self.generateNode(components[node].Grouping), + .Function => try self.generateFunction(node), + .FunDeclaration => try self.generateFunDeclaration(node), + .VarDeclaration => try self.generateVarDeclaration(node), + .Block => try self.generateBlock(node), + .Call => try self.generateCall(node), + .NamedVariable => try self.generateNamedVariable(node), + .Return => try self.generateReturn(node), + .If => try self.generateIf(node), + .Binary => try self.generateBinary(node), + .While => try self.generateWhile(node), + .DoUntil => try self.generateDoUntil(node), + .For => try self.generateFor(node), + .Break => try self.generateBreak(node), + .Continue => try self.generateContinue(node), + .List => try self.generateList(node), + .Range => try self.generateRange(node), + .Dot => try self.generateDot(node), + .Subscript => try self.generateSubscript(node), + .Map => try self.generateMap(node), + .Is => try self.generateIs(node), + .As => try self.generateAs(node), + .Try => try self.generateTry(node), + .Throw => try self.generateThrow(node), + .Unwrap => try self.generateUnwrap(node), + .ObjectInit => try self.generateObjectInit(node), + .ForceUnwrap => try self.generateForceUnwrap(node), + .Unary => try self.generateUnary(node), + .Pattern => try self.generatePattern(node), + .ForEach => try self.generateForEach(node), + .TypeExpression => try self.generateTypeExpression(node), + .TypeOfExpression => try self.generateTypeOfExpression(node), + .AsyncCall, + .Resume, + .Resolve, + .Yield, + => return Error.CantCompile, - // Gen getters/setters - switch (foreign_def.zig_type) { - .Struct => { - for (foreign_def.zig_type.Struct.fields, 0..) |field, idx| { - var struct_field = foreign_def.fields.getEntry(field.name).?; - struct_field.value_ptr.*.getter = @alignCast(@ptrCast(m.MIR_gen( - self.ctx, - 0, - getters.items[idx], - ) orelse unreachable)); + else => { + std.debug.print("{} NYI\n", .{tag}); + unreachable; + }, + }; - struct_field.value_ptr.*.setter = @alignCast(@ptrCast(m.MIR_gen( + if (tag != .Break and tag != .Continue) { + // Patch opt jumps if needed + if (self.state.?.ast.nodes.items(.patch_opt_jumps)[node]) { + std.debug.assert(self.state.?.opt_jump != null); + + const out_label = m.MIR_new_label(self.ctx); + + // We reached here, means nothing was null, set the alloca with the value and use it has the node return value + self.MOV( + m.MIR_new_reg_op(self.ctx, self.state.?.opt_jump.?.alloca), + value.?, + ); + + self.JMP(out_label); + + // Patch opt blocks with the branching + for (self.state.?.opt_jump.?.current_insn.items) |current_insn| { + m.MIR_insert_insn_after( self.ctx, - 0, - setters.items[idx], - ) orelse unreachable)); + self.state.?.function.?, + current_insn, + m.MIR_new_insn_arr( + self.ctx, + m.MIR_BEQ, + 3, + &[_]m.MIR_op_t{ + m.MIR_new_label_op(self.ctx, out_label), + m.MIR_new_reg_op(self.ctx, self.state.?.opt_jump.?.alloca), + m.MIR_new_uint_op(self.ctx, Value.Null.val), + }, + ), + ); } - }, - .Union => { - for (foreign_def.zig_type.Union.fields, 0..) |field, idx| { - var struct_field = foreign_def.fields.getEntry(field.name).?; - struct_field.value_ptr.*.getter = @alignCast(@ptrCast(m.MIR_gen( - self.ctx, - 0, - getters.items[idx], - ) orelse unreachable)); - struct_field.value_ptr.*.setter = @alignCast(@ptrCast(m.MIR_gen( - self.ctx, - 0, - setters.items[idx], - ) orelse unreachable)); + self.append(out_label); + + value = m.MIR_new_reg_op(self.ctx, self.state.?.opt_jump.?.alloca); + + self.state.?.opt_jump.?.deinit(); + self.state.?.opt_jump = null; + } + // Close scope if needed + if (constant == null or tag != .Range) { // Range creates locals for its limits, but we don't push anything if its constant + try self.closeScope(node); + } + } + + return value; +} + +fn closeScope(self: *Self, node: Ast.Node.Index) !void { + if (self.state.?.ast.nodes.items(.ends_scope)[node]) |closing| { + for (closing) |op| { + if (op == .OP_CLOSE_UPVALUE) { + try self.buildCloseUpValues(); + } else if (op == .OP_POP) { + try self.buildPop(null); + } else { + unreachable; } - }, - else => unreachable, + } } } -fn buildZdefUnionGetter( - self: *Self, - union_name: []const u8, - field_name: []const u8, - buzz_type: *o.ObjTypeDef, - zig_type: *const ZigType, -) Error!m.MIR_item_t { - var getter_name = std.ArrayList(u8).init(self.vm.gc.allocator); - defer getter_name.deinit(); +fn buildCloseUpValues(self: *Self) !void { + const ctx_reg = self.state.?.ctx_reg.?; + const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); + const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); + const index = try self.REG("index", m.MIR_T_I64); - try getter_name.writer().print( - "zdef_union_{s}_{s}_getter\x00", - .{ - union_name, - field_name, - }, + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), ); - // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't - var vm_name = self.vm.gc.allocator.dupeZ(u8, "vm") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(vm_name); - var data_name = self.vm.gc.allocator.dupeZ(u8, "data") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(data_name); - const function = m.MIR_new_func_arr( + // *[*]Value + const stack_top_ptr = m.MIR_new_mem_op( self.ctx, - @ptrCast(getter_name.items.ptr), + m.MIR_T_P, + @offsetOf(o.NativeCtx, "stack_top"), + ctx_reg, + index, 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = @ptrCast(vm_name.ptr), - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = @ptrCast(data_name.ptr), - .size = undefined, - }, - }, ); - self.state.?.function = function; - self.state.?.vm_reg = m.MIR_reg(self.ctx, "vm", function.u.func); + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), + stack_top_ptr, + ); - const result_value = m.MIR_new_reg_op( + // [*]Value + const stack_top = m.MIR_new_mem_op( self.ctx, - try self.REG( - "result_value", - m.MIR_T_I64, - ), + m.MIR_T_P, + 0, + stack_top_ptr_base, + index, + 1, ); - const data_reg = m.MIR_reg(self.ctx, "data", function.u.func); + // Store value on stack top + self.SUB( + m.MIR_new_reg_op(self.ctx, stack_top_base), + stack_top, + m.MIR_new_uint_op(self.ctx, @sizeOf(u64)), + ); - // Getting an union field is essentialy casting it the concrete buzz type - switch (zig_type.*) { - .Struct, .Union => { - try self.buildExternApiCall( - .bz_containerFromSlice, - result_value, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, @intFromPtr(buzz_type)), - m.MIR_new_reg_op(self.ctx, data_reg), - m.MIR_new_uint_op(self.ctx, zig_type.size()), - }, - ); + try self.buildExternApiCall( + .bz_closeUpValues, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_reg_op(self.ctx, stack_top_base), }, - else => { - const index = try self.REG("index", m.MIR_T_I64); - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); + ); +} - const field_ptr = m.MIR_new_mem_op( - self.ctx, - zigToMIRType(zig_type.*), - 0, - data_reg, - index, - 0, - ); +fn buildStackPtr(self: *Self, distance: usize) !m.MIR_op_t { + const ctx_reg = self.state.?.ctx_reg.?; + const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); + const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); + const index = try self.REG("index", m.MIR_T_I64); - try self.buildZigValueToBuzzValue( - buzz_type, - zig_type.*, - field_ptr, - result_value, - ); - }, - } + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), + ); - self.RET(result_value); + // *[*]Value + const stack_top_ptr = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + @offsetOf(o.NativeCtx, "stack_top"), + ctx_reg, + index, + 1, + ); - m.MIR_finish_func(self.ctx); + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), + stack_top_ptr, + ); - _ = m.MIR_new_export(self.ctx, @ptrCast(getter_name.items.ptr)); + // [*]Value + const stack_top = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + 0, + stack_top_ptr_base, + index, + 1, + ); - return function; -} - -fn buildZdefUnionSetter( - self: *Self, - union_name: []const u8, - field_name: []const u8, - buzz_type: *o.ObjTypeDef, - zig_type: *const ZigType, -) Error!m.MIR_item_t { - var setter_name = std.ArrayList(u8).init(self.vm.gc.allocator); - defer setter_name.deinit(); - - try setter_name.writer().print( - "zdef_union_{s}_{s}_setter\x00", - .{ - union_name, - field_name, - }, + // Store value on stack top + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_base), + stack_top, ); - // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't - var vm_name = self.vm.gc.allocator.dupeZ(u8, "vm") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(vm_name); - var data_name = self.vm.gc.allocator.dupeZ(u8, "data") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(data_name); - var new_value_name = self.vm.gc.allocator.dupeZ(u8, "new_value") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(new_value_name); - const function = m.MIR_new_func_arr( + const ptr = m.MIR_new_reg_op( self.ctx, - @ptrCast(setter_name.items.ptr), - 0, - null, - 3, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = @ptrCast(vm_name.ptr), - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = @ptrCast(data_name.ptr), - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = @ptrCast(new_value_name.ptr), - .size = undefined, - }, - }, + try self.REG("ptr", m.MIR_T_I64), ); - self.state.?.function = function; - self.state.?.vm_reg = m.MIR_reg(self.ctx, "vm", function.u.func); - - const data_reg = m.MIR_reg(self.ctx, "data", function.u.func); - const new_value_reg = m.MIR_new_reg_op(self.ctx, m.MIR_reg(self.ctx, "new_value", function.u.func)); - switch (zig_type.*) { - .Struct, .Union => { - const ptr_reg = m.MIR_new_reg_op(self.ctx, try self.REG("ptr_reg", m.MIR_T_I64)); - try self.buildValueToForeignContainerPtr( - new_value_reg, - ptr_reg, - ); + self.SUB( + ptr, + stack_top, + m.MIR_new_uint_op(self.ctx, (1 + distance) * @sizeOf(u64)), + ); - try self.buildExternApiCall( - .memcpy, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, data_reg), - m.MIR_new_uint_op(self.ctx, zig_type.size()), - ptr_reg, - m.MIR_new_uint_op(self.ctx, zig_type.size()), - }, - ); - }, - else => { - const index = try self.REG("index", m.MIR_T_I64); - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); + return ptr; +} - const field_ptr = m.MIR_new_mem_op( - self.ctx, - zigToMIRType(zig_type.*), - 0, - data_reg, - index, - 0, - ); +fn buildPush(self: *Self, value: m.MIR_op_t) !void { + const ctx_reg = self.state.?.ctx_reg.?; + const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); + const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); + const index = try self.REG("index", m.MIR_T_I64); - try self.buildBuzzValueToZigValue( - buzz_type, - zig_type.*, - new_value_reg, - field_ptr, - ); - }, - } + // Avoid intertwining the push and its value expression + const value_reg = m.MIR_new_reg_op( + self.ctx, + try self.REG("value", m.MIR_T_I64), + ); + self.MOV(value_reg, value); - m.MIR_finish_func(self.ctx); + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), + ); - _ = m.MIR_new_export(self.ctx, @ptrCast(setter_name.items.ptr)); + // *[*]Value + const stack_top_ptr = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + @offsetOf(o.NativeCtx, "stack_top"), + ctx_reg, + index, + 1, + ); - return function; -} + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), + stack_top_ptr, + ); -fn buildZdefContainerGetter( - self: *Self, - offset: usize, - struct_name: []const u8, - field_name: []const u8, - buzz_type: *o.ObjTypeDef, - zig_type: *const ZigType, -) Error!m.MIR_item_t { - var getter_name = std.ArrayList(u8).init(self.vm.gc.allocator); - defer getter_name.deinit(); + // [*]Value + const stack_top = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + 0, + stack_top_ptr_base, + index, + 1, + ); - try getter_name.writer().print( - "zdef_struct_{s}_{s}_getter\x00", - .{ - struct_name, - field_name, - }, + // Store value on stack top + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_base), + stack_top, ); - // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't - var vm_name = self.vm.gc.allocator.dupeZ(u8, "vm") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(vm_name); - var data_name = self.vm.gc.allocator.dupeZ(u8, "data") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(data_name); - const function = m.MIR_new_func_arr( + const top = m.MIR_new_mem_op( self.ctx, - @ptrCast(getter_name.items.ptr), + m.MIR_T_P, + 0, + stack_top_base, + index, 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = @ptrCast(vm_name.ptr), - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = @ptrCast(data_name.ptr), - .size = undefined, - }, - }, ); - self.state.?.function = function; - self.state.?.vm_reg = m.MIR_reg(self.ctx, "vm", function.u.func); + self.MOV( + top, + value_reg, + ); - const data_reg = m.MIR_reg(self.ctx, "data", function.u.func); + // Increment stack top + self.ADD( + stack_top, + m.MIR_new_reg_op(self.ctx, stack_top_base), + m.MIR_new_uint_op(self.ctx, @sizeOf(u64)), + ); +} +// FIXME: we should not need 3 MOV to get to the value? +fn buildPop(self: *Self, dest: ?m.MIR_op_t) !void { + const ctx_reg = self.state.?.ctx_reg.?; + const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); + const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); const index = try self.REG("index", m.MIR_T_I64); + self.MOV( m.MIR_new_reg_op(self.ctx, index), m.MIR_new_uint_op(self.ctx, 0), ); - const field_ptr = m.MIR_new_mem_op( + // *[*]Value + const stack_top_ptr = m.MIR_new_mem_op( self.ctx, - zigToMIRType(zig_type.*), - @intCast(offset), - data_reg, + m.MIR_T_P, + @offsetOf(o.NativeCtx, "stack_top"), + ctx_reg, index, - 0, + 1, ); - const result_value = m.MIR_new_reg_op( + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), + stack_top_ptr, + ); + + // [*]Value + const stack_top = m.MIR_new_mem_op( self.ctx, - try self.REG( - "result_value", - m.MIR_T_I64, - ), + m.MIR_T_P, + 0, + stack_top_ptr_base, + index, + 1, ); - try self.buildZigValueToBuzzValue( - buzz_type, - zig_type.*, - field_ptr, - result_value, + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_base), + stack_top, ); - self.RET(result_value); + // Decrement stack top + self.SUB( + stack_top, + m.MIR_new_reg_op(self.ctx, stack_top_base), + m.MIR_new_uint_op(self.ctx, @sizeOf(u64)), + ); - m.MIR_finish_func(self.ctx); + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_base), + stack_top, + ); - _ = m.MIR_new_export(self.ctx, @ptrCast(getter_name.items.ptr)); + // Store new top in result reg + if (dest) |into| { + const top = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + 0, + stack_top_base, + index, + 1, + ); - return function; + self.MOV( + into, + top, + ); + } } -fn buildZdefContainerSetter( - self: *Self, - offset: usize, - struct_name: []const u8, - field_name: []const u8, - buzz_type: *o.ObjTypeDef, - zig_type: *const ZigType, -) Error!m.MIR_item_t { - var setter_name = std.ArrayList(u8).init(self.vm.gc.allocator); - defer setter_name.deinit(); +fn buildPeek(self: *Self, distance: u32, dest: m.MIR_op_t) !void { + const ctx_reg = self.state.?.ctx_reg.?; + const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); + const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); + const index = try self.REG("index", m.MIR_T_I64); - try setter_name.writer().print( - "zdef_struct_{s}_{s}_setter\x00", - .{ - struct_name, - field_name, - }, + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), ); - // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't - var vm_name = self.vm.gc.allocator.dupeZ(u8, "vm") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(vm_name); - var data_name = self.vm.gc.allocator.dupeZ(u8, "data") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(data_name); - var new_value_name = self.vm.gc.allocator.dupeZ(u8, "new_value") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(new_value_name); - const function = m.MIR_new_func_arr( + // *[*]Value + const stack_top_ptr = m.MIR_new_mem_op( self.ctx, - @ptrCast(setter_name.items.ptr), + m.MIR_T_P, + @offsetOf(o.NativeCtx, "stack_top"), + ctx_reg, + index, + 1, + ); + + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), + stack_top_ptr, + ); + + // [*]Value + const stack_top = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, 0, - null, - 3, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = @ptrCast(vm_name.ptr), - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = @ptrCast(data_name.ptr), - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = @ptrCast(new_value_name.ptr), - .size = undefined, - }, - }, + stack_top_ptr_base, + index, + 1, ); - self.state.?.function = function; - self.state.?.vm_reg = m.MIR_reg(self.ctx, "vm", function.u.func); + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_base), + stack_top, + ); - const data_reg = m.MIR_reg(self.ctx, "data", function.u.func); - const new_value_reg = m.MIR_reg(self.ctx, "new_value", function.u.func); + const top = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + (-1 - @as(i32, @intCast(distance))) * @sizeOf(u64), + stack_top_base, + index, + 1, + ); + + self.MOV(dest, top); +} +fn buildGetLocal(self: *Self, slot: usize) !m.MIR_op_t { + const ctx_reg = self.state.?.ctx_reg.?; const index = try self.REG("index", m.MIR_T_I64); + self.MOV( m.MIR_new_reg_op(self.ctx, index), m.MIR_new_uint_op(self.ctx, 0), ); - const field_ptr = m.MIR_new_mem_op( + const base = try self.REG("base", m.MIR_T_I64); + + self.MOV( + m.MIR_new_reg_op(self.ctx, base), + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, + @offsetOf(o.NativeCtx, "base"), + ctx_reg, + index, + @sizeOf(u64), + ), + ); + + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, slot), + ); + + // Avoid intertwining the get local and its value expression + const value_reg = m.MIR_new_reg_op( self.ctx, - zigToMIRType(zig_type.*), - @intCast(offset), - data_reg, - index, + try self.REG("value", m.MIR_T_I64), + ); + self.MOV( + value_reg, + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, + 0, + base, + index, + @sizeOf(u64), + ), + ); + + return value_reg; +} + +fn buildSetLocal(self: *Self, slot: usize, value: m.MIR_op_t) !void { + const ctx_reg = self.state.?.ctx_reg.?; + const index = try self.REG("index", m.MIR_T_I64); + const base = try self.REG("base", m.MIR_T_I64); + + // Avoid intertwining the set local and its value expression + const value_reg = m.MIR_new_reg_op( + self.ctx, + try self.REG("value", m.MIR_T_I64), + ); + self.MOV(value_reg, value); + + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), + ); + + self.MOV( + m.MIR_new_reg_op(self.ctx, base), + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, + @offsetOf(o.NativeCtx, "base"), + ctx_reg, + index, + 0, + ), + ); + + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, slot), + ); + + const local = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, 0, + base, + index, + @sizeOf(u64), ); - try self.buildBuzzValueToZigValue( - buzz_type, - zig_type.*, - m.MIR_new_reg_op(self.ctx, new_value_reg), - field_ptr, + self.MOV(local, value_reg); +} + +fn buildGetGlobal(self: *Self, slot: usize) !m.MIR_op_t { + const ctx_reg = self.state.?.ctx_reg.?; + const index = try self.REG("index", m.MIR_T_I64); + + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), ); - m.MIR_finish_func(self.ctx); + const globals = try self.REG("globals", m.MIR_T_I64); - _ = m.MIR_new_export(self.ctx, @ptrCast(setter_name.items.ptr)); + self.MOV( + m.MIR_new_reg_op(self.ctx, globals), + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + @offsetOf(o.NativeCtx, "globals"), + ctx_reg, + index, + @sizeOf(u64), + ), + ); - return function; + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, slot), + ); + + return m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, + 0, + globals, + index, + @sizeOf(u64), + ); } -fn buildBuzzValueToZigValue(self: *Self, buzz_type: *o.ObjTypeDef, zig_type: ZigType, buzz_value: m.MIR_op_t, dest: m.MIR_op_t) !void { - switch (zig_type) { - .Int => { - if (buzz_type.def_type == .Float) { - const tmp_float = m.MIR_new_reg_op( - self.ctx, - try self.REG("tmp_float", m.MIR_T_D), - ); +fn buildSetGlobal(self: *Self, slot: usize, value: m.MIR_op_t) !void { + const ctx_reg = self.state.?.ctx_reg.?; + const index = try self.REG("index", m.MIR_T_I64); + const globals = try self.REG("globals", m.MIR_T_I64); - // This is a int represented by a buzz float value - self.buildValueToDouble(buzz_value, tmp_float); + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), + ); - // Convert it back to an int - self.D2I(dest, tmp_float); - } else { - self.buildValueToInteger(buzz_value, dest); - } - }, - // TODO: float can't be truncated like ints, we need a D2F instruction - .Float => { - if (zig_type.Float.bits == 64) { - self.buildValueToDouble(buzz_value, dest); - } else { - assert(zig_type.Float.bits == 32); - self.buildValueToFloat(buzz_value, dest); - } - }, - .Bool => self.buildValueToBoolean(buzz_value, dest), - .Pointer => { - // Is it a [*:0]const u8 - // zig fmt: off - if (zig_type.Pointer.child.* == .Int - and zig_type.Pointer.child.Int.bits == 8 - and zig_type.Pointer.child.Int.signedness == .unsigned) { - // zig fmt: on - try self.buildValueToCString(buzz_value, dest); - } else if (zig_type.Pointer.child.* == .Struct or zig_type.Pointer.child.* == .Union) { - try self.buildValueToForeignContainerPtr(buzz_value, dest); - } else { - try self.buildValueToUserData(buzz_value, dest); - } - }, - .Optional => { - // Is it a [*:0]const u8 - // zig fmt: off - if (zig_type.Optional.child.Pointer.child.* == .Int - and zig_type.Optional.child.Pointer.child.Int.bits == 8 - and zig_type.Optional.child.Pointer.child.Int.signedness == .unsigned) { - // zig fmt: on - try self.buildValueToOptionalCString(buzz_value, dest); - } else if (zig_type.Optional.child.Pointer.child.* == .Struct) { - try self.buildValueToOptionalForeignContainerPtr(buzz_value, dest); - } else { - try self.buildValueToOptionalUserData(buzz_value, dest); - } - }, - else => unreachable, - } -} + self.MOV( + m.MIR_new_reg_op(self.ctx, globals), + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, + @offsetOf(o.NativeCtx, "globals"), + ctx_reg, + index, + 0, + ), + ); -fn buildZigValueToBuzzValue(self: *Self, buzz_type: *o.ObjTypeDef, zig_type: ZigType, zig_value: m.MIR_op_t, dest: m.MIR_op_t) !void { - switch (zig_type) { - .Int => { - if (buzz_type.def_type == .Float) { - // This is a int represented by a buzz float value - const tmp_float = m.MIR_new_reg_op( - self.ctx, - try self.REG("tmp_float", m.MIR_T_D), - ); + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, slot), + ); - // Convert it back to an int - if (zig_type.Int.signedness == .signed) { - self.I2D(tmp_float, zig_value); - } else { - self.UI2D(tmp_float, zig_value); - } + const global = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, + 0, + globals, + index, + @sizeOf(u64), + ); - // And to a buzz value - self.buildValueFromDouble(tmp_float, dest); - } else { - self.buildValueFromInteger(zig_value, dest); - } - }, - .Float => { - if (zig_type.Float.bits == 64) { - self.buildValueFromDouble(zig_value, dest); - } else { - assert(zig_type.Float.bits == 32); - self.buildValueFromFloat(zig_value, dest); - } - }, - .Bool => self.buildValueFromBoolean(zig_value, dest), - .Void => self.MOV(dest, m.MIR_new_uint_op(self.ctx, v.Value.Void.val)), - .Union, .Struct => unreachable, // FIXME: should call an api function build a ObjForeignContainer - .Pointer => { - // Is it a [*:0]const u8 - // zig fmt: off - if (zig_type.Pointer.child.* == .Int - and zig_type.Pointer.child.Int.bits == 8 - and zig_type.Pointer.child.Int.signedness == .unsigned) { - // zig fmt: on - try self.buildValueFromCString(zig_value, dest); - } else if (zig_type.Pointer.child.* == .Struct) { - try self.buildValueFromForeignContainerPtr(buzz_type, zig_value, dest); - } else { - try self.buildValueFromUserData(zig_value, dest); - } - }, - .Optional => { - // Is it a [*:0]const u8 - // zig fmt: off - if (zig_type.Optional.child.Pointer.child.* == .Int - and zig_type.Optional.child.Pointer.child.Int.bits == 8 - and zig_type.Optional.child.Pointer.child.Int.signedness == .unsigned) { - // zig fmt: on - try self.buildValueFromOptionalCString(zig_value, dest); - } else if (zig_type.Optional.child.Pointer.child.* == .Struct) { - try self.buildValueFromOptionalForeignContainerPtr(buzz_type, zig_value, dest); - } else { - try self.buildValueFromOptionalUserData(zig_value, dest); - } - }, - else => unreachable, - } + self.MOV(global, value); } -pub fn compileZdef(self: *Self, zdef: *const n.ZdefNode.ZdefElement) Error!*o.ObjNative { - var wrapper_name = std.ArrayList(u8).init(self.vm.gc.allocator); - defer wrapper_name.deinit(); +fn buildValueToBoolean(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + self.EQ( + dest, + value, + m.MIR_new_uint_op(self.ctx, Value.TrueMask), + ); +} - try wrapper_name.writer().print("zdef_{s}\x00", .{zdef.zdef.name}); +fn buildValueFromBoolean(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + const true_label = m.MIR_new_label(self.ctx); + const out_label = m.MIR_new_label(self.ctx); - var dupped_symbol = self.vm.gc.allocator.dupeZ(u8, zdef.zdef.name) catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(dupped_symbol); + self.BEQ( + m.MIR_new_label_op(self.ctx, true_label), + value, + m.MIR_new_uint_op(self.ctx, 1), + ); - const module = m.MIR_new_module(self.ctx, @ptrCast(wrapper_name.items.ptr)); - defer m.MIR_finish_module(self.ctx); + self.MOV( + dest, + m.MIR_new_uint_op(self.ctx, Value.False.val), + ); - if (BuildOptions.jit_debug) { - std.debug.print( - "Compiling zdef wrapper for `{s}` of type `{s}`\n", - .{ - zdef.zdef.name, - (zdef.zdef.type_def.toStringAlloc(self.vm.gc.allocator) catch unreachable).items, - }, - ); - } + self.JMP(out_label); - // FIXME: Not everything applies to a zdef, maybe split the two states? - self.state = .{ - .module = module, - .prototypes = std.AutoHashMap(ExternApi, m.MIR_item_t).init(self.vm.gc.allocator), - .function_node = undefined, - .registers = std.AutoHashMap([*:0]const u8, usize).init(self.vm.gc.allocator), - .closure = undefined, - }; - defer self.reset(); + self.append(true_label); - // Build wrapper - const wrapper_item = try self.buildZdefWrapper(zdef); + self.MOV( + dest, + m.MIR_new_uint_op(self.ctx, Value.True.val), + ); - _ = m.MIR_new_export(self.ctx, @ptrCast(wrapper_name.items.ptr)); + self.append(out_label); +} - if (BuildOptions.jit_debug) { - var debug_path = std.ArrayList(u8).init(self.vm.gc.allocator); - defer debug_path.deinit(); - debug_path.writer().print("./dist/gen/zdef-{s}.mod.mir\u{0}", .{zdef.zdef.name}) catch unreachable; +fn buildValueToInteger(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + self.ANDS( + dest, + value, + m.MIR_new_uint_op(self.ctx, 0xffffffff), + ); +} - const debug_file = std.c.fopen(@ptrCast(debug_path.items.ptr), "w").?; - defer _ = std.c.fclose(debug_file); +fn buildValueFromInteger(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + self.OR( + dest, + m.MIR_new_uint_op(self.ctx, Value.IntegerMask), + value, + ); +} - m.MIR_output_module(self.ctx, debug_file, module); - } +fn buildValueToObj(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + self.NOT(dest, m.MIR_new_uint_op(self.ctx, Value.PointerMask)); + self.AND(dest, value, dest); +} - m.MIR_load_module(self.ctx, module); +fn buildValueFromObj(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + self.OR( + dest, + m.MIR_new_uint_op(self.ctx, Value.PointerMask), + value, + ); +} - // Load function we're wrapping - m.MIR_load_external(self.ctx, dupped_symbol, zdef.fn_ptr.?); +fn buildValueToCString(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + try self.buildExternApiCall( + .bz_valueToCString, + dest, + &[_]m.MIR_op_t{value}, + ); +} - // Load external functions - var it_ext = self.required_ext_api.iterator(); - while (it_ext.next()) |kv| { - switch (kv.key_ptr.*) { - // TODO: don't mix those with actual api functions - .rawfn, .nativefn => {}, - else => m.MIR_load_external( - self.ctx, - kv.key_ptr.*.name(), - kv.key_ptr.*.ptr(), - ), - } - } +fn buildValueToOptionalCString(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + const null_label = m.MIR_new_label(self.ctx); - // Link everything together - m.MIR_link(self.ctx, m.MIR_set_lazy_gen_interface, null); + self.MOV( + dest, + m.MIR_new_uint_op(self.ctx, 0), + ); - m.MIR_gen_init(self.ctx, 1); - defer m.MIR_gen_finish(self.ctx); + self.BEQ( + m.MIR_new_label_op(self.ctx, null_label), + value, + m.MIR_new_uint_op(self.ctx, Value.Null.val), + ); - var obj_native = try self.vm.gc.allocateObject( - o.ObjNative, - .{ - .native = m.MIR_gen(self.ctx, 0, wrapper_item) orelse unreachable, - }, + try self.buildExternApiCall( + .bz_valueToCString, + dest, + &[_]m.MIR_op_t{value}, ); - return obj_native; + self.append(null_label); } -fn zigToMIRType(zig_type: ZigType) m.MIR_type_t { - return switch (zig_type) { - .Int => if (zig_type.Int.signedness == .signed) - switch (zig_type.Int.bits) { - 8 => m.MIR_T_I8, - 16 => m.MIR_T_I16, - 32 => m.MIR_T_I32, - 64 => m.MIR_T_I64, - else => unreachable, - } - else switch (zig_type.Int.bits) { - 8 => m.MIR_T_U8, - 16 => m.MIR_T_U16, - 32 => m.MIR_T_U32, - 64 => m.MIR_T_U64, - else => unreachable, - }, - .Float => switch (zig_type.Float.bits) { - 32 => m.MIR_T_F, - 64 => m.MIR_T_D, - else => unreachable, +fn buildValueFromCString(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + try self.buildExternApiCall( + .bz_stringZ, + dest, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + value, }, - .Bool => m.MIR_T_U8, - // Optional only allowed on pointers so its either 0 or an actual ptr - .Pointer, - .Optional, - => m.MIR_T_I64, - // See https://github.com/vnmakarov/mir/issues/332 passing struct by values will need some work - .Struct => unreachable, //m.MIR_T_BLK, - else => unreachable, - }; + ); } -fn zigToMIRRegType(zig_type: ZigType) m.MIR_type_t { - return switch (zig_type) { - .Int, .Bool, .Pointer => m.MIR_T_I64, - .Float => switch (zig_type.Float.bits) { - 32 => m.MIR_T_F, - 64 => m.MIR_T_D, - else => unreachable, - }, - // See https://github.com/vnmakarov/mir/issues/332 passing struct by values will need some work - .Struct => unreachable, //m.MIR_T_BLK, - // Optional are only allowed on pointers - .Optional => m.MIR_T_I64, - else => { - std.debug.print("{}\n", .{zig_type}); - unreachable; +fn buildValueFromOptionalCString(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + const null_label = m.MIR_new_label(self.ctx); + + self.MOV( + dest, + m.MIR_new_uint_op(self.ctx, Value.Null.val), + ); + + self.BEQ( + m.MIR_new_label_op(self.ctx, null_label), + value, + m.MIR_new_uint_op(self.ctx, 0), + ); + + try self.buildExternApiCall( + .bz_stringZ, + dest, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + value, }, - }; + ); + + self.append(null_label); } -fn buildZdefWrapper(self: *Self, zdef_element: *const n.ZdefNode.ZdefElement) Error!m.MIR_item_t { - var wrapper_name = std.ArrayList(u8).init(self.vm.gc.allocator); - defer wrapper_name.deinit(); +fn buildValueToUserData(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + try self.buildExternApiCall( + .bz_valueToUserData, + dest, + &[_]m.MIR_op_t{value}, + ); +} - try wrapper_name.writer().print("zdef_{s}\x00", .{zdef_element.zdef.name}); +fn buildValueToForeignContainerPtr(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + try self.buildExternApiCall( + .bz_valueToForeignContainerPtr, + dest, + &[_]m.MIR_op_t{value}, + ); +} - var wrapper_protocol_name = std.ArrayList(u8).init(self.vm.gc.allocator); - defer wrapper_protocol_name.deinit(); +fn buildValueFromForeignContainerPtr(self: *Self, type_def: *o.ObjTypeDef, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + try self.buildExternApiCall( + .bz_containerFromSlice, + dest, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_uint_op(self.ctx, @intFromPtr(type_def)), + value, + m.MIR_new_uint_op(self.ctx, type_def.resolved_type.?.ForeignContainer.zig_type.size()), + }, + ); +} - try wrapper_protocol_name.writer().print("p_zdef_{s}\x00", .{zdef_element.zdef.name}); +fn buildValueFromOptionalForeignContainerPtr(self: *Self, type_def: *o.ObjTypeDef, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + const null_label = m.MIR_new_label(self.ctx); - var dupped_symbol = self.vm.gc.allocator.dupeZ(u8, zdef_element.zdef.name) catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(dupped_symbol); + self.MOV( + dest, + m.MIR_new_uint_op(self.ctx, Value.Null.val), + ); - // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't - var ctx_name = self.vm.gc.allocator.dupeZ(u8, "ctx") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(ctx_name); - const function = m.MIR_new_func_arr( - self.ctx, - @ptrCast(wrapper_name.items.ptr), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = @ptrCast(ctx_name.ptr), - .size = undefined, - }, + self.BEQ( + m.MIR_new_label_op(self.ctx, null_label), + value, + m.MIR_new_uint_op(self.ctx, 0), + ); + + try self.buildExternApiCall( + .bz_containerFromSlice, + dest, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_uint_op(self.ctx, @intFromPtr(type_def)), + value, + m.MIR_new_uint_op(self.ctx, type_def.resolved_type.?.ForeignContainer.zig_type.size()), }, ); - self.state.?.function = function; + self.append(null_label); +} - // Build ref to ctx arg and vm - self.state.?.ctx_reg = m.MIR_reg(self.ctx, "ctx", function.u.func); - self.state.?.vm_reg = m.MIR_new_func_reg(self.ctx, function.u.func, m.MIR_T_I64, "vm"); +fn buildValueToOptionalForeignContainerPtr(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + const null_label = m.MIR_new_label(self.ctx); - const index = try self.REG("index", m.MIR_T_I64); self.MOV( - m.MIR_new_reg_op(self.ctx, index), + dest, m.MIR_new_uint_op(self.ctx, 0), ); - self.MOV( - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "vm"), - self.state.?.ctx_reg.?, - index, - 0, - ), + + self.BEQ( + m.MIR_new_label_op(self.ctx, null_label), + value, + m.MIR_new_uint_op(self.ctx, Value.Null.val), ); - const function_def = zdef_element.zdef.type_def.resolved_type.?.Function; - const zig_function_def = zdef_element.zdef.zig_type; + try self.buildExternApiCall( + .bz_valueToForeignContainerPtr, + dest, + &[_]m.MIR_op_t{value}, + ); - // Get arguments from stack - var full_args = std.ArrayList(m.MIR_op_t).init(self.vm.gc.allocator); - defer full_args.deinit(); + self.append(null_label); +} - var arg_types = std.ArrayList(m.MIR_var_t).init(self.vm.gc.allocator); - defer arg_types.deinit(); +fn buildValueFromUserData(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + try self.buildExternApiCall( + .bz_userDataToValue, + dest, + &[_]m.MIR_op_t{value}, + ); +} - for (zig_function_def.Fn.params) |param| { - try arg_types.append( - .{ - .type = zigToMIRType( - if (param.type) |param_type| - param_type.* - else - .{ .Void = {} }, - ), - .name = "param", - .size = undefined, - }, - ); - } +fn buildValueFromOptionalUserData(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + const null_label = m.MIR_new_label(self.ctx); - const zig_return_type = if (zig_function_def.Fn.return_type) |return_type| - return_type.* - else - ZigType{ .Void = {} }; + self.MOV( + dest, + m.MIR_new_uint_op(self.ctx, Value.Null.val), + ); - const buzz_return_type = function_def.return_type; + self.BEQ( + m.MIR_new_label_op(self.ctx, null_label), + value, + m.MIR_new_uint_op(self.ctx, 0), + ); - var return_mir_type = if (zig_return_type != .Void) - zigToMIRRegType(zig_return_type) - else - null; + try self.buildExternApiCall( + .bz_userDataToValue, + dest, + &[_]m.MIR_op_t{value}, + ); - const result = if (return_mir_type) |rmt| - m.MIR_new_reg_op( - self.ctx, - try self.REG( - "result", - rmt, - ), - ) - else - null; + self.append(null_label); +} - const result_value = m.MIR_new_reg_op( - self.ctx, - try self.REG( - "result_value", - m.MIR_T_I64, - ), +fn buildValueToOptionalUserData(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + const null_label = m.MIR_new_label(self.ctx); + + self.MOV( + dest, + m.MIR_new_uint_op(self.ctx, 0), ); - // proto - try full_args.append( - m.MIR_new_ref_op( - self.ctx, - m.MIR_new_proto_arr( - self.ctx, - @ptrCast(wrapper_protocol_name.items.ptr), - if (return_mir_type != null) 1 else 0, - if (return_mir_type) |rmt| &[_]m.MIR_type_t{rmt} else null, - arg_types.items.len, - arg_types.items.ptr, - ), - ), + self.BEQ( + m.MIR_new_label_op(self.ctx, null_label), + value, + m.MIR_new_uint_op(self.ctx, Value.Null.val), ); - // import - try full_args.append( - m.MIR_new_ref_op( + + try self.buildExternApiCall( + .bz_valueToUserData, + dest, + &[_]m.MIR_op_t{value}, + ); + + self.append(null_label); +} + +fn buildValueFromFloat(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + // Allocate memory + const addr = self.REG("cast", m.MIR_T_I64) catch unreachable; + self.ALLOCA(addr, @sizeOf(u64)); + + // Put the value in it as double + self.FMOV( + m.MIR_new_mem_op( self.ctx, - m.MIR_new_import(self.ctx, @ptrCast(dupped_symbol)), + m.MIR_T_F, + 0, + addr, + 0, + 0, ), + value, ); - if (result) |res| { - try full_args.append(res); - } - // actual args - var it = function_def.parameters.iterator(); - var idx = function_def.parameters.count(); - var zidx: usize = 0; - while (it.next() != null) : ({ - idx -= 1; - zidx += 1; - }) { - const param_value = m.MIR_new_reg_op( - self.ctx, - try self.REG("param_value", m.MIR_T_I64), - ); - const param = m.MIR_new_reg_op( - self.ctx, - try self.REG("param", zigToMIRRegType(zig_function_def.Fn.params[zidx].type.?.*)), - ); - try self.buildPeek(@intCast(idx - 1), param_value); + // Take it out as u64 + self.MOV( + dest, + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, + 0, + addr, + 0, + 0, + ), + ); +} - try self.buildBuzzValueToZigValue( - function_def.parameters.get(function_def.parameters.keys()[zidx]).?, - zig_function_def.Fn.params[zidx].type.?.*, - param_value, - param, - ); +fn buildValueFromDouble(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + // Allocate memory + const addr = self.REG("cast", m.MIR_T_I64) catch unreachable; + self.ALLOCA(addr, @sizeOf(u64)); - try full_args.append(param); - } + // Put the value in it as double + self.DMOV( + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_D, + 0, + addr, + 0, + 0, + ), + value, + ); - // Make the call - self.append( - m.MIR_new_insn_arr( + // Take it out as u64 + self.MOV( + dest, + m.MIR_new_mem_op( self.ctx, - m.MIR_CALL, - full_args.items.len, - full_args.items.ptr, + m.MIR_T_U64, + 0, + addr, + 0, + 0, ), ); +} - // Push result on stack - if (result) |res| { - try self.buildZigValueToBuzzValue( - buzz_return_type, - zig_return_type, - res, - result_value, - ); - try self.buildPush(result_value); - } +fn buildValueToDouble(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + // Allocate memory + const addr = self.REG("cast", m.MIR_T_I64) catch unreachable; + self.ALLOCA(addr, @sizeOf(u64)); - // Return -1/0/1 - self.RET( - m.MIR_new_int_op( + // Put the value in it as u64 + self.MOV( + m.MIR_new_mem_op( self.ctx, - if (function_def.return_type.def_type != .Void) - 1 - else - 0, + m.MIR_T_U64, + 0, + addr, + 0, + 0, ), + value, ); - m.MIR_finish_func(self.ctx); - - return function; + // Take it out as double + self.DMOV( + dest, + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_D, + 0, + addr, + 0, + 0, + ), + ); } -fn closeScope(self: *Self, node: *n.ParseNode) !void { - if (node.ends_scope) |closing| { - for (closing.items) |op| { - if (op == .OP_CLOSE_UPVALUE) { - try self.buildCloseUpValues(); - } else if (op == .OP_POP) { - try self.buildPop(null); - } else { - unreachable; - } - } - } +fn buildValueToFloat(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { + // Allocate memory + const addr = self.REG("cast", m.MIR_T_I64) catch unreachable; + self.ALLOCA(addr, @sizeOf(u64)); + + // Put the value in it as u64 + self.MOV( + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_U64, + 0, + addr, + 0, + 0, + ), + value, + ); + + // Take it out as float + self.FMOV( + dest, + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_F, + 0, + addr, + 0, + 0, + ), + ); } -fn buildCloseUpValues(self: *Self) !void { - const ctx_reg = self.state.?.ctx_reg.?; - const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); - const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); +fn buildValueToForeignContainer(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { const index = try self.REG("index", m.MIR_T_I64); - self.MOV( m.MIR_new_reg_op(self.ctx, index), m.MIR_new_uint_op(self.ctx, 0), ); - // *[*]Value - const stack_top_ptr = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "stack_top"), - ctx_reg, - index, - 1, + const foreign = try self.REG("foreign", m.MIR_T_P); + try self.buildExternApiCall( + .bz_valueToForeignContainerPtr, + m.MIR_new_reg_op(self.ctx, foreign), + &[_]m.MIR_op_t{value}, ); self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), - stack_top_ptr, + dest, + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + 0, + foreign, + index, + 0, + ), + ); +} + +fn buildReturn(self: *Self, value: m.MIR_op_t) !void { + const ctx_reg = self.state.?.ctx_reg.?; + const index = try self.REG("index", m.MIR_T_I64); + + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), ); + // Get base // [*]Value - const stack_top = m.MIR_new_mem_op( + const base = m.MIR_new_mem_op( self.ctx, m.MIR_T_P, - 0, - stack_top_ptr_base, + @offsetOf(o.NativeCtx, "base"), + ctx_reg, index, 1, ); - // Store value on stack top - self.SUB( - m.MIR_new_reg_op(self.ctx, stack_top_base), - stack_top, - m.MIR_new_uint_op(self.ctx, @sizeOf(u64)), - ); - try self.buildExternApiCall( .bz_closeUpValues, null, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_reg_op(self.ctx, stack_top_base), + base, }, ); -} - -fn buildStackPtr(self: *Self, distance: usize) !m.MIR_op_t { - const ctx_reg = self.state.?.ctx_reg.?; - const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); - const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); - const index = try self.REG("index", m.MIR_T_I64); - - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); // *[*]Value const stack_top_ptr = m.MIR_new_mem_op( @@ -1383,3885 +1377,4010 @@ fn buildStackPtr(self: *Self, distance: usize) !m.MIR_op_t { 1, ); - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), - stack_top_ptr, - ); - - // [*]Value - const stack_top = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - 0, - stack_top_ptr_base, - index, - 1, - ); - - // Store value on stack top - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_base), - stack_top, - ); + // Reset stack_top to base + self.MOV(try self.LOAD(stack_top_ptr), base); - const ptr = m.MIR_new_reg_op( - self.ctx, - try self.REG("ptr", m.MIR_T_I64), - ); + // Do return + self.RET(value); +} - self.SUB( - ptr, - stack_top, - m.MIR_new_uint_op(self.ctx, (1 + distance) * @sizeOf(u64)), - ); +// Unwrap buzz value to its raw mir Value +fn unwrap(self: *Self, def_type: o.ObjTypeDef.Type, value: m.MIR_op_t, dest: m.MIR_op_t) !void { + return switch (def_type) { + .Bool => self.buildValueToBoolean(value, dest), + .Integer => self.buildValueToInteger(value, dest), + .Float => self.buildValueToDouble(value, dest), + .Void => self.MOV(dest, value), + .String, + .Pattern, + .ObjectInstance, + .Object, + .Protocol, + .ProtocolInstance, + .Enum, + .EnumInstance, + .List, + .Map, + .Function, + .Type, + .Fiber, + .UserData, + => self.buildValueToObj(value, dest), + .ForeignContainer => try self.buildValueToForeignContainer(value, dest), + .Placeholder, + .Generic, + .Any, + => unreachable, + }; +} - return ptr; +// Wrap mir value to buzz Value +fn wrap(self: *Self, def_type: o.ObjTypeDef.Type, value: m.MIR_op_t, dest: m.MIR_op_t) void { + return switch (def_type) { + .Bool => self.buildValueFromBoolean(value, dest), + .Integer => self.buildValueFromInteger(value, dest), + .Float => self.buildValueFromDouble(value, dest), + .Void => self.MOV(dest, m.MIR_new_uint_op(self.ctx, Value.Void.val)), + .String, + .Pattern, + .ObjectInstance, + .Object, + .Protocol, + .ProtocolInstance, + .Enum, + .EnumInstance, + .List, + .Map, + .Function, + .Type, + .Fiber, + .UserData, + => self.buildValueFromObj(value, dest), + .ForeignContainer, + .Placeholder, + .Generic, + .Any, + => unreachable, + }; } -fn buildPush(self: *Self, value: m.MIR_op_t) !void { - const ctx_reg = self.state.?.ctx_reg.?; - const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); - const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); - const index = try self.REG("index", m.MIR_T_I64); +fn buildExternApiCall(self: *Self, method: ExternApi, dest: ?m.MIR_op_t, args: []const m.MIR_op_t) !void { + var full_args = std.ArrayList(m.MIR_op_t).init(self.vm.gc.allocator); + defer full_args.deinit(); - // Avoid intertwining the push and its value expression - const value_reg = m.MIR_new_reg_op( - self.ctx, - try self.REG("value", m.MIR_T_I64), - ); - self.MOV(value_reg, value); + try full_args.append(m.MIR_new_ref_op(self.ctx, try method.declare(self))); + try full_args.append(m.MIR_new_ref_op(self.ctx, m.MIR_new_import(self.ctx, method.name()))); + if (dest) |udest| { + try full_args.append(udest); + } + try full_args.appendSlice(args); - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), + self.append( + m.MIR_new_insn_arr( + self.ctx, + m.MIR_CALL, + full_args.items.len, + full_args.items.ptr, + ), ); +} - // *[*]Value - const stack_top_ptr = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "stack_top"), - ctx_reg, - index, - 1, - ); +fn generateString(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const elements = self.state.?.ast.nodes.items(.components)[node].String; + const type_defs = self.state.?.ast.nodes.items(.type_def); - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), - stack_top_ptr, - ); + if (elements.len == 0) { + return m.MIR_new_uint_op( + self.ctx, + self.state.?.closure.function.chunk.constants.items[0].val, + ); // Constant 0 is the empty string + } - // [*]Value - const stack_top = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - 0, - stack_top_ptr_base, - index, - 1, - ); + var previous: ?m.MIR_op_t = null; + for (elements) |element| { + var value = (try self.generateNode(element)).?; - // Store value on stack top - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_base), - stack_top, - ); + if (type_defs[element].?.def_type != .String or type_defs[element].?.optional) { + const dest = m.MIR_new_reg_op( + self.ctx, + try self.REG("result", m.MIR_T_I64), + ); - const top = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - 0, - stack_top_base, - index, - 1, - ); + try self.buildExternApiCall( + .bz_toString, + dest, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + value, + }, + ); - self.MOV( - top, - value_reg, - ); + value = dest; + } - // Increment stack top - self.ADD( - stack_top, - m.MIR_new_reg_op(self.ctx, stack_top_base), - m.MIR_new_uint_op(self.ctx, @sizeOf(u64)), - ); -} + if (previous) |uprevious| { + const dest = m.MIR_new_reg_op( + self.ctx, + try self.REG("result", m.MIR_T_I64), + ); -// FIXME: we should not need 3 MOV to get to the value? -fn buildPop(self: *Self, dest: ?m.MIR_op_t) !void { - const ctx_reg = self.state.?.ctx_reg.?; - const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); - const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); - const index = try self.REG("index", m.MIR_T_I64); + try self.buildExternApiCall( + .bz_objStringConcat, + dest, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + uprevious, + value, + }, + ); - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); + value = dest; + } - // *[*]Value - const stack_top_ptr = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "stack_top"), - ctx_reg, - index, - 1, - ); + if (previous != null) { + try self.buildPop(null); + } - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), - stack_top_ptr, - ); + previous = value; - // [*]Value - const stack_top = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - 0, - stack_top_ptr_base, - index, - 1, - ); + try self.buildPush(previous.?); + } - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_base), - stack_top, - ); + try self.buildPop(null); - // Decrement stack top - self.SUB( - stack_top, - m.MIR_new_reg_op(self.ctx, stack_top_base), - m.MIR_new_uint_op(self.ctx, @sizeOf(u64)), - ); + return previous.?; +} - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_base), - stack_top, - ); +fn generateNamedVariable(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].NamedVariable; + const type_def = self.state.?.ast.nodes.items(.type_def)[node]; - // Store new top in result reg - if (dest) |into| { - const top = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - 0, - stack_top_base, - index, - 1, - ); + const function_type = if (type_def.?.def_type == .Function) + type_def.?.resolved_type.?.Function.function_type + else + null; + const is_constant_fn = function_type != null and function_type.? != .Extern and function_type.? != .Anonymous; - self.MOV( - into, - top, - ); - } -} + switch (components.slot_type) { + .Global => { + if (components.value) |value| { + std.debug.assert(!is_constant_fn); -fn buildPeek(self: *Self, distance: u32, dest: m.MIR_op_t) !void { - const ctx_reg = self.state.?.ctx_reg.?; - const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); - const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); - const index = try self.REG("index", m.MIR_T_I64); + try self.buildSetGlobal(components.slot, (try self.generateNode(value)).?); - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); + return null; + } else if (is_constant_fn) { + // Get the actual Value as it is right now (which is correct since a function doesn't change) + const closure = o.ObjClosure.cast(self.state.?.closure.globals.items[components.slot].obj()).?; - // *[*]Value - const stack_top_ptr = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "stack_top"), - ctx_reg, - index, - 1, - ); + // Does it need to be compiled? + if (self.compiled_closures.get(closure) == null) { + if (self.blacklisted_closures.get(closure) != null) { + return Error.CantCompile; + } - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), - stack_top_ptr, - ); + // Remember we need to set native fields of this ObjFunction later + try self.objclosures_queue.put(closure, {}); - // [*]Value - const stack_top = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - 0, - stack_top_ptr_base, - index, - 1, - ); + // Remember that we need to compile this function later + try self.functions_queue.put(closure.function.node, null); + } - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_base), - stack_top, - ); + return m.MIR_new_uint_op(self.ctx, closure.toValue().val); + } else { + return try self.buildGetGlobal(components.slot); + } + }, + .Local => { + if (components.value) |value| { + try self.buildSetLocal(components.slot, (try self.generateNode(value)).?); - const top = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - (-1 - @as(i32, @intCast(distance))) * @sizeOf(u64), - stack_top_base, - index, - 1, - ); + return null; + } - self.MOV(dest, top); -} + return try self.buildGetLocal(components.slot); + }, + .UpValue => { + if (components.value) |value| { + try self.buildExternApiCall( + .bz_setUpValue, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), + m.MIR_new_uint_op(self.ctx, components.slot), + (try self.generateNode(value)).?, + }, + ); -fn buildGetLocal(self: *Self, slot: usize) !m.MIR_op_t { - const ctx_reg = self.state.?.ctx_reg.?; - const index = try self.REG("index", m.MIR_T_I64); + return null; + } - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); + const upvalue = m.MIR_new_reg_op( + self.ctx, + try self.REG("upvalue", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_getUpValue, + upvalue, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), + m.MIR_new_uint_op(self.ctx, components.slot), + }, + ); - const base = try self.REG("base", m.MIR_T_I64); + return upvalue; + }, + } +} - self.MOV( - m.MIR_new_reg_op(self.ctx, base), - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - @offsetOf(o.NativeCtx, "base"), - ctx_reg, - index, - @sizeOf(u64), - ), - ); +fn generateCall(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const node_types = self.state.?.ast.nodes.items(.tag); + const node_components = self.state.?.ast.nodes.items(.components); + const components = node_components[node].Call; + const type_defs = self.state.?.ast.nodes.items(.type_def); + const lexemes = self.state.?.ast.tokens.items(.lexeme); - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, slot), - ); + // This is not a call but an Enum(value) + if (type_defs[components.callee].?.def_type == .Enum) { + const result_reg = try self.REG("enum_case", m.MIR_T_I64); - // Avoid intertwining the get local and its value expression - const value_reg = m.MIR_new_reg_op( - self.ctx, - try self.REG("value", m.MIR_T_I64), - ); - self.MOV( - value_reg, - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - 0, - base, - index, - @sizeOf(u64), - ), - ); + try self.buildExternApiCall( + .bz_getEnumCaseFromValue, + m.MIR_new_reg_op(self.ctx, result_reg), + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + (try self.generateNode(components.arguments[0].value)).?, + }, + ); - return value_reg; -} + return m.MIR_new_reg_op(self.ctx, result_reg); + } -fn buildSetLocal(self: *Self, slot: usize, value: m.MIR_op_t) !void { - const ctx_reg = self.state.?.ctx_reg.?; - const index = try self.REG("index", m.MIR_T_I64); - const base = try self.REG("base", m.MIR_T_I64); + // Find out if call is invoke or regular call + const dot = if (node_types[components.callee] == .Dot) + components.callee + else + null; + const invoked_on = if (dot != null) + type_defs[node_components[dot.?].Dot.callee].?.def_type + else + null; - // Avoid intertwining the set local and its value expression - const value_reg = m.MIR_new_reg_op( - self.ctx, - try self.REG("value", m.MIR_T_I64), - ); - self.MOV(value_reg, value); + const subject = if (invoked_on != null) + try self.generateNode(node_components[dot.?].Dot.callee) + else + null; + const callee_reg = try self.REG("callee", m.MIR_T_I64); + const callee = m.MIR_new_reg_op(self.ctx, callee_reg); + if (invoked_on != null) { + switch (invoked_on.?) { + .Object => try self.buildExternApiCall( + .bz_getObjectField, + callee, + &[_]m.MIR_op_t{ + subject.?, + m.MIR_new_uint_op( + self.ctx, + (try self.vm.gc.copyString( + self.state.?.ast.tokens.items(.lexeme)[node_components[dot.?].Dot.identifier], + )).toValue().val, + ), + }, + ), + .ObjectInstance, + .ProtocolInstance, + .String, + .Pattern, + .Fiber, + .List, + .Map, + => try self.buildExternApiCall( + switch (invoked_on.?) { + .ObjectInstance, .ProtocolInstance => .bz_getInstanceField, + .String => .bz_getStringField, + .Pattern => .bz_getPatternField, + .Fiber => .bz_getFiberField, + .List => .bz_getListField, + .Map => .bz_getMapField, + else => unreachable, + }, + callee, + &[_]m.MIR_op_t{ + // vm + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + // subject + subject.?, + // member + m.MIR_new_uint_op( + self.ctx, + (try self.vm.gc.copyString( + self.state.?.ast.tokens.items(.lexeme)[node_components[dot.?].Dot.identifier], + )).toValue().val, + ), + // bound + m.MIR_new_uint_op(self.ctx, 0), + }, + ), + else => unreachable, + } + } else { + self.MOV(callee, (try self.generateNode(components.callee)).?); + } - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); + const callee_type = if (dot != null) + node_components[dot.?].Dot.member_type_def + else + type_defs[components.callee]; - self.MOV( - m.MIR_new_reg_op(self.ctx, base), - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - @offsetOf(o.NativeCtx, "base"), - ctx_reg, - index, - 0, - ), - ); + const function_type_def = callee_type.?; + const function_type = function_type_def.resolved_type.?.Function.function_type; - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, slot), - ); + const error_types = function_type_def.resolved_type.?.Function.error_types; + const has_catch_clause = components.catch_default != null and error_types != null and error_types.?.len > 0 and function_type != .Extern; - const local = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - 0, - base, - index, - @sizeOf(u64), - ); + // If we have a catch value, create alloca for return value so we can replace it when error is raised + const return_alloca = if (has_catch_clause) + try self.REG("return_value", m.MIR_T_I64) + else + null; - self.MOV(local, value_reg); -} + if (return_alloca) |alloca| { + self.ALLOCA(alloca, @sizeOf(u64)); + } -fn buildGetGlobal(self: *Self, slot: usize) !m.MIR_op_t { - const ctx_reg = self.state.?.ctx_reg.?; - const index = try self.REG("index", m.MIR_T_I64); + const post_call_label = if (has_catch_clause) + m.MIR_new_label(self.ctx) + else + null; - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); + const catch_value = if (components.catch_default) |value| + (try self.generateNode(value)).? + else + null; - const globals = try self.REG("globals", m.MIR_T_I64); + if (has_catch_clause) { + const catch_label = m.MIR_new_label(self.ctx); + const continue_label = m.MIR_new_label(self.ctx); - self.MOV( - m.MIR_new_reg_op(self.ctx, globals), - m.MIR_new_mem_op( + // setjmp to catch any error bubbling up here + const try_ctx = m.MIR_new_reg_op( self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "globals"), - ctx_reg, - index, - @sizeOf(u64), - ), - ); - - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, slot), - ); - - return m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - 0, - globals, - index, - @sizeOf(u64), - ); -} - -fn buildSetGlobal(self: *Self, slot: usize, value: m.MIR_op_t) !void { - const ctx_reg = self.state.?.ctx_reg.?; - const index = try self.REG("index", m.MIR_T_I64); - const globals = try self.REG("globals", m.MIR_T_I64); + try self.REG("try_ctx", m.MIR_T_I64), + ); - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); + try self.buildExternApiCall( + .bz_setTryCtx, + try_ctx, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, + ); - self.MOV( - m.MIR_new_reg_op(self.ctx, globals), - m.MIR_new_mem_op( + const env = m.MIR_new_reg_op( self.ctx, - m.MIR_T_U64, - @offsetOf(o.NativeCtx, "globals"), - ctx_reg, - index, - 0, - ), - ); - - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, slot), - ); + try self.REG("env", m.MIR_T_I64), + ); + self.ADD( + env, + try_ctx, + m.MIR_new_uint_op(self.ctx, @offsetOf(r.TryCtx, "env")), + ); - const global = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - 0, - globals, - index, - @sizeOf(u64), - ); + const status = try self.REG("status", m.MIR_T_I64); + try self.buildExternApiCall( + .setjmp, + m.MIR_new_reg_op(self.ctx, status), + &[_]m.MIR_op_t{env}, + ); - self.MOV(global, value); -} + self.BEQ( + m.MIR_new_label_op(self.ctx, catch_label), + m.MIR_new_reg_op(self.ctx, status), + m.MIR_new_uint_op(self.ctx, 1), + ); -fn buildValueToBoolean(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - self.EQ( - dest, - value, - m.MIR_new_uint_op(self.ctx, v.TrueMask), - ); -} + self.JMP(continue_label); -fn buildValueFromBoolean(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - const true_label = m.MIR_new_label(self.ctx); - const out_label = m.MIR_new_label(self.ctx); + self.append(catch_label); - self.BEQ( - m.MIR_new_label_op(self.ctx, true_label), - value, - m.MIR_new_uint_op(self.ctx, 1), - ); + // on error set return alloca with catch_value + // FIXME: probably not how you use the alloca, maybe use it in a mem_op + self.MOV( + m.MIR_new_reg_op(self.ctx, return_alloca.?), + catch_value.?, + ); - self.MOV( - dest, - m.MIR_new_uint_op(self.ctx, v.Value.False.val), - ); + self.JMP(post_call_label.?); - self.JMP(out_label); + // else continue + self.append(continue_label); + } - self.append(true_label); + // This is an async call, create a fiber + if (components.is_async) { + // TODO: fiber + unreachable; + } - self.MOV( - dest, - m.MIR_new_uint_op(self.ctx, v.Value.True.val), - ); + // Arguments - self.append(out_label); -} + // if invoked, first arg is `this` + if (invoked_on != null) { + _ = try self.buildPush(subject.?); + } else { + _ = try self.buildPush(m.MIR_new_uint_op(self.ctx, Value.Void.val)); + } -fn buildValueToInteger(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - self.ANDS( - dest, - value, - m.MIR_new_uint_op(self.ctx, 0xffffffff), - ); -} + const args: std.AutoArrayHashMap(*o.ObjString, *o.ObjTypeDef) = function_type_def.resolved_type.?.Function.parameters; + const defaults = function_type_def.resolved_type.?.Function.defaults; + const arg_keys = args.keys(); -fn buildValueFromInteger(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - self.OR( - dest, - m.MIR_new_uint_op(self.ctx, v.IntegerMask), - value, - ); -} + var arguments = std.AutoArrayHashMap(*o.ObjString, m.MIR_op_t).init(self.vm.gc.allocator); + defer arguments.deinit(); -fn buildValueToObj(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - self.NOT(dest, m.MIR_new_uint_op(self.ctx, v.PointerMask)); - self.AND(dest, value, dest); -} + // Evaluate arguments + for (components.arguments, 0..) |argument, index| { + const actual_arg_key = if (index == 0 and argument.name == null) + arg_keys[0] + else + (try self.vm.gc.copyString(lexemes[argument.name.?])); -fn buildValueFromObj(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - self.OR( - dest, - m.MIR_new_uint_op(self.ctx, v.PointerMask), - value, - ); -} + try arguments.put( + actual_arg_key, + (try self.generateNode(argument.value)).?, + ); + } -fn buildValueToCString(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - try self.buildExternApiCall( - .bz_valueToCString, - dest, - &[_]m.MIR_op_t{value}, - ); -} + // Push them in order on the stack with default value if missing argument + for (arg_keys) |key| { + if (arguments.get(key)) |arg| { + try self.buildPush(arg); + } else { + var value = defaults.get(key).?; + value = if (value.isObj()) try o.cloneObject(value.obj(), self.vm) else value; -fn buildValueToOptionalCString(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - const null_label = m.MIR_new_label(self.ctx); + // Push clone of default + const clone = try self.REG("clone", m.MIR_T_I64); + try self.buildExternApiCall( + .bz_clone, + m.MIR_new_reg_op(self.ctx, clone), + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_uint_op(self.ctx, value.val), + }, + ); - self.MOV( - dest, - m.MIR_new_uint_op(self.ctx, 0), - ); + try self.buildPush(m.MIR_new_reg_op(self.ctx, clone)); + } + } - self.BEQ( - m.MIR_new_label_op(self.ctx, null_label), - value, - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), + const new_ctx = try self.REG("new_ctx", m.MIR_T_I64); + self.ALLOCA(new_ctx, @sizeOf(o.NativeCtx)); + try self.buildExternApiCall( + .bz_context, + callee, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), + callee, + m.MIR_new_reg_op(self.ctx, new_ctx), + m.MIR_new_uint_op(self.ctx, arg_keys.len), + }, ); - try self.buildExternApiCall( - .bz_valueToCString, - dest, - &[_]m.MIR_op_t{value}, - ); - - self.append(null_label); -} - -fn buildValueFromCString(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - try self.buildExternApiCall( - .bz_stringZ, - dest, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - value, - }, - ); -} - -fn buildValueFromOptionalCString(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - const null_label = m.MIR_new_label(self.ctx); - - self.MOV( - dest, - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), + // Regular function, just call it + const result = try self.REG("result", m.MIR_T_I64); + self.append( + m.MIR_new_insn_arr( + self.ctx, + m.MIR_CALL, + 4, + &[_]m.MIR_op_t{ + m.MIR_new_ref_op( + self.ctx, + if (function_type == .Extern) + try ExternApi.nativefn.declare(self) + else + try ExternApi.rawfn.declare(self), + ), + callee, + m.MIR_new_reg_op(self.ctx, result), + m.MIR_new_reg_op(self.ctx, new_ctx), + }, + ), ); - self.BEQ( - m.MIR_new_label_op(self.ctx, null_label), - value, - m.MIR_new_uint_op(self.ctx, 0), - ); + if (post_call_label) |label| { + self.append(label); - try self.buildExternApiCall( - .bz_stringZ, - dest, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - value, - }, - ); + self.MOV( + m.MIR_new_reg_op(self.ctx, result), + m.MIR_new_reg_op(self.ctx, return_alloca.?), + ); + } - self.append(null_label); -} + if (function_type == .Extern) { + return try self.generateHandleExternReturn( + function_type_def.resolved_type.?.Function.error_types != null, + function_type_def.resolved_type.?.Function.return_type.def_type != .Void, + m.MIR_new_reg_op(self.ctx, result), + function_type_def.resolved_type.?.Function.parameters.count(), + catch_value, + ); + } -fn buildValueToUserData(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - try self.buildExternApiCall( - .bz_valueToUserData, - dest, - &[_]m.MIR_op_t{value}, - ); + return m.MIR_new_reg_op(self.ctx, result); } -fn buildValueToForeignContainerPtr(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - try self.buildExternApiCall( - .bz_valueToForeignContainerPtr, - dest, - &[_]m.MIR_op_t{value}, - ); -} +// Handle Extern call like VM.callNative does +fn generateHandleExternReturn( + self: *Self, + can_fail: bool, + should_return: bool, + return_code: m.MIR_op_t, + arg_count: usize, + catch_value: ?m.MIR_op_t, +) !m.MIR_op_t { + if (can_fail) { + const continue_label = m.MIR_new_label(self.ctx); -fn buildValueFromForeignContainerPtr(self: *Self, type_def: *o.ObjTypeDef, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - try self.buildExternApiCall( - .bz_containerFromSlice, - dest, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, @intFromPtr(type_def)), - value, - m.MIR_new_uint_op(self.ctx, type_def.resolved_type.?.ForeignContainer.zig_type.size()), - }, - ); -} + self.BNE( + continue_label, + return_code, + m.MIR_new_int_op(self.ctx, -1), + ); -fn buildValueFromOptionalForeignContainerPtr(self: *Self, type_def: *o.ObjTypeDef, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - const null_label = m.MIR_new_label(self.ctx); + if (catch_value) |value| { + // Pop error + const discard = try self.REG("discard", m.MIR_T_I64); + try self.buildPop(m.MIR_new_reg_op(self.ctx, discard)); - self.MOV( - dest, - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), - ); + // Push catch value + try self.buildPush(value); + } else { + try self.buildExternApiCall( + .bz_rethrow, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, + ); - self.BEQ( - m.MIR_new_label_op(self.ctx, null_label), - value, - m.MIR_new_uint_op(self.ctx, 0), - ); + try self.buildExternApiCall( + .exit, + null, + &[_]m.MIR_op_t{m.MIR_new_uint_op(self.ctx, 1)}, + ); + } - try self.buildExternApiCall( - .bz_containerFromSlice, - dest, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, @intFromPtr(type_def)), - value, - m.MIR_new_uint_op(self.ctx, type_def.resolved_type.?.ForeignContainer.zig_type.size()), - }, - ); + self.append(continue_label); + } - self.append(null_label); -} + const result = try self.REG("result", m.MIR_T_I64); + if (should_return) { + try self.buildPop(m.MIR_new_reg_op(self.ctx, result)); + } else { + self.MOV( + m.MIR_new_reg_op(self.ctx, result), + m.MIR_new_uint_op(self.ctx, Value.Void.val), + ); + } -fn buildValueToOptionalForeignContainerPtr(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - const null_label = m.MIR_new_label(self.ctx); + const ctx_reg = self.state.?.ctx_reg.?; + const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); + const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); + const index = try self.REG("index", m.MIR_T_I64); self.MOV( - dest, + m.MIR_new_reg_op(self.ctx, index), m.MIR_new_uint_op(self.ctx, 0), ); - self.BEQ( - m.MIR_new_label_op(self.ctx, null_label), - value, - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), + // *[*]Value + const stack_top_ptr = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + @offsetOf(o.NativeCtx, "stack_top"), + ctx_reg, + index, + 1, ); - try self.buildExternApiCall( - .bz_valueToForeignContainerPtr, - dest, - &[_]m.MIR_op_t{value}, + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), + stack_top_ptr, ); - self.append(null_label); -} - -fn buildValueFromUserData(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - try self.buildExternApiCall( - .bz_userDataToValue, - dest, - &[_]m.MIR_op_t{value}, + // [*]Value + const stack_top = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + 0, + stack_top_ptr_base, + index, + 1, ); -} - -fn buildValueFromOptionalUserData(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - const null_label = m.MIR_new_label(self.ctx); self.MOV( - dest, - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), + m.MIR_new_reg_op(self.ctx, stack_top_base), + stack_top, ); - self.BEQ( - m.MIR_new_label_op(self.ctx, null_label), - value, - m.MIR_new_uint_op(self.ctx, 0), + // Reset stack + self.SUB( + stack_top, + m.MIR_new_reg_op(self.ctx, stack_top_base), + m.MIR_new_uint_op(self.ctx, (arg_count + 1) * @sizeOf(u64)), ); - try self.buildExternApiCall( - .bz_userDataToValue, - dest, - &[_]m.MIR_op_t{value}, + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_base), + stack_top, ); - self.append(null_label); + return m.MIR_new_reg_op(self.ctx, result); } -fn buildValueToOptionalUserData(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - const null_label = m.MIR_new_label(self.ctx); - - self.MOV( - dest, - m.MIR_new_uint_op(self.ctx, 0), - ); +fn generateReturn(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Return; - self.BEQ( - m.MIR_new_label_op(self.ctx, null_label), - value, - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), - ); + if (components.unconditional) { + self.state.?.return_emitted = true; + } - try self.buildExternApiCall( - .bz_valueToUserData, - dest, - &[_]m.MIR_op_t{value}, + try self.buildReturn( + if (components.value) |value| + (try self.generateNode(value)).? + else + m.MIR_new_uint_op(self.ctx, Value.Void.val), ); - self.append(null_label); + return null; } -fn buildValueFromFloat(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - // Allocate memory - const addr = self.REG("cast", m.MIR_T_I64) catch unreachable; - self.ALLOCA(addr, @sizeOf(u64)); +fn generateIf(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const values = self.state.?.ast.nodes.items(.value); + const type_defs = self.state.?.ast.nodes.items(.type_def); + const components = self.state.?.ast.nodes.items(.components)[node].If; + // We assume that if condition is const, the codegen will already have generated the Value + const constant_condition = if (components.unwrapped_identifier == null and components.casted_type == null) + values[components.condition] + else + null; - // Put the value in it as double - self.FMOV( - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_F, - 0, - addr, - 0, - 0, - ), - value, + // Generate condition + const condition_value = if (constant_condition == null) + (try self.generateNode(components.condition)).? + else + null; + const condition = m.MIR_new_reg_op( + self.ctx, + try self.REG("condition", m.MIR_T_I64), ); - // Take it out as u64 - self.MOV( - dest, - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - 0, - addr, - 0, - 0, - ), - ); -} + const resolved = if (!components.is_statement) + try self.REG("resolved", m.MIR_T_I64) + else + null; -fn buildValueFromDouble(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - // Allocate memory - const addr = self.REG("cast", m.MIR_T_I64) catch unreachable; - self.ALLOCA(addr, @sizeOf(u64)); + // Is it `if (opt -> unwrapped)`? + if (components.unwrapped_identifier != null) { + try self.buildExternApiCall( + .bz_valueEqual, + condition, + &[_]m.MIR_op_t{ + condition_value.?, + m.MIR_new_uint_op(self.ctx, Value.Null.val), + }, + ); - // Put the value in it as double - self.DMOV( - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_D, - 0, - addr, - 0, - 0, - ), - value, - ); + // TODO: replace with condition ^ (MIR_OR, MIR_XOR?) 1 + const true_label = m.MIR_new_label(self.ctx); + const out_label = m.MIR_new_label(self.ctx); - // Take it out as u64 - self.MOV( - dest, - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - 0, - addr, - 0, - 0, - ), - ); -} + self.BEQ( + m.MIR_new_label_op(self.ctx, true_label), + condition, + m.MIR_new_uint_op(self.ctx, Value.True.val), + ); -fn buildValueToDouble(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - // Allocate memory - const addr = self.REG("cast", m.MIR_T_I64) catch unreachable; - self.ALLOCA(addr, @sizeOf(u64)); + self.MOV( + condition, + m.MIR_new_uint_op(self.ctx, 1), + ); - // Put the value in it as u64 - self.MOV( - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - 0, - addr, - 0, - 0, - ), - value, - ); + self.JMP(out_label); - // Take it out as double - self.DMOV( - dest, - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_D, - 0, - addr, - 0, - 0, - ), - ); -} + self.append(true_label); -fn buildValueToFloat(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) void { - // Allocate memory - const addr = self.REG("cast", m.MIR_T_I64) catch unreachable; - self.ALLOCA(addr, @sizeOf(u64)); + self.MOV( + condition, + m.MIR_new_uint_op(self.ctx, 0), + ); - // Put the value in it as u64 - self.MOV( - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_U64, - 0, - addr, - 0, - 0, - ), - value, - ); + self.append(out_label); + } else if (components.casted_type) |casted_type| { + try self.buildExternApiCall( + .bz_valueIs, + condition, + &[_]m.MIR_op_t{ + condition_value.?, + m.MIR_new_uint_op( + self.ctx, + @constCast(type_defs[casted_type].?).toValue().val, + ), + }, + ); - // Take it out as float - self.FMOV( - dest, - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_F, - 0, - addr, - 0, - 0, - ), + try self.unwrap( + .Bool, + condition, + condition, + ); + } else if (constant_condition == null) { + try self.unwrap( + .Bool, + condition_value.?, + condition, + ); + } + + const out_label = m.MIR_new_label(self.ctx); + const then_label = m.MIR_new_label(self.ctx); + const else_label = if (components.else_branch != null) + m.MIR_new_label(self.ctx) + else + null; + + if (constant_condition != null) { + self.JMP( + if (constant_condition.?.boolean()) + then_label + else if (components.else_branch != null) + else_label.? + else + out_label, + ); + } else { + self.BEQ( + m.MIR_new_label_op(self.ctx, then_label), + condition, + m.MIR_new_uint_op(self.ctx, 1), + ); + + self.JMP( + if (components.else_branch != null) + else_label.? + else + out_label, + ); + } + + if (constant_condition == null or constant_condition.?.boolean()) { + self.append(then_label); + + // Push unwrapped value as local of the then block + if (components.unwrapped_identifier != null or components.casted_type != null) { + try self.buildPush( + if (constant_condition) |constant| + m.MIR_new_uint_op(self.ctx, constant.val) + else + condition_value.?, + ); + } + + if (components.is_statement) { + _ = try self.generateNode(components.body); + } else { + self.MOV( + m.MIR_new_reg_op(self.ctx, resolved.?), + (try self.generateNode(components.body)).?, + ); + } + + self.JMP(out_label); + } + + if (constant_condition == null or !constant_condition.?.boolean()) { + if (components.else_branch) |else_branch| { + self.append(else_label); + + if (components.is_statement) { + _ = try self.generateNode(else_branch); + } else { + self.MOV( + m.MIR_new_reg_op(self.ctx, resolved.?), + (try self.generateNode(else_branch)).?, + ); + } + } + } + + self.append(out_label); + + return if (components.is_statement) + null + else + m.MIR_new_reg_op(self.ctx, resolved.?); +} + +fn generateTypeExpression(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const type_expression = self.state.?.ast.nodes.items(.components)[node].TypeExpression; + return m.MIR_new_uint_op( + self.ctx, + @constCast(self.state.?.ast.nodes.items(.type_def)[type_expression].?).toValue().val, ); } -fn buildValueToForeignContainer(self: *Self, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - const index = try self.REG("index", m.MIR_T_I64); - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), +fn generateTypeOfExpression(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const value = (try self.generateNode(self.state.?.ast.nodes.items(.components)[node].TypeOfExpression)).?; + const result = m.MIR_new_reg_op( + self.ctx, + try self.REG("typeof", m.MIR_T_I64), ); - const foreign = try self.REG("foreign", m.MIR_T_P); try self.buildExternApiCall( - .bz_valueToForeignContainerPtr, - m.MIR_new_reg_op(self.ctx, foreign), - &[_]m.MIR_op_t{value}, - ); - - self.MOV( - dest, - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - 0, - foreign, - index, - 0, - ), + .bz_valueTypeOf, + result, + &[_]m.MIR_op_t{ + value, + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, ); -} - -// Unwrap buzz value to its raw mir Value -fn unwrap(self: *Self, def_type: o.ObjTypeDef.Type, value: m.MIR_op_t, dest: m.MIR_op_t) !void { - return switch (def_type) { - .Bool => self.buildValueToBoolean(value, dest), - .Integer => self.buildValueToInteger(value, dest), - .Float => self.buildValueToDouble(value, dest), - .Void => self.MOV(dest, value), - .String, - .Pattern, - .ObjectInstance, - .Object, - .Protocol, - .ProtocolInstance, - .Enum, - .EnumInstance, - .List, - .Map, - .Function, - .Type, - .Fiber, - .UserData, - => self.buildValueToObj(value, dest), - .ForeignContainer => try self.buildValueToForeignContainer(value, dest), - .Placeholder, - .Generic, - .Any, - => unreachable, - }; -} - -// Wrap mir value to buzz Value -fn wrap(self: *Self, def_type: o.ObjTypeDef.Type, value: m.MIR_op_t, dest: m.MIR_op_t) void { - return switch (def_type) { - .Bool => self.buildValueFromBoolean(value, dest), - .Integer => self.buildValueFromInteger(value, dest), - .Float => self.buildValueFromDouble(value, dest), - .Void => self.MOV(dest, m.MIR_new_uint_op(self.ctx, v.Value.Void.val)), - .String, - .Pattern, - .ObjectInstance, - .Object, - .Protocol, - .ProtocolInstance, - .Enum, - .EnumInstance, - .List, - .Map, - .Function, - .Type, - .Fiber, - .UserData, - => self.buildValueFromObj(value, dest), - .ForeignContainer, - .Placeholder, - .Generic, - .Any, - => unreachable, - }; -} - -fn buildExternApiCall(self: *Self, method: ExternApi, dest: ?m.MIR_op_t, args: []const m.MIR_op_t) !void { - var full_args = std.ArrayList(m.MIR_op_t).init(self.vm.gc.allocator); - defer full_args.deinit(); - - try full_args.append(m.MIR_new_ref_op(self.ctx, try method.declare(self))); - try full_args.append(m.MIR_new_ref_op(self.ctx, m.MIR_new_import(self.ctx, method.name()))); - if (dest) |udest| { - try full_args.append(udest); - } - try full_args.appendSlice(args); - self.append( - m.MIR_new_insn_arr( - self.ctx, - m.MIR_CALL, - full_args.items.len, - full_args.items.ptr, - ), - ); + return result; } -fn generateNode(self: *Self, node: *n.ParseNode) Error!?m.MIR_op_t { - const constant = node.isConstant(node) and node.node_type != .List and node.node_type != .Map; +fn generateBinary(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const node_components = self.state.?.ast.nodes.items(.components); + const components = node_components[node].Binary; + const type_defs = self.state.?.ast.nodes.items(.type_def); - var value = if (constant) - m.MIR_new_uint_op( - self.ctx, - (node.toValue(node, self.vm.gc) catch return VM.Error.Custom).val, - ) - else switch (node.node_type) { - .Boolean => m.MIR_new_uint_op( - self.ctx, - v.Value.fromBoolean(n.BooleanNode.cast(node).?.constant).val, - ), - .Float => m.MIR_new_double_op( - self.ctx, - n.FloatNode.cast(node).?.float_constant, - ), - .Integer => m.MIR_new_uint_op( - self.ctx, - v.Value.fromInteger(n.IntegerNode.cast(node).?.integer_constant).val, - ), - .StringLiteral => m.MIR_new_uint_op( - self.ctx, - n.StringLiteralNode.cast(node).?.constant.toValue().val, - ), - .Null => m.MIR_new_uint_op( - self.ctx, - v.Value.Null.val, - ), - .Void => m.MIR_new_uint_op( - self.ctx, - v.Value.Void.val, - ), - .String => try self.generateString(n.StringNode.cast(node).?), - .Expression => try self.generateNode(n.ExpressionNode.cast(node).?.expression), - .GenericResolve => try self.generateNode(n.GenericResolveNode.cast(node).?.expression), - .Grouping => try self.generateNode(n.GroupingNode.cast(node).?.expression), - .Function => try self.generateFunction(n.FunctionNode.cast(node).?), - .FunDeclaration => try self.generateFunDeclaration(n.FunDeclarationNode.cast(node).?), - .VarDeclaration => try self.generateVarDeclaration(n.VarDeclarationNode.cast(node).?), - .Block => try self.generateBlock(n.BlockNode.cast(node).?), - .Call => try self.generateCall(n.CallNode.cast(node).?), - .NamedVariable => try self.generateNamedVariable(n.NamedVariableNode.cast(node).?), - .Return => try self.generateReturn(n.ReturnNode.cast(node).?), - .If => try self.generateIf(n.IfNode.cast(node).?), - .Binary => try self.generateBinary(n.BinaryNode.cast(node).?), - .While => try self.generateWhile(n.WhileNode.cast(node).?), - .DoUntil => try self.generateDoUntil(n.DoUntilNode.cast(node).?), - .For => try self.generateFor(n.ForNode.cast(node).?), - .Break => try self.generateBreak(node), - .Continue => try self.generateContinue(node), - .List => try self.generateList(n.ListNode.cast(node).?), - .Range => try self.generateRange(n.RangeNode.cast(node).?), - .Dot => try self.generateDot(n.DotNode.cast(node).?), - .Subscript => try self.generateSubscript(n.SubscriptNode.cast(node).?), - .Map => try self.generateMap(n.MapNode.cast(node).?), - .Is => try self.generateIs(n.IsNode.cast(node).?), - .As => try self.generateAs(n.AsNode.cast(node).?), - .Try => try self.generateTry(n.TryNode.cast(node).?), - .Throw => try self.generateThrow(n.ThrowNode.cast(node).?), - .Unwrap => try self.generateUnwrap(n.UnwrapNode.cast(node).?), - .ObjectInit => try self.generateObjectInit(n.ObjectInitNode.cast(node).?), - .ForceUnwrap => try self.generateForceUnwrap(n.ForceUnwrapNode.cast(node).?), - .Unary => try self.generateUnary(n.UnaryNode.cast(node).?), - .Pattern => try self.generatePattern(n.PatternNode.cast(node).?), - .ForEach => try self.generateForEach(n.ForEachNode.cast(node).?), - .TypeExpression => try self.generateTypeExpression(n.TypeExpressionNode.cast(node).?), - .TypeOfExpression => try self.generateTypeOfExpression(n.TypeOfExpressionNode.cast(node).?), - .AsyncCall, - .Resume, - .Resolve, - .Yield, - => return Error.CantCompile, + const left_type_def = type_defs[components.left].?.def_type; + const right_type_def = type_defs[components.right].?.def_type; + return switch (components.operator) { + .Ampersand, + .Bor, + .Xor, + .ShiftLeft, + .ShiftRight, + => try self.generateBitwise(components), + .QuestionQuestion, + .And, + .Or, + => try self.generateConditional(components), else => { - std.debug.print("{} NYI\n", .{node.node_type}); - unreachable; - }, - }; + const left_value = (try self.generateNode(components.left)).?; + const right_value = (try self.generateNode(components.right)).?; - if (node.node_type != .Break and node.node_type != .Continue) { - // Patch opt jumps if needed - if (node.patch_opt_jumps) { - assert(self.state.?.opt_jump != null); + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + var left = m.MIR_new_reg_op( + self.ctx, + try self.REG("left", if (left_type_def == .Float) m.MIR_T_D else m.MIR_T_I64), + ); + var right = m.MIR_new_reg_op( + self.ctx, + try self.REG("right", if (right_type_def == .Float) m.MIR_T_D else m.MIR_T_I64), + ); - const out_label = m.MIR_new_label(self.ctx); + if (left_type_def == .Integer) { + try self.unwrap(.Integer, left_value, left); + } else if (left_type_def == .Float) { + try self.unwrap(.Float, left_value, left); + } else { + self.MOV(left, left_value); + } - // We reached here, means nothing was null, set the alloca with the value and use it has the node return value - self.MOV( - m.MIR_new_reg_op(self.ctx, self.state.?.opt_jump.?.alloca), - value.?, - ); + if (right_type_def == .Integer) { + try self.unwrap(.Integer, right_value, right); + } else if (right_type_def == .Float) { + try self.unwrap(.Float, right_value, right); + } else { + self.MOV(right, right_value); + } - self.JMP(out_label); + // Avoid collection + if (left_type_def != .Integer and left_type_def != .Float) { + try self.buildPush(left_value); + } - // Patch opt blocks with the branching - for (self.state.?.opt_jump.?.current_insn.items) |current_insn| { - m.MIR_insert_insn_after( - self.ctx, - self.state.?.function.?, - current_insn, - m.MIR_new_insn_arr( - self.ctx, - m.MIR_BEQ, - 3, + if (right_type_def != .Integer and right_type_def != .Float) { + try self.buildPush(right_value); + } + + switch (components.operator) { + .EqualEqual => try self.buildExternApiCall( + .bz_valueEqual, + res, + &[_]m.MIR_op_t{ + left_value, + right_value, + }, + ), + .BangEqual => { + try self.buildExternApiCall( + .bz_valueEqual, + res, &[_]m.MIR_op_t{ - m.MIR_new_label_op(self.ctx, out_label), - m.MIR_new_reg_op(self.ctx, self.state.?.opt_jump.?.alloca), - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), + left_value, + right_value, }, - ), - ); - } + ); - self.append(out_label); + try self.unwrap(.Bool, res, res); - value = m.MIR_new_reg_op(self.ctx, self.state.?.opt_jump.?.alloca); + const true_label = m.MIR_new_label(self.ctx); + const out_label = m.MIR_new_label(self.ctx); - self.state.?.opt_jump.?.deinit(); - self.state.?.opt_jump = null; - } - // Close scope if needed - if (!constant or node.node_type != .Range) { // Range creates locals for its limits, but we don't push anything if its constant - try self.closeScope(node); - } - } + self.BEQ( + m.MIR_new_label_op(self.ctx, true_label), + res, + m.MIR_new_uint_op(self.ctx, 1), + ); - return value; -} + self.MOV( + res, + m.MIR_new_uint_op(self.ctx, Value.True.val), + ); -fn generateString(self: *Self, string_node: *n.StringNode) Error!?m.MIR_op_t { - if (string_node.elements.len == 0) { - return m.MIR_new_uint_op( - self.ctx, - self.state.?.closure.function.chunk.constants.items[0].val, - ); // Constant 0 is the empty string - } + self.JMP(out_label); - var previous: ?m.MIR_op_t = null; - for (string_node.elements) |element| { - var value = (try self.generateNode(element)).?; + self.append(true_label); - if (element.type_def.?.def_type != .String or element.type_def.?.optional) { - const dest = m.MIR_new_reg_op( - self.ctx, - try self.REG("result", m.MIR_T_I64), - ); + self.MOV( + res, + m.MIR_new_uint_op(self.ctx, Value.False.val), + ); - try self.buildExternApiCall( - .bz_toString, - dest, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - value, + self.append(out_label); }, - ); - - value = dest; - } + .Greater, .Less, .GreaterEqual, .LessEqual => { + if (left_type_def == .Float or right_type_def == .Float) { + if (left_type_def == .Integer) { + const left_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("left_float", m.MIR_T_D), + ); + self.I2D(left_f, left); + left = left_f; + } - if (previous) |uprevious| { - const dest = m.MIR_new_reg_op( - self.ctx, - try self.REG("result", m.MIR_T_I64), - ); + if (right_type_def == .Integer) { + const right_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("right_float", m.MIR_T_D), + ); + self.I2D(right_f, right); + right = right_f; + } - try self.buildExternApiCall( - .bz_objStringConcat, - dest, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - uprevious, - value, + switch (components.operator) { + .Greater => self.DGT(res, left, right), + .Less => self.DLT(res, left, right), + .GreaterEqual => self.DGE(res, left, right), + .LessEqual => self.DLE(res, left, right), + else => unreachable, + } + + self.wrap(.Bool, res, res); + } else { + switch (components.operator) { + .Greater => self.GTS(res, left, right), + .Less => self.LTS(res, left, right), + .GreaterEqual => self.GES(res, left, right), + .LessEqual => self.LES(res, left, right), + else => unreachable, + } + + self.wrap(.Bool, res, res); + } }, - ); + .Plus => { + switch (left_type_def) { + .Integer, .Float => { + if (left_type_def == .Float or right_type_def == .Float) { + if (left_type_def == .Integer) { + const left_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("left_float", m.MIR_T_D), + ); + self.I2D(left_f, left); + left = left_f; + } - value = dest; - } + if (right_type_def == .Integer) { + const right_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("right_float", m.MIR_T_D), + ); + self.I2D(right_f, right); + right = right_f; + } - if (previous != null) { - try self.buildPop(null); - } + const f_res = m.MIR_new_reg_op( + self.ctx, + try self.REG("f_res", m.MIR_T_D), + ); + self.DADD(f_res, left, right); - previous = value; + self.wrap(.Float, f_res, res); + } else { + self.ADDS(res, left, right); - try self.buildPush(previous.?); - } + self.wrap(.Integer, res, res); + } + }, + .String => { + try self.buildExternApiCall( + .bz_objStringConcat, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + left, + right, + }, + ); + }, + .List => { + try self.buildExternApiCall( + .bz_listConcat, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + left, + right, + }, + ); + }, + .Map => { + try self.buildExternApiCall( + .bz_mapConcat, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + left, + right, + }, + ); + }, + else => unreachable, + } + }, + .Minus => { + if (left_type_def == .Float or right_type_def == .Float) { + if (left_type_def == .Integer) { + const left_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("left_float", m.MIR_T_D), + ); + self.I2D(left_f, left); + left = left_f; + } - try self.buildPop(null); + if (right_type_def == .Integer) { + const right_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("right_float", m.MIR_T_D), + ); + self.I2D(right_f, right); + right = right_f; + } - return previous.?; -} + const f_res = m.MIR_new_reg_op( + self.ctx, + try self.REG("f_res", m.MIR_T_D), + ); + self.DSUB(f_res, left, right); -fn generateNamedVariable(self: *Self, named_variable_node: *n.NamedVariableNode) Error!?m.MIR_op_t { - const function_type: ?o.ObjFunction.FunctionType = if (named_variable_node.node.type_def.?.def_type == .Function) - named_variable_node.node.type_def.?.resolved_type.?.Function.function_type - else - null; - const is_constant_fn = function_type != null and function_type.? != .Extern and function_type.? != .Anonymous; + self.wrap(.Float, f_res, res); + } else { + self.SUBS(res, left, right); - switch (named_variable_node.slot_type) { - .Global => { - if (named_variable_node.value) |value| { - assert(!is_constant_fn); + self.wrap(.Integer, res, res); + } + }, + .Star => { + if (left_type_def == .Float or right_type_def == .Float) { + if (left_type_def == .Integer) { + const left_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("left_float", m.MIR_T_D), + ); + self.I2D(left_f, left); + left = left_f; + } - try self.buildSetGlobal(named_variable_node.slot, (try self.generateNode(value)).?); + if (right_type_def == .Integer) { + const right_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("right_float", m.MIR_T_D), + ); + self.I2D(right_f, right); + right = right_f; + } - return null; - } else if (is_constant_fn) { - // Get the actual Value as it is right now (which is correct since a function doesn't change) - const closure = o.ObjClosure.cast(self.state.?.closure.globals.items[named_variable_node.slot].obj()).?; + const f_res = m.MIR_new_reg_op( + self.ctx, + try self.REG("f_res", m.MIR_T_D), + ); + self.DMUL(f_res, left, right); - // Does it need to be compiled? - if (self.compiled_closures.get(closure) == null) { - if (self.blacklisted_closures.get(closure) != null) { - return Error.CantCompile; + self.wrap(.Float, f_res, res); + } else { + self.MULS(res, left, right); + + self.wrap(.Integer, res, res); } + }, + .Slash => { + if (left_type_def == .Float or right_type_def == .Float) { + if (left_type_def == .Integer) { + const left_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("left_float", m.MIR_T_D), + ); + self.I2D(left_f, left); + left = left_f; + } - // Remember we need to set native fields of this ObjFunction later - try self.objclosures_queue.put(closure, {}); + if (right_type_def == .Integer) { + const right_f = m.MIR_new_reg_op( + self.ctx, + try self.REG("right_float", m.MIR_T_D), + ); + self.I2D(right_f, right); + right = right_f; + } - // Remember that we need to compile this function later - try self.functions_queue.put( - @ptrCast( - @alignCast(closure.function.node), - ), - null, - ); - } + const f_res = m.MIR_new_reg_op( + self.ctx, + try self.REG("f_res", m.MIR_T_D), + ); + self.DDIV(f_res, left, right); - return m.MIR_new_uint_op(self.ctx, closure.toValue().val); - } else { - return try self.buildGetGlobal(named_variable_node.slot); + self.wrap(.Float, f_res, res); + } else { + self.DIVS(res, left, right); + + self.wrap(.Integer, res, res); + } + }, + .Percent => { + if (left_type_def == .Float or right_type_def == .Float) { + // FIXME: mir doesn't seem to have a mod/rem for floats? + unreachable; + } else { + self.MODS(res, left, right); + + self.wrap(.Integer, res, res); + } + }, + else => unreachable, } - }, - .Local => { - if (named_variable_node.value) |value| { - try self.buildSetLocal(named_variable_node.slot, (try self.generateNode(value)).?); - return null; + if (left_type_def != .Integer and left_type_def != .Float) { + try self.buildPop(null); + } + + if (right_type_def != .Integer and right_type_def != .Float) { + try self.buildPop(null); } - return try self.buildGetLocal(named_variable_node.slot); + return res; }, - .UpValue => { - if (named_variable_node.value) |value| { - try self.buildExternApiCall( - .bz_setUpValue, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), - m.MIR_new_uint_op(self.ctx, named_variable_node.slot), - (try self.generateNode(value)).?, - }, - ); - - return null; - } - - const upvalue = m.MIR_new_reg_op( - self.ctx, - try self.REG("upvalue", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getUpValue, - upvalue, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), - m.MIR_new_uint_op(self.ctx, named_variable_node.slot), - }, - ); - - return upvalue; - }, - } + }; } -fn generateCall(self: *Self, call_node: *n.CallNode) Error!?m.MIR_op_t { - // This is not a call but an Enum(value) - if (call_node.callee.type_def.?.def_type == .Enum) { - const value = call_node.arguments.get(call_node.arguments.keys()[0]).?; - const result_reg = try self.REG("enum_case", m.MIR_T_I64); +fn generateConditional(self: *Self, binary: Ast.Binary) Error!?m.MIR_op_t { + const value = m.MIR_new_reg_op( + self.ctx, + try self.REG("value", m.MIR_T_I64), + ); - try self.buildExternApiCall( - .bz_getEnumCaseFromValue, - m.MIR_new_reg_op(self.ctx, result_reg), - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(call_node.callee)).?, - (try self.generateNode(value)).?, - }, - ); + self.MOV( + value, + (try self.generateNode(binary.left)).?, + ); - return m.MIR_new_reg_op(self.ctx, result_reg); - } + const out_label = m.MIR_new_label(self.ctx); - // Find out if call is invoke or regular call - const dot = n.DotNode.cast(call_node.callee); - const invoked_on = if (call_node.callee.node_type == .Dot) - dot.?.callee.type_def.?.def_type - else - null; + self.BNE( + out_label, + value, + m.MIR_new_uint_op( + self.ctx, + switch (binary.operator) { + .QuestionQuestion => Value.Null.val, + .And => Value.True.val, + .Or => Value.False.val, + else => unreachable, + }, + ), + ); - const subject = if (invoked_on != null) try self.generateNode(dot.?.callee) else null; - const callee_reg = try self.REG("callee", m.MIR_T_I64); - const callee = m.MIR_new_reg_op(self.ctx, callee_reg); - if (invoked_on != null) { - switch (invoked_on.?) { - .Object => try self.buildExternApiCall( - .bz_getObjectField, - callee, - &[_]m.MIR_op_t{ - subject.?, - m.MIR_new_uint_op( - self.ctx, - (try self.vm.gc.copyString(n.DotNode.cast(call_node.callee).?.identifier.lexeme)).toValue().val, - ), - }, - ), - .ObjectInstance, - .ProtocolInstance, - .String, - .Pattern, - .Fiber, - .List, - .Map, - => try self.buildExternApiCall( - switch (invoked_on.?) { - .ObjectInstance, .ProtocolInstance => .bz_getInstanceField, - .String => .bz_getStringField, - .Pattern => .bz_getPatternField, - .Fiber => .bz_getFiberField, - .List => .bz_getListField, - .Map => .bz_getMapField, - else => unreachable, - }, - callee, - &[_]m.MIR_op_t{ - // vm - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - // subject - subject.?, - // member - m.MIR_new_uint_op( - self.ctx, - (try self.vm.gc.copyString(n.DotNode.cast(call_node.callee).?.identifier.lexeme)).toValue().val, - ), - // bound - m.MIR_new_uint_op(self.ctx, 0), - }, - ), - else => unreachable, - } - } else { - self.MOV(callee, (try self.generateNode(call_node.callee)).?); - } + self.MOV( + value, + (try self.generateNode(binary.right)).?, + ); - const callee_type = switch (call_node.callee.node_type) { - .Dot => n.DotNode.cast(call_node.callee).?.member_type_def, - else => call_node.callee.type_def, - }; + self.append(out_label); - const function_type_def = callee_type.?; - const function_type = function_type_def.resolved_type.?.Function.function_type; + return value; +} - const error_types = function_type_def.resolved_type.?.Function.error_types; - const has_catch_clause = call_node.catch_default != null and error_types != null and error_types.?.len > 0 and function_type != .Extern; +fn generateBitwise(self: *Self, binary: Ast.Binary) Error!?m.MIR_op_t { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + const left = m.MIR_new_reg_op( + self.ctx, + try self.REG("left", m.MIR_T_I64), + ); + const right = m.MIR_new_reg_op( + self.ctx, + try self.REG("right", m.MIR_T_I64), + ); - // If we have a catch value, create alloca for return value so we can replace it when error is raised - const return_alloca = if (has_catch_clause) - try self.REG("return_value", m.MIR_T_I64) - else - null; + try self.unwrap( + .Integer, + (try self.generateNode(binary.left)).?, + left, + ); + try self.unwrap( + .Integer, + (try self.generateNode(binary.right)).?, + right, + ); - if (return_alloca) |alloca| { - self.ALLOCA(alloca, @sizeOf(u64)); + switch (binary.operator) { + .Ampersand => self.AND(res, left, right), + .Bor => self.OR(res, left, right), + .Xor => self.XOR(res, left, right), + .ShiftLeft => self.SHL(res, left, right), + .ShiftRight => self.SHR(res, left, right), + else => unreachable, } - const post_call_label = if (has_catch_clause) - m.MIR_new_label(self.ctx) - else - null; + self.wrap(.Integer, res, res); - const catch_value = if (call_node.catch_default) |value| - (try self.generateNode(value)).? - else - null; + return res; +} - if (has_catch_clause) { - const catch_label = m.MIR_new_label(self.ctx); - const continue_label = m.MIR_new_label(self.ctx); +fn generateWhile(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].While; + const condition_value = self.state.?.ast.nodes.items(.value)[components.condition]; - // setjmp to catch any error bubbling up here - const try_ctx = m.MIR_new_reg_op( - self.ctx, - try self.REG("try_ctx", m.MIR_T_I64), - ); + if (condition_value != null and !condition_value.?.boolean()) { + return null; + } - try self.buildExternApiCall( - .bz_setTryCtx, - try_ctx, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - }, - ); + const cond_label = m.MIR_new_label(self.ctx); + const out_label = m.MIR_new_label(self.ctx); - const env = m.MIR_new_reg_op( - self.ctx, - try self.REG("env", m.MIR_T_I64), - ); - self.ADD( - env, - try_ctx, - m.MIR_new_uint_op(self.ctx, @offsetOf(r.TryCtx, "env")), - ); + const previous_out_label = self.state.?.break_label; + self.state.?.break_label = out_label; + const previous_continue_label = self.state.?.continue_label; + self.state.?.continue_label = cond_label; - const status = try self.REG("status", m.MIR_T_I64); - try self.buildExternApiCall( - .setjmp, - m.MIR_new_reg_op(self.ctx, status), - &[_]m.MIR_op_t{env}, - ); + self.append(cond_label); - self.BEQ( - m.MIR_new_label_op(self.ctx, catch_label), - m.MIR_new_reg_op(self.ctx, status), - m.MIR_new_uint_op(self.ctx, 1), - ); + self.BEQ( + m.MIR_new_label_op(self.ctx, out_label), + (try self.generateNode(components.condition)).?, + m.MIR_new_uint_op(self.ctx, Value.False.val), + ); - self.JMP(continue_label); + _ = try self.generateNode(components.body); - self.append(catch_label); + self.JMP(cond_label); - // on error set return alloca with catch_value - // FIXME: probably not how you use the alloca, maybe use it in a mem_op - self.MOV( - m.MIR_new_reg_op(self.ctx, return_alloca.?), - catch_value.?, - ); + self.append(out_label); - self.JMP(post_call_label.?); + self.state.?.break_label = previous_out_label; + self.state.?.continue_label = previous_continue_label; - // else continue - self.append(continue_label); - } + return null; +} - // This is an async call, create a fiber - if (call_node.async_call) { - // TODO: fiber - unreachable; - } +fn generateDoUntil(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].DoUntil; - // Arguments + const out_label = m.MIR_new_label(self.ctx); + const loop_label = m.MIR_new_label(self.ctx); - // if invoked, first arg is `this` - if (invoked_on != null) { - _ = try self.buildPush(subject.?); - } else { - _ = try self.buildPush(m.MIR_new_uint_op(self.ctx, v.Value.Void.val)); - } + const previous_out_label = self.state.?.break_label; + self.state.?.break_label = out_label; + const previous_continue_label = self.state.?.continue_label; + self.state.?.continue_label = loop_label; - const args: std.AutoArrayHashMap(*o.ObjString, *o.ObjTypeDef) = function_type_def.resolved_type.?.Function.parameters; - const defaults = function_type_def.resolved_type.?.Function.defaults; - const arg_keys = args.keys(); + self.append(loop_label); - var arguments = std.AutoArrayHashMap(*o.ObjString, m.MIR_op_t).init(self.vm.gc.allocator); - defer arguments.deinit(); + _ = try self.generateNode(components.body); - // Evaluate arguments - for (call_node.arguments.keys(), 0..) |arg_key, index| { - const argument = call_node.arguments.get(arg_key).?; - const actual_arg_key = if (index == 0 and std.mem.eql(u8, arg_key.string, "$")) arg_keys[0] else arg_key; + self.BEQ( + m.MIR_new_label_op(self.ctx, loop_label), + (try self.generateNode(components.condition)).?, + m.MIR_new_uint_op(self.ctx, Value.False.val), + ); - try arguments.put(actual_arg_key, (try self.generateNode(argument)).?); - } + self.append(out_label); - // Push them in order on the stack with default value if missing argument - for (arg_keys) |key| { - if (arguments.get(key)) |arg| { - try self.buildPush(arg); - } else { - var value = defaults.get(key).?; - value = if (value.isObj()) try o.cloneObject(value.obj(), self.vm) else value; + self.state.?.break_label = previous_out_label; + self.state.?.continue_label = previous_continue_label; - // Push clone of default - const clone = try self.REG("clone", m.MIR_T_I64); - try self.buildExternApiCall( - .bz_clone, - m.MIR_new_reg_op(self.ctx, clone), - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, value.val), - }, - ); + return null; +} - try self.buildPush(m.MIR_new_reg_op(self.ctx, clone)); - } +fn generateFor(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].For; + const condition_value = self.state.?.ast.nodes.items(.value)[components.condition]; + + if (condition_value != null and !condition_value.?.boolean()) { + return null; } - const new_ctx = try self.REG("new_ctx", m.MIR_T_I64); - self.ALLOCA(new_ctx, @sizeOf(o.NativeCtx)); - try self.buildExternApiCall( - .bz_context, - callee, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), - callee, - m.MIR_new_reg_op(self.ctx, new_ctx), - m.MIR_new_uint_op(self.ctx, arg_keys.len), - }, - ); + const cond_label = m.MIR_new_label(self.ctx); + const out_label = m.MIR_new_label(self.ctx); + const previous_out_label = self.state.?.break_label; + self.state.?.break_label = out_label; + const previous_continue_label = self.state.?.continue_label; + self.state.?.continue_label = cond_label; - // Regular function, just call it - const result = try self.REG("result", m.MIR_T_I64); - self.append( - m.MIR_new_insn_arr( - self.ctx, - m.MIR_CALL, - 4, - &[_]m.MIR_op_t{ - m.MIR_new_ref_op( - self.ctx, - if (function_type == .Extern) - try ExternApi.nativefn.declare(self) - else - try ExternApi.rawfn.declare(self), - ), - callee, - m.MIR_new_reg_op(self.ctx, result), - m.MIR_new_reg_op(self.ctx, new_ctx), - }, - ), + // Init expressions + for (components.init_declarations) |expr| { + _ = try self.generateNode(expr); + } + + // Condition + self.append(cond_label); + + self.BEQ( + m.MIR_new_label_op(self.ctx, out_label), + (try self.generateNode(components.condition)).?, + m.MIR_new_uint_op(self.ctx, Value.False.val), ); - if (post_call_label) |label| { - self.append(label); + _ = try self.generateNode(components.body); - self.MOV( - m.MIR_new_reg_op(self.ctx, result), - m.MIR_new_reg_op(self.ctx, return_alloca.?), - ); + // Post loop + for (components.post_loop) |expr| { + _ = try self.generateNode(expr); } - if (function_type == .Extern) { - return try self.generateHandleExternReturn( - function_type_def.resolved_type.?.Function.error_types != null, - function_type_def.resolved_type.?.Function.return_type.def_type != .Void, - m.MIR_new_reg_op(self.ctx, result), - function_type_def.resolved_type.?.Function.parameters.count(), - catch_value, - ); - } + self.JMP(cond_label); - return m.MIR_new_reg_op(self.ctx, result); -} + self.append(out_label); -// Handle Extern call like VM.callNative does -fn generateHandleExternReturn( - self: *Self, - can_fail: bool, - should_return: bool, - return_code: m.MIR_op_t, - arg_count: usize, - catch_value: ?m.MIR_op_t, -) !m.MIR_op_t { - if (can_fail) { - const continue_label = m.MIR_new_label(self.ctx); + self.state.?.break_label = previous_out_label; + self.state.?.continue_label = previous_continue_label; - self.BNE( - continue_label, - return_code, - m.MIR_new_int_op(self.ctx, -1), - ); + return null; +} - if (catch_value) |value| { - // Pop error - const discard = try self.REG("discard", m.MIR_T_I64); - try self.buildPop(m.MIR_new_reg_op(self.ctx, discard)); +fn generateBreak(self: *Self, break_node: Ast.Node.Index) Error!?m.MIR_op_t { + try self.closeScope(break_node); - // Push catch value - try self.buildPush(value); - } else { - try self.buildExternApiCall( - .bz_rethrow, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - }, - ); + self.JMP(self.state.?.break_label.?); - try self.buildExternApiCall( - .exit, - null, - &[_]m.MIR_op_t{m.MIR_new_uint_op(self.ctx, 1)}, - ); - } + return null; +} - self.append(continue_label); - } +fn generateContinue(self: *Self, continue_node: Ast.Node.Index) Error!?m.MIR_op_t { + try self.closeScope(continue_node); - const result = try self.REG("result", m.MIR_T_I64); - if (should_return) { - try self.buildPop(m.MIR_new_reg_op(self.ctx, result)); - } else { - self.MOV( - m.MIR_new_reg_op(self.ctx, result), - m.MIR_new_uint_op(self.ctx, v.Value.Void.val), - ); - } + self.JMP(self.state.?.continue_label.?); - const ctx_reg = self.state.?.ctx_reg.?; - const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); - const stack_top_base = try self.REG("stack_top_base", m.MIR_T_I64); - const index = try self.REG("index", m.MIR_T_I64); + return null; +} - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); +fn generateList(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].List; + const type_def = self.state.?.ast.nodes.items(.type_def)[node]; - // *[*]Value - const stack_top_ptr = m.MIR_new_mem_op( + const new_list = m.MIR_new_reg_op( self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "stack_top"), - ctx_reg, - index, - 1, - ); - - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), - stack_top_ptr, + try self.REG("new_list", m.MIR_T_I64), ); - // [*]Value - const stack_top = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - 0, - stack_top_ptr_base, - index, - 1, + try self.buildExternApiCall( + .bz_newList, + new_list, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_uint_op(self.ctx, type_def.?.resolved_type.?.List.item_type.toValue().val), + }, ); - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_base), - stack_top, - ); + // Prevent collection + try self.buildPush(new_list); - // Reset stack - self.SUB( - stack_top, - m.MIR_new_reg_op(self.ctx, stack_top_base), - m.MIR_new_uint_op(self.ctx, (arg_count + 1) * @sizeOf(u64)), - ); + for (components.items) |item| { + try self.buildExternApiCall( + .bz_listAppend, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + new_list, + (try self.generateNode(item)).?, + }, + ); + } - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_base), - stack_top, - ); + try self.buildPop(null); - return m.MIR_new_reg_op(self.ctx, result); + return new_list; } -fn buildReturn(self: *Self, value: m.MIR_op_t) !void { - const ctx_reg = self.state.?.ctx_reg.?; - const index = try self.REG("index", m.MIR_T_I64); - - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); +fn generateRange(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Range; + const type_def = self.state.?.ast.nodes.items(.type_def)[node]; - // Get base - // [*]Value - const base = m.MIR_new_mem_op( + const new_list = m.MIR_new_reg_op( self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "base"), - ctx_reg, - index, - 1, + try self.REG("new_list", m.MIR_T_I64), ); try self.buildExternApiCall( - .bz_closeUpValues, - null, + .bz_newList, + new_list, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - base, + m.MIR_new_uint_op(self.ctx, type_def.?.resolved_type.?.List.item_type.toValue().val), }, ); - // *[*]Value - const stack_top_ptr = m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "stack_top"), - ctx_reg, - index, - 1, - ); - - // Reset stack_top to base - self.MOV(try self.LOAD(stack_top_ptr), base); + // Prevent collection + try self.buildPush(new_list); - // Do return - self.RET(value); -} + const low = (try self.generateNode(components.low)).?; + const high = (try self.generateNode(components.high)).?; -fn generateReturn(self: *Self, return_node: *n.ReturnNode) Error!?m.MIR_op_t { - if (return_node.unconditional) { - self.state.?.return_emitted = true; - } - - try self.buildReturn( - if (return_node.value) |value| - (try self.generateNode(value)).? - else - m.MIR_new_uint_op(self.ctx, v.Value.Void.val), + const current = m.MIR_new_reg_op( + self.ctx, + try self.REG("current", m.MIR_T_I64), ); - - return null; -} - -fn generateIf(self: *Self, if_node: *n.IfNode) Error!?m.MIR_op_t { - const constant_condition = if (if_node.condition.isConstant(if_node.condition) and if_node.unwrapped_identifier == null and if_node.casted_type == null) - if_node.condition.toValue(if_node.condition, self.vm.gc) catch unreachable - else - null; - - // Generate condition - const condition_value = if (constant_condition == null) - (try self.generateNode(if_node.condition)).? - else - null; - const condition = m.MIR_new_reg_op( + self.MOV(current, low); + const reached_limit = m.MIR_new_reg_op( self.ctx, - try self.REG("condition", m.MIR_T_I64), + try self.REG("reached_limit", m.MIR_T_I64), ); + const unwrapped_low = m.MIR_new_reg_op( + self.ctx, + try self.REG("unwrapped_low", m.MIR_T_I64), + ); + try self.unwrap(.Integer, low, unwrapped_low); - const resolved = if (!if_node.is_statement) - try self.REG("resolved", m.MIR_T_I64) - else - null; - - // Is it `if (opt -> unwrapped)`? - if (if_node.unwrapped_identifier != null) { - try self.buildExternApiCall( - .bz_valueEqual, - condition, - &[_]m.MIR_op_t{ - condition_value.?, - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), - }, - ); - - // TODO: replace with condition ^ (MIR_OR, MIR_XOR?) 1 - const true_label = m.MIR_new_label(self.ctx); - const out_label = m.MIR_new_label(self.ctx); - - self.BEQ( - m.MIR_new_label_op(self.ctx, true_label), - condition, - m.MIR_new_uint_op(self.ctx, v.Value.True.val), - ); - - self.MOV( - condition, - m.MIR_new_uint_op(self.ctx, 1), - ); + const unwrapped_high = m.MIR_new_reg_op( + self.ctx, + try self.REG("unwrapped_high", m.MIR_T_I64), + ); + try self.unwrap(.Integer, high, unwrapped_high); - self.JMP(out_label); + // Select increment + const is_negative_delta = m.MIR_new_reg_op( + self.ctx, + try self.REG("is_negative_delta", m.MIR_T_I64), + ); + self.GES(is_negative_delta, unwrapped_low, unwrapped_high); - self.append(true_label); + const neg_loop_label = m.MIR_new_label(self.ctx); + self.BEQ( + m.MIR_new_label_op(self.ctx, neg_loop_label), + is_negative_delta, + m.MIR_new_uint_op(self.ctx, 1), + ); - self.MOV( - condition, - m.MIR_new_uint_op(self.ctx, 0), - ); + const exit_label = m.MIR_new_label(self.ctx); - self.append(out_label); - } else if (if_node.casted_type) |casted_type| { - try self.buildExternApiCall( - .bz_valueIs, - condition, - &[_]m.MIR_op_t{ - condition_value.?, - m.MIR_new_uint_op(self.ctx, casted_type.toValue().val), - }, - ); + const pos_loop_label = m.MIR_new_label(self.ctx); + self.append(pos_loop_label); - try self.unwrap( - .Bool, - condition, - condition, - ); - } else if (constant_condition == null) { - try self.unwrap( - .Bool, - condition_value.?, - condition, - ); - } + self.GES(reached_limit, unwrapped_low, unwrapped_high); + self.BEQ( + m.MIR_new_label_op(self.ctx, exit_label), + reached_limit, + m.MIR_new_uint_op(self.ctx, 1), + ); - const out_label = m.MIR_new_label(self.ctx); - const then_label = m.MIR_new_label(self.ctx); - const else_label = if (if_node.else_branch != null) - m.MIR_new_label(self.ctx) - else - null; + // Add new element + try self.buildExternApiCall( + .bz_listAppend, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + new_list, + current, + }, + ); - if (constant_condition != null) { - self.JMP( - if (constant_condition.?.boolean()) - then_label - else if (if_node.else_branch != null) - else_label.? - else - out_label, - ); - } else { - self.BEQ( - m.MIR_new_label_op(self.ctx, then_label), - condition, - m.MIR_new_uint_op(self.ctx, 1), - ); + // Increment + self.ADDS(unwrapped_low, unwrapped_low, m.MIR_new_uint_op(self.ctx, 1)); + self.wrap(.Integer, unwrapped_low, current); - self.JMP( - if (if_node.else_branch != null) - else_label.? - else - out_label, - ); - } + self.JMP(pos_loop_label); - if (constant_condition == null or constant_condition.?.boolean()) { - self.append(then_label); + self.append(neg_loop_label); - // Push unwrapped value as local of the then block - if (if_node.unwrapped_identifier != null or if_node.casted_type != null) { - try self.buildPush( - if (constant_condition) |constant| - m.MIR_new_uint_op(self.ctx, constant.val) - else - condition_value.?, - ); - } + self.LES(reached_limit, unwrapped_low, unwrapped_high); + self.BEQ( + m.MIR_new_label_op(self.ctx, exit_label), + reached_limit, + m.MIR_new_uint_op(self.ctx, 1), + ); - if (if_node.is_statement) { - _ = try self.generateNode(if_node.body); - } else { - self.MOV( - m.MIR_new_reg_op(self.ctx, resolved.?), - (try self.generateNode(if_node.body)).?, - ); - } + // Add new element + try self.buildExternApiCall( + .bz_listAppend, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + new_list, + current, + }, + ); - self.JMP(out_label); - } + // Increment + self.SUBS(unwrapped_low, unwrapped_low, m.MIR_new_uint_op(self.ctx, 1)); + self.wrap(.Integer, unwrapped_low, current); - if (constant_condition == null or !constant_condition.?.boolean()) { - if (if_node.else_branch) |else_branch| { - self.append(else_label); + self.JMP(neg_loop_label); - if (if_node.is_statement) { - _ = try self.generateNode(else_branch); - } else { - self.MOV( - m.MIR_new_reg_op(self.ctx, resolved.?), - (try self.generateNode(else_branch)).?, - ); - } - } - } + self.append(exit_label); - self.append(out_label); + try self.buildPop(null); // Pop list - return if (if_node.is_statement) - null - else - m.MIR_new_reg_op(self.ctx, resolved.?); + return new_list; } -fn generateTypeExpression(self: *Self, type_expression_node: *n.TypeExpressionNode) Error!?m.MIR_op_t { - return m.MIR_new_uint_op( - self.ctx, - type_expression_node.value.val, - ); -} +fn generateMap(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Map; + const type_def = self.state.?.ast.nodes.items(.type_def)[node]; -fn generateTypeOfExpression(self: *Self, typeof_expression_node: *n.TypeOfExpressionNode) Error!?m.MIR_op_t { - const value = (try self.generateNode(typeof_expression_node.expression)).?; - const result = m.MIR_new_reg_op( + const new_map = m.MIR_new_reg_op( self.ctx, - try self.REG("typeof", m.MIR_T_I64), + try self.REG("new_map", m.MIR_T_I64), ); try self.buildExternApiCall( - .bz_valueTypeOf, - result, + .bz_newMap, + new_map, &[_]m.MIR_op_t{ - value, m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_uint_op(self.ctx, @constCast(type_def.?).toValue().val), }, ); - return result; -} + // Prevent collection + try self.buildPush(new_map); -fn generateBinary(self: *Self, binary_node: *n.BinaryNode) Error!?m.MIR_op_t { - const left_type_def = binary_node.left.type_def.?.def_type; - const right_type_def = binary_node.right.type_def.?.def_type; + for (components.entries) |entry| { + try self.buildExternApiCall( + .bz_mapSet, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + new_map, + (try self.generateNode(entry.key)).?, + (try self.generateNode(entry.value)).?, + }, + ); + } - return switch (binary_node.operator) { - .Ampersand, - .Bor, - .Xor, - .ShiftLeft, - .ShiftRight, - => try self.generateBitwise(binary_node), - .QuestionQuestion, - .And, - .Or, - => try self.genereateConditional(binary_node), - else => { - const left_value = (try self.generateNode(binary_node.left)).?; - const right_value = (try self.generateNode(binary_node.right)).?; + try self.buildPop(null); - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - var left = m.MIR_new_reg_op( - self.ctx, - try self.REG("left", if (left_type_def == .Float) m.MIR_T_D else m.MIR_T_I64), - ); - var right = m.MIR_new_reg_op( - self.ctx, - try self.REG("right", if (right_type_def == .Float) m.MIR_T_D else m.MIR_T_I64), - ); + return new_map; +} - if (left_type_def == .Integer) { - try self.unwrap(.Integer, left_value, left); - } else if (left_type_def == .Float) { - try self.unwrap(.Float, left_value, left); - } else { - self.MOV(left, left_value); - } +fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Dot; + const type_defs = self.state.?.ast.nodes.items(.type_def); - if (right_type_def == .Integer) { - try self.unwrap(.Integer, right_value, right); - } else if (right_type_def == .Float) { - try self.unwrap(.Float, right_value, right); - } else { - self.MOV(right, right_value); - } + const callee_type = type_defs[components.callee].?; + const member_lexeme = self.state.?.ast.tokens.items(.lexeme)[components.identifier]; + const member_identifier = (try self.vm.gc.copyString(member_lexeme)).toValue().val; - // Avoid collection - if (left_type_def != .Integer and left_type_def != .Float) { - try self.buildPush(left_value); - } + switch (callee_type.def_type) { + .Fiber => { + switch (components.member_kind) { + .Call => return try self.generateCall(components.value_or_call_or_enum.Call), + else => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_getFiberField, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op( + self.ctx, + member_identifier, + ), + m.MIR_new_uint_op(self.ctx, 1), + }, + ); - if (right_type_def != .Integer and right_type_def != .Float) { - try self.buildPush(right_value); + return res; + }, } + }, - switch (binary_node.operator) { - .EqualEqual => try self.buildExternApiCall( - .bz_valueEqual, - res, - &[_]m.MIR_op_t{ - left_value, - right_value, - }, - ), - .BangEqual => { + .Pattern => { + switch (components.member_kind) { + .Call => return try self.generateCall(components.value_or_call_or_enum.Call), + else => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); try self.buildExternApiCall( - .bz_valueEqual, + .bz_getPatternField, res, &[_]m.MIR_op_t{ - left_value, - right_value, + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, 1), }, ); - try self.unwrap(.Bool, res, res); - - const true_label = m.MIR_new_label(self.ctx); - const out_label = m.MIR_new_label(self.ctx); + return res; + }, + } + }, - self.BEQ( - m.MIR_new_label_op(self.ctx, true_label), - res, - m.MIR_new_uint_op(self.ctx, 1), + .String => { + switch (components.member_kind) { + .Call => return try self.generateCall(components.value_or_call_or_enum.Call), + else => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), ); - - self.MOV( + try self.buildExternApiCall( + .bz_getStringField, res, - m.MIR_new_uint_op(self.ctx, v.Value.True.val), + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, 1), + }, ); - self.JMP(out_label); + return res; + }, + } + }, - self.append(true_label); + .Object => { + switch (components.member_kind) { + .Call => return try self.generateCall(components.value_or_call_or_enum.Call), + .Value => { + const gen_value = (try self.generateNode(components.value_or_call_or_enum.Value)).?; - self.MOV( + try self.buildExternApiCall( + .bz_setObjectField, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + gen_value, + }, + ); + + return gen_value; + }, + else => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_getObjectField, res, - m.MIR_new_uint_op(self.ctx, v.Value.False.val), + &[_]m.MIR_op_t{ + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + }, ); - self.append(out_label); + return res; }, - .Greater, .Less, .GreaterEqual, .LessEqual => { - if (left_type_def == .Float or right_type_def == .Float) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } + } + }, - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } + .ObjectInstance, .ProtocolInstance => { + switch (components.member_kind) { + .Call => return try self.generateCall(components.value_or_call_or_enum.Call), + .Value => { + const gen_value = (try self.generateNode(components.value_or_call_or_enum.Value)).?; - switch (binary_node.operator) { - .Greater => self.DGT(res, left, right), - .Less => self.DLT(res, left, right), - .GreaterEqual => self.DGE(res, left, right), - .LessEqual => self.DLE(res, left, right), - else => unreachable, - } + try self.buildExternApiCall( + .bz_setInstanceField, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + gen_value, + }, + ); - self.wrap(.Bool, res, res); - } else { - switch (binary_node.operator) { - .Greater => self.GTS(res, left, right), - .Less => self.LTS(res, left, right), - .GreaterEqual => self.GES(res, left, right), - .LessEqual => self.LES(res, left, right), - else => unreachable, - } + return gen_value; + }, + else => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_getInstanceField, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, 1), + }, + ); - self.wrap(.Bool, res, res); - } + return res; }, - .Plus => { - switch (binary_node.left.type_def.?.def_type) { - .Integer, .Float => { - if (left_type_def == .Float or right_type_def == .Float) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } + } + }, - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } + .ForeignContainer => { + switch (components.member_kind) { + .Call => return try self.generateCall(components.value_or_call_or_enum.Call), + .Value => { + const gen_value = (try self.generateNode(components.value_or_call_or_enum.Value)).?; - const f_res = m.MIR_new_reg_op( - self.ctx, - try self.REG("f_res", m.MIR_T_D), - ); - self.DADD(f_res, left, right); + try self.buildExternApiCall( + .bz_containerSet, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, @as(u64, @intFromPtr(member_lexeme.ptr))), + m.MIR_new_uint_op(self.ctx, member_lexeme.len), + gen_value, + }, + ); - self.wrap(.Float, f_res, res); - } else { - self.ADDS(res, left, right); + return gen_value; + }, + else => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); - self.wrap(.Integer, res, res); - } - }, - .String => { - try self.buildExternApiCall( - .bz_objStringConcat, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - left, - right, - }, - ); - }, - .List => { - try self.buildExternApiCall( - .bz_listConcat, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - left, - right, - }, - ); - }, - .Map => { - try self.buildExternApiCall( - .bz_mapConcat, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - left, - right, - }, - ); + try self.buildExternApiCall( + .bz_containerGet, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, @as(u64, @intFromPtr(member_lexeme.ptr))), + m.MIR_new_uint_op(self.ctx, member_lexeme.len), }, - else => unreachable, - } - }, - .Minus => { - if (left_type_def == .Float or right_type_def == .Float) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } + ); - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } + return res; + }, + } + }, - const f_res = m.MIR_new_reg_op( - self.ctx, - try self.REG("f_res", m.MIR_T_D), - ); - self.DSUB(f_res, left, right); + .Enum => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_getEnumCase, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + }, + ); - self.wrap(.Float, f_res, res); - } else { - self.SUBS(res, left, right); + return res; + }, - self.wrap(.Integer, res, res); - } + .EnumInstance => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_getEnumCaseValue, + res, + &[_]m.MIR_op_t{ + (try self.generateNode(components.callee)).?, }, - .Star => { - if (left_type_def == .Float or right_type_def == .Float) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } + ); - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } + return res; + }, - const f_res = m.MIR_new_reg_op( - self.ctx, - try self.REG("f_res", m.MIR_T_D), - ); - self.DMUL(f_res, left, right); + .List => { + switch (components.member_kind) { + .Call => return try self.generateCall(components.value_or_call_or_enum.Call), + else => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_getListField, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, 1), + }, + ); - self.wrap(.Float, f_res, res); - } else { - self.MULS(res, left, right); + return res; + }, + } + }, - self.wrap(.Integer, res, res); - } + .Map => { + switch (components.member_kind) { + .Call => return try self.generateCall(components.value_or_call_or_enum.Call), + else => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_getMapField, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, 1), + }, + ); + + return res; }, - .Slash => { - if (left_type_def == .Float or right_type_def == .Float) { - if (left_type_def == .Integer) { - const left_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("left_float", m.MIR_T_D), - ); - self.I2D(left_f, left); - left = left_f; - } + } + }, - if (right_type_def == .Integer) { - const right_f = m.MIR_new_reg_op( - self.ctx, - try self.REG("right_float", m.MIR_T_D), - ); - self.I2D(right_f, right); - right = right_f; - } + else => unreachable, + } +} - const f_res = m.MIR_new_reg_op( - self.ctx, - try self.REG("f_res", m.MIR_T_D), - ); - self.DDIV(f_res, left, right); +fn generateSubscript(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Subscript; + const type_defs = self.state.?.ast.nodes.items(.type_def); - self.wrap(.Float, f_res, res); - } else { - self.DIVS(res, left, right); + const subscripted = (try self.generateNode(components.subscripted)).?; + const index_val = (try self.generateNode(components.index)).?; + const value = if (components.value) |val| (try self.generateNode(val)).? else null; - self.wrap(.Integer, res, res); - } + switch (type_defs[components.subscripted].?.def_type) { + .List => { + const index = m.MIR_new_reg_op( + self.ctx, + try self.REG("index", m.MIR_T_I64), + ); + + try self.unwrap(.Integer, index_val, index); + + if (value) |val| { + try self.buildExternApiCall( + .bz_listSet, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + subscripted, + index, + val, + }, + ); + + return subscripted; + } + + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + + try self.buildExternApiCall( + .bz_listGet, + res, + &[_]m.MIR_op_t{ + subscripted, + index, }, - .Percent => { - if (left_type_def == .Float or right_type_def == .Float) { - // FIXME: mir doesn't seem to have a mod/rem for floats? - unreachable; - } else { - self.MODS(res, left, right); + ); - self.wrap(.Integer, res, res); - } + return res; + }, + .String => { + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + + try self.buildExternApiCall( + .bz_objStringSubscript, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + subscripted, + index_val, }, - else => unreachable, - } + ); - if (left_type_def != .Integer and left_type_def != .Float) { - try self.buildPop(null); - } + return res; + }, + .Map => { + if (value) |val| { + try self.buildExternApiCall( + .bz_mapSet, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + subscripted, + index_val, + val, + }, + ); - if (right_type_def != .Integer and right_type_def != .Float) { - try self.buildPop(null); + return subscripted; } + const res = m.MIR_new_reg_op( + self.ctx, + try self.REG("res", m.MIR_T_I64), + ); + + try self.buildExternApiCall( + .bz_mapGet, + res, + &[_]m.MIR_op_t{ + subscripted, + index_val, + }, + ); + return res; }, - }; + else => unreachable, + } } -fn genereateConditional(self: *Self, binary_node: *n.BinaryNode) Error!?m.MIR_op_t { - const value = m.MIR_new_reg_op( +fn generateIs(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Is; + + const res = m.MIR_new_reg_op( self.ctx, - try self.REG("value", m.MIR_T_I64), + try self.REG("res", m.MIR_T_I64), ); - self.MOV( - value, - (try self.generateNode(binary_node.left)).?, + try self.buildExternApiCall( + .bz_valueIs, + res, + &[_]m.MIR_op_t{ + (try self.generateNode(components.left)).?, + m.MIR_new_uint_op( + self.ctx, + self.state.?.ast.nodes.items(.value)[components.constant].?.val, + ), + }, ); - const out_label = m.MIR_new_label(self.ctx); + return res; +} - self.BNE( - out_label, - value, - m.MIR_new_uint_op( - self.ctx, - switch (binary_node.operator) { - .QuestionQuestion => v.Value.Null.val, - .And => v.Value.True.val, - .Or => v.Value.False.val, - else => unreachable, - }, - ), - ); +fn generateAs(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].As; + const left = m.MIR_new_reg_op( + self.ctx, + try self.REG("left", m.MIR_T_I64), + ); self.MOV( - value, - (try self.generateNode(binary_node.right)).?, + left, + (try self.generateNode(components.left)).?, ); - self.append(out_label); - - return value; -} - -fn generateBitwise(self: *Self, binary_node: *n.BinaryNode) Error!?m.MIR_op_t { const res = m.MIR_new_reg_op( self.ctx, try self.REG("res", m.MIR_T_I64), ); - const left = m.MIR_new_reg_op( - self.ctx, - try self.REG("left", m.MIR_T_I64), + + try self.buildExternApiCall( + .bz_valueIs, + res, + &[_]m.MIR_op_t{ + left, + m.MIR_new_uint_op( + self.ctx, + self.state.?.ast.nodes.items(.value)[components.constant].?.val, + ), + }, ); - const right = m.MIR_new_reg_op( - self.ctx, - try self.REG("right", m.MIR_T_I64), + + const casted_label = m.MIR_new_label(self.ctx); + + self.BEQ( + m.MIR_new_label_op(self.ctx, casted_label), + res, + m.MIR_new_uint_op(self.ctx, Value.True.val), ); - try self.unwrap( - .Integer, - (try self.generateNode(binary_node.left)).?, + self.MOV( left, + m.MIR_new_uint_op(self.ctx, Value.Null.val), ); - try self.unwrap( - .Integer, - (try self.generateNode(binary_node.right)).?, - right, - ); - - switch (binary_node.operator) { - .Ampersand => self.AND(res, left, right), - .Bor => self.OR(res, left, right), - .Xor => self.XOR(res, left, right), - .ShiftLeft => self.SHL(res, left, right), - .ShiftRight => self.SHR(res, left, right), - else => unreachable, - } - self.wrap(.Integer, res, res); + self.append(casted_label); - return res; + return left; } -fn generateWhile(self: *Self, while_node: *n.WhileNode) Error!?m.MIR_op_t { - if (while_node.condition.isConstant(while_node.condition) and !(while_node.condition.toValue(while_node.condition, self.vm.gc) catch @panic("Could not fold while loop")).boolean()) { - return null; - } +fn generateTry(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Try; + const type_defs = self.state.?.ast.nodes.items(.type_def); - const cond_label = m.MIR_new_label(self.ctx); + const raise_label = m.MIR_new_label(self.ctx); const out_label = m.MIR_new_label(self.ctx); + const catch_label = m.MIR_new_label(self.ctx); + var clause_labels = std.ArrayList(m.MIR_insn_t).init(self.vm.gc.allocator); + defer clause_labels.deinit(); - const previous_out_label = self.state.?.break_label; - self.state.?.break_label = out_label; - const previous_continue_label = self.state.?.continue_label; - self.state.?.continue_label = cond_label; - - self.append(cond_label); + for (components.clauses) |_| { + try clause_labels.append( + m.MIR_new_label(self.ctx), + ); + } - self.BEQ( - m.MIR_new_label_op(self.ctx, out_label), - (try self.generateNode(while_node.condition)).?, - m.MIR_new_uint_op(self.ctx, v.Value.False.val), - ); + const unconditional_label = if (components.unconditional_clause != null) + m.MIR_new_label(self.ctx) + else + null; - _ = try self.generateNode(while_node.block); + const index = try self.REG("index", m.MIR_T_I64); + const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); - self.JMP(cond_label); + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), + ); - self.append(out_label); + // *[*]Value + const stack_top_ptr = m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + @offsetOf(o.NativeCtx, "stack_top"), + self.state.?.ctx_reg.?, + index, + 1, + ); - self.state.?.break_label = previous_out_label; - self.state.?.continue_label = previous_continue_label; + self.MOV( + m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), + stack_top_ptr, + ); - return null; -} + // [*]Value + const stack_top = m.MIR_new_reg_op( + self.ctx, + try self.REG("stack_top", m.MIR_T_I64), + ); -fn generateDoUntil(self: *Self, do_until_node: *n.DoUntilNode) Error!?m.MIR_op_t { - const out_label = m.MIR_new_label(self.ctx); - const loop_label = m.MIR_new_label(self.ctx); + self.MOV( + stack_top, + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + 0, + stack_top_ptr_base, + index, + 1, + ), + ); - const previous_out_label = self.state.?.break_label; - self.state.?.break_label = out_label; - const previous_continue_label = self.state.?.continue_label; - self.state.?.continue_label = loop_label; + // Set it as current jump env + const try_ctx = m.MIR_new_reg_op(self.ctx, try self.REG("try_ctx", m.MIR_T_I64)); + try self.buildExternApiCall( + .bz_setTryCtx, + try_ctx, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, + ); - self.append(loop_label); + const env = m.MIR_new_reg_op( + self.ctx, + try self.REG("env", m.MIR_T_I64), + ); + self.ADD( + env, + try_ctx, + m.MIR_new_uint_op(self.ctx, @offsetOf(r.TryCtx, "env")), + ); - _ = try self.generateNode(do_until_node.block); + const status = try self.REG("status", m.MIR_T_I64); + try self.buildExternApiCall( + .setjmp, + m.MIR_new_reg_op(self.ctx, status), + &[_]m.MIR_op_t{env}, + ); self.BEQ( - m.MIR_new_label_op(self.ctx, loop_label), - (try self.generateNode(do_until_node.condition)).?, - m.MIR_new_uint_op(self.ctx, v.Value.False.val), + m.MIR_new_label_op(self.ctx, catch_label), + m.MIR_new_reg_op(self.ctx, status), + m.MIR_new_int_op(self.ctx, 1), ); - self.append(out_label); - - self.state.?.break_label = previous_out_label; - self.state.?.continue_label = previous_continue_label; - - return null; -} - -fn generateFor(self: *Self, for_node: *n.ForNode) Error!?m.MIR_op_t { - if (for_node.condition.isConstant(for_node.condition) and !(for_node.condition.toValue(for_node.condition, self.vm.gc) catch @panic("Could not fold for loop")).boolean()) { - return null; - } - - const cond_label = m.MIR_new_label(self.ctx); - const out_label = m.MIR_new_label(self.ctx); - const previous_out_label = self.state.?.break_label; - self.state.?.break_label = out_label; - const previous_continue_label = self.state.?.continue_label; - self.state.?.continue_label = cond_label; + _ = try self.generateNode(components.body); - // Init expressions - for (for_node.init_declarations.items) |expr| { - _ = try self.generateNode(&expr.node); - } + self.JMP(out_label); - // Condition - self.append(cond_label); + self.append(catch_label); - self.BEQ( - m.MIR_new_label_op(self.ctx, out_label), - (try self.generateNode(for_node.condition)).?, - m.MIR_new_uint_op(self.ctx, v.Value.False.val), + const payload = m.MIR_new_reg_op( + self.ctx, + try self.REG("payload", m.MIR_T_I64), ); + try self.buildPop(payload); - _ = try self.generateNode(for_node.body); - - // Post loop - for (for_node.post_loop.items) |expr| { - _ = try self.generateNode(expr); - } + // Get stack top as it was before try block + // Close upvalues up to it + try self.buildExternApiCall( + .bz_closeUpValues, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + stack_top, + }, + ); - self.JMP(cond_label); + // Restore stack top as it was before the try block + self.MOV( + try self.LOAD(stack_top_ptr), + stack_top, + ); - self.append(out_label); + // Put error back on stack + try self.buildPush(payload); - self.state.?.break_label = previous_out_label; - self.state.?.continue_label = previous_continue_label; + self.JMP(if (clause_labels.items.len > 0) + clause_labels.items[0] + else + unconditional_label.?); - return null; -} + for (components.clauses, 0..) |clause, idx| { + const label = clause_labels.items[idx]; -fn generateBreak(self: *Self, break_node: *n.ParseNode) Error!?m.MIR_op_t { - try self.closeScope(break_node); + self.append(label); - self.JMP(self.state.?.break_label.?); + // Get error payload from stack + const err_payload = try self.REG("err_paylaod", m.MIR_T_I64); + try self.buildPeek( + 0, + m.MIR_new_reg_op(self.ctx, err_payload), + ); - return null; -} + const matches = try self.REG("matches", m.MIR_T_I64); + try self.buildExternApiCall( + .bz_valueIs, + m.MIR_new_reg_op(self.ctx, matches), + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, err_payload), + m.MIR_new_uint_op(self.ctx, @constCast(type_defs[clause.type_def].?).toValue().val), + }, + ); -fn generateContinue(self: *Self, continue_node: *n.ParseNode) Error!?m.MIR_op_t { - try self.closeScope(continue_node); + try self.unwrap( + .Bool, + m.MIR_new_reg_op(self.ctx, matches), + m.MIR_new_reg_op(self.ctx, matches), + ); - self.JMP(self.state.?.continue_label.?); + self.BEQ( + m.MIR_new_label_op( + self.ctx, + if (idx < components.clauses.len - 1) + clause_labels.items[idx + 1] + else if (unconditional_label) |unconditional| + unconditional + else + raise_label, + ), + m.MIR_new_reg_op(self.ctx, matches), + m.MIR_new_uint_op(self.ctx, 0), + ); - return null; -} + // Unwind TryCtx + try self.buildExternApiCall( + .bz_popTryCtx, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, + ); -fn generateList(self: *Self, list_node: *n.ListNode) Error!?m.MIR_op_t { - const new_list = m.MIR_new_reg_op( - self.ctx, - try self.REG("new_list", m.MIR_T_I64), - ); + _ = try self.generateNode(clause.body); - try self.buildExternApiCall( - .bz_newList, - new_list, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, list_node.node.type_def.?.resolved_type.?.List.item_type.toValue().val), - }, - ); + self.JMP(out_label); + } - // Prevent collection - try self.buildPush(new_list); + if (unconditional_label) |label| { + self.append(label); - for (list_node.items) |item| { + // Unwind TryCtx try self.buildExternApiCall( - .bz_listAppend, + .bz_popTryCtx, null, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - new_list, - (try self.generateNode(item)).?, }, ); - } - try self.buildPop(null); + _ = try self.generateNode(components.unconditional_clause.?); - return new_list; -} + self.JMP(out_label); + } -fn generateRange(self: *Self, range: *n.RangeNode) Error!?m.MIR_op_t { - const new_list = m.MIR_new_reg_op( - self.ctx, - try self.REG("new_list", m.MIR_T_I64), - ); + self.append(raise_label); + // Unwind TryCtx try self.buildExternApiCall( - .bz_newList, - new_list, + .bz_popTryCtx, + null, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, range.node.type_def.?.resolved_type.?.List.item_type.toValue().val), }, ); - // Prevent collection - try self.buildPush(new_list); - - const low = (try self.generateNode(range.low)).?; - const high = (try self.generateNode(range.hi)).?; - - const current = m.MIR_new_reg_op( - self.ctx, - try self.REG("current", m.MIR_T_I64), - ); - self.MOV(current, low); - const reached_limit = m.MIR_new_reg_op( - self.ctx, - try self.REG("reached_limit", m.MIR_T_I64), - ); - const unwrapped_low = m.MIR_new_reg_op( - self.ctx, - try self.REG("unwrapped_low", m.MIR_T_I64), - ); - try self.unwrap(.Integer, low, unwrapped_low); - - const unwrapped_high = m.MIR_new_reg_op( - self.ctx, - try self.REG("unwrapped_high", m.MIR_T_I64), + // Raise error again + try self.buildExternApiCall( + .bz_rethrow, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, ); - try self.unwrap(.Integer, high, unwrapped_high); - // Select increment - const is_negative_delta = m.MIR_new_reg_op( - self.ctx, - try self.REG("is_negative_delta", m.MIR_T_I64), - ); - self.GES(is_negative_delta, unwrapped_low, unwrapped_high); + self.append(out_label); - const neg_loop_label = m.MIR_new_label(self.ctx); - self.BEQ( - m.MIR_new_label_op(self.ctx, neg_loop_label), - is_negative_delta, - m.MIR_new_uint_op(self.ctx, 1), + // Unwind TryCtx + try self.buildExternApiCall( + .bz_popTryCtx, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + }, ); - const exit_label = m.MIR_new_label(self.ctx); + return null; +} - const pos_loop_label = m.MIR_new_label(self.ctx); - self.append(pos_loop_label); +fn generateThrow(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Throw; - self.GES(reached_limit, unwrapped_low, unwrapped_high); - self.BEQ( - m.MIR_new_label_op(self.ctx, exit_label), - reached_limit, - m.MIR_new_uint_op(self.ctx, 1), - ); + if (components.unconditional) { + self.state.?.return_emitted = true; + } - // Add new element try self.buildExternApiCall( - .bz_listAppend, + .bz_throw, null, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - new_list, - current, + (try self.generateNode(components.expression)).?, }, ); - // Increment - self.ADDS(unwrapped_low, unwrapped_low, m.MIR_new_uint_op(self.ctx, 1)); - self.wrap(.Integer, unwrapped_low, current); + return null; +} - self.JMP(pos_loop_label); +fn generateUnwrap(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Unwrap; - self.append(neg_loop_label); + const value = (try self.generateNode(components.unwrapped)).?; - self.LES(reached_limit, unwrapped_low, unwrapped_high); - self.BEQ( - m.MIR_new_label_op(self.ctx, exit_label), - reached_limit, - m.MIR_new_uint_op(self.ctx, 1), - ); + // Remember that we need to had a terminator to this block that will jump at the end of the optionals chain + if (self.state.?.opt_jump == null) { + self.state.?.opt_jump = .{ + // Store the value on the stack, that spot will be overwritten with the final value of the optional chain + .alloca = try self.REG("opt", m.MIR_T_I64), + .current_insn = std.ArrayList(m.MIR_insn_t).init(self.vm.gc.allocator), + }; + } - // Add new element - try self.buildExternApiCall( - .bz_listAppend, - null, + const current_insn = m.MIR_new_insn_arr( + self.ctx, + m.MIR_MOV, + 2, &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - new_list, - current, + m.MIR_new_reg_op(self.ctx, self.state.?.opt_jump.?.alloca), + value, }, ); - // Increment - self.SUBS(unwrapped_low, unwrapped_low, m.MIR_new_uint_op(self.ctx, 1)); - self.wrap(.Integer, unwrapped_low, current); + self.append( + current_insn, + ); - self.JMP(neg_loop_label); + try self.state.?.opt_jump.?.current_insn.append(current_insn); - self.append(exit_label); + return value; +} - try self.buildPop(null); // Pop list +fn generateObjectInit(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].ObjectInit; + const type_defs = self.state.?.ast.nodes.items(.type_def); + const type_def = type_defs[node]; + const lexemes = self.state.?.ast.tokens.items(.lexeme); - return new_list; -} + if (type_def.?.def_type == .ForeignContainer) { + return self.generateForeignContainerInit(node); + } -fn generateMap(self: *Self, map_node: *n.MapNode) Error!?m.MIR_op_t { - const new_map = m.MIR_new_reg_op( + const object = if (components.object != null and type_defs[components.object.?].?.def_type == .Object) + (try self.generateNode(components.object.?)).? + else + m.MIR_new_uint_op(self.ctx, Value.Null.val); + + const typedef = m.MIR_new_uint_op( self.ctx, - try self.REG("new_map", m.MIR_T_I64), + @constCast(type_def.?).toValue().val, ); + const instance = m.MIR_new_reg_op( + self.ctx, + try self.REG("instance", m.MIR_T_I64), + ); try self.buildExternApiCall( - .bz_newMap, - new_map, + .bz_instance, + instance, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, map_node.node.type_def.?.toValue().val), + object, + typedef, }, ); - // Prevent collection - try self.buildPush(new_map); + // Push to prevent collection + try self.buildPush(instance); - for (map_node.keys, 0..) |key, index| { + for (components.properties) |property| { try self.buildExternApiCall( - .bz_mapSet, + .bz_setInstanceField, null, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - new_map, - (try self.generateNode(key)).?, - (try self.generateNode(map_node.values[index])).?, + instance, + m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(lexemes[property.name])).toValue().val), + (try self.generateNode(property.value)).?, }, ); } - try self.buildPop(null); + try self.buildPop(instance); - return new_map; + return instance; } -fn generateDot(self: *Self, dot_node: *n.DotNode) Error!?m.MIR_op_t { - const callee_type = dot_node.callee.type_def.?; - - switch (callee_type.def_type) { - .Fiber => { - if (dot_node.call) |call| { - return try self.generateCall(call); - } +fn generateForeignContainerInit(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].ObjectInit; + const type_defs = self.state.?.ast.nodes.items(.type_def); + const type_def = type_defs[node]; + const lexemes = self.state.?.ast.tokens.items(.lexeme); - const res = m.MIR_new_reg_op( + const instance = m.MIR_new_reg_op( + self.ctx, + try self.REG("instance", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .bz_containerInstance, + instance, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_uint_op( self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getFiberField, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - m.MIR_new_uint_op(self.ctx, 1), - }, - ); - - return res; + @constCast(type_def.?).toValue().val, + ), }, + ); - .Pattern => { - if (dot_node.call) |call| { - return try self.generateCall(call); - } - - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getPatternField, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - m.MIR_new_uint_op(self.ctx, 1), - }, - ); + for (components.properties) |property| { + try self.buildExternApiCall( + .bz_containerSet, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + instance, + m.MIR_new_uint_op(self.ctx, @as(u64, @intFromPtr(lexemes[property.name].ptr))), + m.MIR_new_uint_op(self.ctx, lexemes[property.name].len), + (try self.generateNode(property.value)).?, + }, + ); + } - return res; - }, + return instance; +} - .String => { - if (dot_node.call) |call| { - return try self.generateCall(call); - } +fn generateForceUnwrap(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].ForceUnwrap; - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getStringField, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - m.MIR_new_uint_op(self.ctx, 1), - }, - ); + const expr = (try self.generateNode(components.unwrapped)).?; - return res; - }, + const out_label = m.MIR_new_label(self.ctx); - .Object => { - if (dot_node.call) |call| { - return try self.generateCall(call); - } + self.BNE( + out_label, + expr, + m.MIR_new_uint_op(self.ctx, Value.Null.val), + ); - if (dot_node.value) |value| { - const gen_value = (try self.generateNode(value)).?; + try self.buildExternApiCall( + .bz_throw, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString("Force unwrapped optional is null")).toValue().val), + }, + ); - try self.buildExternApiCall( - .bz_setObjectField, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - gen_value, - }, - ); + self.append(out_label); - return gen_value; - } + return expr; +} - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getObjectField, - res, - &[_]m.MIR_op_t{ - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - }, - ); +fn generateUnary(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Unary; + const left_type_def = self.state.?.ast.nodes.items(.type_def)[components.expression]; - return res; - }, + const left = (try self.generateNode(components.expression)).?; + const result = m.MIR_new_reg_op( + self.ctx, + try self.REG("result", m.MIR_T_I64), + ); - .ObjectInstance, .ProtocolInstance => { - if (dot_node.call) |call| { - return try self.generateCall(call); - } - - if (dot_node.value) |value| { - const gen_value = (try self.generateNode(value)).?; - - try self.buildExternApiCall( - .bz_setInstanceField, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - gen_value, - }, - ); + switch (components.operator) { + .Bnot => { + try self.unwrap(.Integer, left, result); + self.NOTS(result, result); + self.wrap(.Integer, result, result); + }, + .Bang => { + try self.unwrap(.Bool, left, result); - return gen_value; - } + const true_label = m.MIR_new_label(self.ctx); + const out_label = m.MIR_new_label(self.ctx); - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getInstanceField, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - m.MIR_new_uint_op(self.ctx, 1), - }, + self.BEQ( + m.MIR_new_label_op(self.ctx, out_label), + result, + m.MIR_new_uint_op(self.ctx, 1), ); - return res; - }, + self.MOV( + result, + m.MIR_new_uint_op(self.ctx, Value.True.val), + ); - .ForeignContainer => { - if (dot_node.value) |value| { - const gen_value = (try self.generateNode(value)).?; + self.JMP(out_label); - try self.buildExternApiCall( - .bz_containerSet, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, @as(u64, @intFromPtr(dot_node.identifier.lexeme.ptr))), - m.MIR_new_uint_op(self.ctx, dot_node.identifier.lexeme.len), - gen_value, - }, - ); + self.append(true_label); - return gen_value; - } else { - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); + self.MOV( + result, + m.MIR_new_uint_op(self.ctx, Value.False.val), + ); - try self.buildExternApiCall( - .bz_containerGet, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, @as(u64, @intFromPtr(dot_node.identifier.lexeme.ptr))), - m.MIR_new_uint_op(self.ctx, dot_node.identifier.lexeme.len), - }, - ); + self.append(out_label); + }, + .Minus => { + try self.unwrap(.Integer, left, result); - return res; + if (left_type_def.?.def_type == .Integer) { + self.NEGS(result, result); + } else { + self.DNEG(result, result); } - }, - .Enum => { - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getEnumCase, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - }, + self.wrap( + left_type_def.?.def_type, + result, + result, ); - - return res; }, + else => unreachable, + } - .EnumInstance => { - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getEnumCaseValue, - res, - &[_]m.MIR_op_t{ - (try self.generateNode(dot_node.callee)).?, - }, - ); + return result; +} - return res; - }, +fn generatePattern(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + return m.MIR_new_uint_op( + self.ctx, + self.state.?.ast.nodes.items(.components)[node].Pattern.toValue().val, + ); +} - .List => { - if (dot_node.call) |call| { - return try self.generateCall(call); - } +fn generateForEach(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].ForEach; + const iterable_type_def = self.state.?.ast.nodes.items(.type_def)[components.iterable]; - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getListField, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - m.MIR_new_uint_op(self.ctx, 1), - }, - ); + // If iterable is empty constant, skip the node + if (self.state.?.ast.nodes.items(.value)[components.iterable]) |iterable| { + if (switch (iterable.obj().obj_type) { + .List => o.ObjList.cast(iterable.obj()).?.items.items.len == 0, + .Map => o.ObjMap.cast(iterable.obj()).?.map.count() == 0, + .String => o.ObjString.cast(iterable.obj()).?.string.len == 0, + .Enum => o.ObjEnum.cast(iterable.obj()).?.cases.len == 0, + else => unreachable, + }) { + return null; + } + } - return res; - }, + // key, value and iterable are locals of the foreach scope + // var declaration so will push value on stack + _ = try self.generateNode(components.key); + // var declaration so will push value on stack + _ = try self.generateNode(components.value); + const iterable = (try self.generateNode(components.iterable)).?; + try self.buildPush(iterable); - .Map => { - if (dot_node.call) |call| { - return try self.generateCall(call); - } + const key_ptr = try self.buildStackPtr(2); + const value_ptr = try self.buildStackPtr(1); - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .bz_getMapField, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(dot_node.callee)).?, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(dot_node.identifier.lexeme)).toValue().val), - m.MIR_new_uint_op(self.ctx, 1), - }, - ); + const cond_label = m.MIR_new_label(self.ctx); + const out_label = m.MIR_new_label(self.ctx); - return res; - }, + const previous_out_label = self.state.?.break_label; + self.state.?.break_label = out_label; + const previous_continue_label = self.state.?.continue_label; + self.state.?.continue_label = cond_label; - else => unreachable, + self.append(cond_label); + + // Call appropriate `next` method + if (iterable_type_def.?.def_type == .Fiber) { + // TODO: fiber foreach (tricky, need to complete foreach op after it has yielded) + unreachable; + } else if (iterable_type_def.?.def_type == .Enum) { + try self.buildExternApiCall( + .bz_enumNext, + try self.LOAD(value_ptr), + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + iterable, + try self.LOAD(value_ptr), + }, + ); + + // If next key is null stop, otherwise do loop + self.BEQ( + m.MIR_new_label_op(self.ctx, out_label), + try self.LOAD(value_ptr), + m.MIR_new_uint_op(self.ctx, Value.Null.val), + ); + } else { + // The `next` method will store the new key in the key local + try self.buildExternApiCall( + switch (iterable_type_def.?.def_type) { + .String => .bz_stringNext, + .List => .bz_listNext, + .Map => .bz_mapNext, + else => unreachable, + }, + try self.LOAD(value_ptr), + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + iterable, + // Pass ptr so the method can put he new key in it + key_ptr, + }, + ); + + // If next key is null stop, otherwise loop + self.BEQ( + m.MIR_new_label_op(self.ctx, out_label), + try self.LOAD(key_ptr), + m.MIR_new_uint_op(self.ctx, Value.Null.val), + ); } + + _ = try self.generateNode(components.body); + + self.JMP(cond_label); + + self.append(out_label); + + self.state.?.break_label = previous_out_label; + self.state.?.continue_label = previous_continue_label; + + return null; } -fn generateSubscript(self: *Self, subscript_node: *n.SubscriptNode) Error!?m.MIR_op_t { - const subscripted = (try self.generateNode(subscript_node.subscripted)).?; - const index_val = (try self.generateNode(subscript_node.index)).?; - const value = if (subscript_node.value) |val| (try self.generateNode(val)).? else null; +fn generateBlock(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + for (self.state.?.ast.nodes.items(.components)[node].Block) |statement| { + _ = try self.generateNode(statement); + } - switch (subscript_node.subscripted.type_def.?.def_type) { - .List => { - const index = m.MIR_new_reg_op( - self.ctx, - try self.REG("index", m.MIR_T_I64), - ); + return null; +} - try self.unwrap(.Integer, index_val, index); +fn generateFunDeclaration(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + return try self.generateFunction( + self.state.?.ast.nodes.items(.components)[node].FunDeclaration.function, + ); +} - if (value) |val| { - try self.buildExternApiCall( - .bz_listSet, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - subscripted, - index, - val, - }, - ); +fn generateVarDeclaration(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].VarDeclaration; - return subscripted; - } + // We should only declare locals + std.debug.assert(components.slot_type == .Local); - const res = m.MIR_new_reg_op( + try self.buildPush( + if (components.value) |value| + (try self.generateNode(value)).? + else + m.MIR_new_uint_op( self.ctx, - try self.REG("res", m.MIR_T_I64), - ); + Value.Null.val, + ), + ); - try self.buildExternApiCall( - .bz_listGet, - res, - &[_]m.MIR_op_t{ - subscripted, - index, - }, - ); + return null; +} - return res; - }, - .String => { - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); +fn generateFunction(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const components = self.state.?.ast.nodes.items(.components)[node].Function; + const function_signature = self.state.?.ast.nodes.items(.components)[components.function_signature.?].FunctionType; + const type_defs = self.state.?.ast.nodes.items(.type_def); - try self.buildExternApiCall( - .bz_objStringSubscript, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - subscripted, - index_val, - }, - ); + const function_def = type_defs[node].?.resolved_type.?.Function; + const function_type = function_def.function_type; - return res; - }, - .Map => { - if (value) |val| { - try self.buildExternApiCall( - .bz_mapSet, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - subscripted, - index_val, - val, - }, - ); + // Those are not allowed to be compiled + std.debug.assert(function_type != .Extern and function_type != .Script and function_type != .ScriptEntryPoint); - return subscripted; - } + // Get fully qualified name of function + var qualified_name = try self.getFunctionQualifiedName(node, true); + defer qualified_name.deinit(); - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); + // If this is not the root function, we need to compile this later + if (self.state.?.function_node != node) { + var nativefn_qualified_name = try self.getFunctionQualifiedName(node, false); + defer nativefn_qualified_name.deinit(); - try self.buildExternApiCall( - .bz_mapGet, - res, - &[_]m.MIR_op_t{ - subscripted, - index_val, - }, - ); + // Remember that we need to compile this function later + try self.functions_queue.put(node, null); - return res; - }, - else => unreachable, + // For now declare it + const native_raw = m.MIR_new_import(self.ctx, @ptrCast(qualified_name.items.ptr)); + const native = m.MIR_new_import(self.ctx, @ptrCast(nativefn_qualified_name.items.ptr)); + + // Call bz_closure + const dest = m.MIR_new_reg_op( + self.ctx, + try self.REG("result", m.MIR_T_I64), + ); + + try self.buildExternApiCall( + .bz_closure, + dest, + &[_]m.MIR_op_t{ + // ctx + m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), + // function_node + m.MIR_new_uint_op(self.ctx, node), + m.MIR_new_ref_op(self.ctx, native), + m.MIR_new_ref_op(self.ctx, native_raw), + }, + ); + + return dest; } -} -fn generateIs(self: *Self, is_node: *n.IsNode) Error!?m.MIR_op_t { - const res = m.MIR_new_reg_op( + // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't + const ctx_name = self.vm.gc.allocator.dupeZ(u8, "ctx") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(ctx_name); + const function = m.MIR_new_func_arr( self.ctx, - try self.REG("res", m.MIR_T_I64), - ); - - try self.buildExternApiCall( - .bz_valueIs, - res, - &[_]m.MIR_op_t{ - (try self.generateNode(is_node.left)).?, - m.MIR_new_uint_op(self.ctx, is_node.constant.val), + @ptrCast(qualified_name.items.ptr), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = @ptrCast(ctx_name.ptr), + .size = undefined, + }, }, ); - return res; -} + self.state.?.function = function; -fn generateAs(self: *Self, as_node: *n.AsNode) Error!?m.MIR_op_t { - const left = m.MIR_new_reg_op( - self.ctx, - try self.REG("left", m.MIR_T_I64), + // Build ref to ctx arg and vm + self.state.?.ctx_reg = m.MIR_reg(self.ctx, "ctx", function.u.func); + self.state.?.vm_reg = m.MIR_new_func_reg(self.ctx, function.u.func, m.MIR_T_I64, "vm"); + + const index = try self.REG("index", m.MIR_T_I64); + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), ); self.MOV( - left, - (try self.generateNode(as_node.left)).?, + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + @offsetOf(o.NativeCtx, "vm"), + self.state.?.ctx_reg.?, + index, + 0, + ), ); - const res = m.MIR_new_reg_op( - self.ctx, - try self.REG("res", m.MIR_T_I64), - ); + if (function_signature.lambda) { + try self.buildReturn( + (try self.generateNode(components.body.?)) orelse m.MIR_new_uint_op( + self.ctx, + Value.Void.val, + ), + ); - try self.buildExternApiCall( - .bz_valueIs, - res, - &[_]m.MIR_op_t{ - left, - m.MIR_new_uint_op(self.ctx, as_node.constant.val), - }, - ); + self.state.?.return_emitted = true; + } else { + _ = try self.generateNode(components.body.?); + } - const casted_label = m.MIR_new_label(self.ctx); + if (type_defs[self.state.?.function_node].?.resolved_type.?.Function.return_type.def_type == .Void and !self.state.?.return_emitted) { + try self.buildReturn(m.MIR_new_uint_op(self.ctx, Value.Void.val)); + } - self.BEQ( - m.MIR_new_label_op(self.ctx, casted_label), - res, - m.MIR_new_uint_op(self.ctx, v.Value.True.val), - ); + m.MIR_finish_func(self.ctx); - self.MOV( - left, - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), - ); + // Add the NativeFn version of the function + const native_fn = try self.generateNativeFn(node, function); - self.append(casted_label); + try self.functions_queue.put( + node, + [_]m.MIR_item_t{ + native_fn, + self.state.?.function.?, + }, + ); - return left; + return m.MIR_new_ref_op(self.ctx, function); } -fn generateTry(self: *Self, try_node: *n.TryNode) Error!?m.MIR_op_t { - const raise_label = m.MIR_new_label(self.ctx); - const out_label = m.MIR_new_label(self.ctx); - const catch_label = m.MIR_new_label(self.ctx); - var clause_labels = std.ArrayList(m.MIR_insn_t).init(self.vm.gc.allocator); - defer clause_labels.deinit(); - - for (try_node.clauses.keys()) |_| { - try clause_labels.append( - m.MIR_new_label(self.ctx), - ); - } +fn generateNativeFn(self: *Self, node: Ast.Node.Index, raw_fn: m.MIR_item_t) !m.MIR_item_t { + const type_defs = self.state.?.ast.nodes.items(.type_def); - const unconditional_label = if (try_node.unconditional_clause != null) - m.MIR_new_label(self.ctx) - else - null; + const function_def = type_defs[node].?.resolved_type.?.Function; + const function_type = function_def.function_type; - const index = try self.REG("index", m.MIR_T_I64); - const stack_top_ptr_base = try self.REG("stack_top_ptr_base", m.MIR_T_I64); + std.debug.assert(function_type != .Extern); - self.MOV( - m.MIR_new_reg_op(self.ctx, index), - m.MIR_new_uint_op(self.ctx, 0), - ); + var nativefn_qualified_name = try self.getFunctionQualifiedName(node, false); + defer nativefn_qualified_name.deinit(); - // *[*]Value - const stack_top_ptr = m.MIR_new_mem_op( + // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't + const ctx_name = self.vm.gc.allocator.dupeZ(u8, "ctx") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(ctx_name); + const function = m.MIR_new_func_arr( self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "stack_top"), - self.state.?.ctx_reg.?, - index, + @ptrCast(nativefn_qualified_name.items.ptr), + 1, + &[_]m.MIR_type_t{m.MIR_T_I64}, 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = @ptrCast(ctx_name.ptr), + .size = undefined, + }, + }, ); - self.MOV( - m.MIR_new_reg_op(self.ctx, stack_top_ptr_base), - stack_top_ptr, - ); + const previous = self.state.?.function; + self.state.?.function = function; + defer self.state.?.function = previous; - // [*]Value - const stack_top = m.MIR_new_reg_op( - self.ctx, - try self.REG("stack_top", m.MIR_T_I64), + const ctx_reg = m.MIR_reg(self.ctx, "ctx", function.u.func); + const index = try self.REG("index", m.MIR_T_I64); + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), ); + const vm_reg = try self.REG("vm", m.MIR_T_I64); self.MOV( - stack_top, + m.MIR_new_reg_op(self.ctx, vm_reg), m.MIR_new_mem_op( self.ctx, m.MIR_T_P, - 0, - stack_top_ptr_base, + @offsetOf(o.NativeCtx, "vm"), + ctx_reg, index, - 1, + 0, ), ); - // Set it as current jump env - const try_ctx = m.MIR_new_reg_op(self.ctx, try self.REG("try_ctx", m.MIR_T_I64)); - try self.buildExternApiCall( - .bz_setTryCtx, - try_ctx, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - }, - ); - - const env = m.MIR_new_reg_op( - self.ctx, - try self.REG("env", m.MIR_T_I64), - ); - self.ADD( - env, - try_ctx, - m.MIR_new_uint_op(self.ctx, @offsetOf(r.TryCtx, "env")), - ); + const should_try = function_type == .Test or (function_def.error_types != null and function_def.error_types.?.len > 0); + if (should_try) { + // Catch any error to forward them as a buzz error (push payload + return -1) + // Set it as current jump env + const try_ctx = m.MIR_new_reg_op( + self.ctx, + try self.REG("try_ctx", m.MIR_T_I64), + ); - const status = try self.REG("status", m.MIR_T_I64); - try self.buildExternApiCall( - .setjmp, - m.MIR_new_reg_op(self.ctx, status), - &[_]m.MIR_op_t{env}, - ); + try self.buildExternApiCall( + .bz_setTryCtx, + try_ctx, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, vm_reg), + }, + ); - self.BEQ( - m.MIR_new_label_op(self.ctx, catch_label), - m.MIR_new_reg_op(self.ctx, status), - m.MIR_new_int_op(self.ctx, 1), - ); + const env = m.MIR_new_reg_op( + self.ctx, + try self.REG("env", m.MIR_T_I64), + ); - _ = try self.generateNode(try_node.body); + self.ADD( + env, + try_ctx, + m.MIR_new_uint_op( + self.ctx, + @offsetOf(r.TryCtx, "env"), + ), + ); - self.JMP(out_label); + // setjmp + const status = m.MIR_new_reg_op( + self.ctx, + try self.REG("status", m.MIR_T_I64), + ); + try self.buildExternApiCall( + .setjmp, + status, + &[_]m.MIR_op_t{env}, + ); - self.append(catch_label); + const fun_label = m.MIR_new_label(self.ctx); - const payload = m.MIR_new_reg_op( + // If status is 0, go to body, else go to catch clauses + self.BEQ( + m.MIR_new_label_op(self.ctx, fun_label), + status, + m.MIR_new_uint_op(self.ctx, 0), + ); + + // Unwind TryCtx + try self.buildExternApiCall( + .bz_popTryCtx, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, vm_reg), + }, + ); + + // Payload already on stack so juste return -1; + self.RET(m.MIR_new_int_op(self.ctx, -1)); + + self.append(fun_label); + } + + // Call the raw function + const result = m.MIR_new_reg_op( self.ctx, - try self.REG("payload", m.MIR_T_I64), + try self.REG("result", m.MIR_T_I64), ); - try self.buildPop(payload); - - // Get stack top as it was before try block - // Close upvalues up to it - try self.buildExternApiCall( - .bz_closeUpValues, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - stack_top, - }, + m.MIR_append_insn( + self.ctx, + function, + m.MIR_new_insn_arr( + self.ctx, + m.MIR_CALL, + 4, + &[_]m.MIR_op_t{ + m.MIR_new_ref_op(self.ctx, try ExternApi.rawfn.declare(self)), + m.MIR_new_ref_op(self.ctx, raw_fn), + result, + m.MIR_new_reg_op(self.ctx, ctx_reg), + }, + ), ); - // Restore stack top as it was before the try block - self.MOV( - try self.LOAD(stack_top_ptr), - stack_top, + const should_return = function_def.return_type.def_type != .Void; + + // Push its result back into the VM + if (should_return) { + try self.buildPush(result); + } else { + try self.buildPush(m.MIR_new_uint_op(self.ctx, Value.Void.val)); + } + + if (should_try) { + // Unwind TryCtx + try self.buildExternApiCall( + .bz_popTryCtx, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, vm_reg), + }, + ); + } + + self.RET( + m.MIR_new_int_op( + self.ctx, + if (should_return) 1 else 0, + ), ); - // Put error back on stack - try self.buildPush(payload); + m.MIR_finish_func(self.ctx); - self.JMP(if (clause_labels.items.len > 0) - clause_labels.items[0] - else - unconditional_label.?); + return function; +} + +fn getFunctionQualifiedName(self: *Self, node: Ast.Node.Index, raw: bool) !std.ArrayList(u8) { + const components = self.state.?.ast.nodes.items(.components)[node].Function; + const type_defs = self.state.?.ast.nodes.items(.type_def); + + const function_def = type_defs[node].?.resolved_type.?.Function; + const function_type = function_def.function_type; + const name = function_def.name.string; + + var qualified_name = std.ArrayList(u8).init(self.vm.gc.allocator); + + try qualified_name.appendSlice(name); + + // Main and script are not allowed to be compiled + std.debug.assert(function_type != .ScriptEntryPoint and function_type != .Script); + + // Don't qualify extern functions + if (function_type != .Extern) { + try qualified_name.append('.'); + try qualified_name.writer().print("{}", .{components.id}); + } + if (function_type != .Extern and raw) { + try qualified_name.appendSlice(".raw"); + } + try qualified_name.append(0); + + return qualified_name; +} + +pub fn compileZdefContainer(self: *Self, ast: Ast, zdef_element: Ast.Zdef.ZdefElement) Error!void { + var wrapper_name = std.ArrayList(u8).init(self.vm.gc.allocator); + defer wrapper_name.deinit(); + + try wrapper_name.writer().print("zdef_{s}\x00", .{zdef_element.zdef.name}); + + const module = m.MIR_new_module(self.ctx, @ptrCast(wrapper_name.items.ptr)); + defer m.MIR_finish_module(self.ctx); + + if (BuildOptions.jit_debug) { + std.debug.print( + "Compiling zdef struct getters/setters for `{s}` of type `{s}`\n", + .{ + zdef_element.zdef.name, + (zdef_element.zdef.type_def.toStringAlloc(self.vm.gc.allocator) catch unreachable).items, + }, + ); + } + + // FIXME: Not everything applies to a zdef, maybe split the two states? + self.state = .{ + .ast = ast, + .module = module, + .prototypes = std.AutoHashMap(ExternApi, m.MIR_item_t).init(self.vm.gc.allocator), + .function_node = undefined, + .registers = std.AutoHashMap([*:0]const u8, usize).init(self.vm.gc.allocator), + .closure = undefined, + }; + defer self.reset(); + + const foreign_def = zdef_element.zdef.type_def.resolved_type.?.ForeignContainer; + + var getters = std.ArrayList(m.MIR_item_t).init(self.vm.gc.allocator); + defer getters.deinit(); + var setters = std.ArrayList(m.MIR_item_t).init(self.vm.gc.allocator); + defer setters.deinit(); + + switch (foreign_def.zig_type) { + .Struct => { + for (foreign_def.zig_type.Struct.fields) |field| { + const container_field = foreign_def.fields.getEntry(field.name).?; + + try getters.append( + try self.buildZdefContainerGetter( + container_field.value_ptr.*.offset, + foreign_def.name.string, + field.name, + foreign_def.buzz_type.get(field.name).?, + field.type, + ), + ); + + try setters.append( + try self.buildZdefContainerSetter( + container_field.value_ptr.*.offset, + foreign_def.name.string, + field.name, + foreign_def.buzz_type.get(field.name).?, + field.type, + ), + ); + } + }, + + .Union => { + for (foreign_def.zig_type.Union.fields) |field| { + const container_field = foreign_def.fields.getEntry(field.name).?; + _ = container_field; + + try getters.append( + try self.buildZdefUnionGetter( + foreign_def.name.string, + field.name, + foreign_def.buzz_type.get(field.name).?, + field.type, + ), + ); + + try setters.append( + try self.buildZdefUnionSetter( + foreign_def.name.string, + field.name, + foreign_def.buzz_type.get(field.name).?, + field.type, + ), + ); + } + }, + + else => unreachable, + } + + if (BuildOptions.jit_debug) { + var debug_path = std.ArrayList(u8).init(self.vm.gc.allocator); + defer debug_path.deinit(); + debug_path.writer().print("./dist/gen/zdef-{s}.mod.mir\u{0}", .{wrapper_name.items}) catch unreachable; + + const debug_file = std.c.fopen(@ptrCast(debug_path.items.ptr), "w").?; + defer _ = std.c.fclose(debug_file); + + m.MIR_output_module(self.ctx, debug_file, module); + } + + m.MIR_load_module(self.ctx, module); + + // Load external functions + var it_ext = self.required_ext_api.iterator(); + while (it_ext.next()) |kv| { + switch (kv.key_ptr.*) { + // TODO: don't mix those with actual api functions + .rawfn, .nativefn => {}, + else => m.MIR_load_external( + self.ctx, + kv.key_ptr.*.name(), + kv.key_ptr.*.ptr(), + ), + } + } + + // Link everything together + m.MIR_link(self.ctx, m.MIR_set_lazy_gen_interface, null); + + m.MIR_gen_init(self.ctx, 1); + defer m.MIR_gen_finish(self.ctx); + + // Gen getters/setters + switch (foreign_def.zig_type) { + .Struct => { + for (foreign_def.zig_type.Struct.fields, 0..) |field, idx| { + const struct_field = foreign_def.fields.getEntry(field.name).?; + struct_field.value_ptr.*.getter = @alignCast(@ptrCast(m.MIR_gen( + self.ctx, + 0, + getters.items[idx], + ) orelse unreachable)); + + struct_field.value_ptr.*.setter = @alignCast(@ptrCast(m.MIR_gen( + self.ctx, + 0, + setters.items[idx], + ) orelse unreachable)); + } + }, + .Union => { + for (foreign_def.zig_type.Union.fields, 0..) |field, idx| { + const struct_field = foreign_def.fields.getEntry(field.name).?; + struct_field.value_ptr.*.getter = @alignCast(@ptrCast(m.MIR_gen( + self.ctx, + 0, + getters.items[idx], + ) orelse unreachable)); + + struct_field.value_ptr.*.setter = @alignCast(@ptrCast(m.MIR_gen( + self.ctx, + 0, + setters.items[idx], + ) orelse unreachable)); + } + }, + else => unreachable, + } +} + +fn buildBuzzValueToZigValue(self: *Self, buzz_type: *o.ObjTypeDef, zig_type: ZigType, buzz_value: m.MIR_op_t, dest: m.MIR_op_t) !void { + switch (zig_type) { + .Int => { + if (buzz_type.def_type == .Float) { + const tmp_float = m.MIR_new_reg_op( + self.ctx, + try self.REG("tmp_float", m.MIR_T_D), + ); + + // This is a int represented by a buzz float value + self.buildValueToDouble(buzz_value, tmp_float); + + // Convert it back to an int + self.D2I(dest, tmp_float); + } else { + self.buildValueToInteger(buzz_value, dest); + } + }, + // TODO: float can't be truncated like ints, we need a D2F instruction + .Float => { + if (zig_type.Float.bits == 64) { + self.buildValueToDouble(buzz_value, dest); + } else { + std.debug.assert(zig_type.Float.bits == 32); + self.buildValueToFloat(buzz_value, dest); + } + }, + .Bool => self.buildValueToBoolean(buzz_value, dest), + .Pointer => { + // Is it a [*:0]const u8 + // zig fmt: off + if (zig_type.Pointer.child.* == .Int + and zig_type.Pointer.child.Int.bits == 8 + and zig_type.Pointer.child.Int.signedness == .unsigned) { + // zig fmt: on + try self.buildValueToCString(buzz_value, dest); + } else if (zig_type.Pointer.child.* == .Struct or zig_type.Pointer.child.* == .Union) { + try self.buildValueToForeignContainerPtr(buzz_value, dest); + } else { + try self.buildValueToUserData(buzz_value, dest); + } + }, + .Optional => { + // Is it a [*:0]const u8 + // zig fmt: off + if (zig_type.Optional.child.Pointer.child.* == .Int + and zig_type.Optional.child.Pointer.child.Int.bits == 8 + and zig_type.Optional.child.Pointer.child.Int.signedness == .unsigned) { + // zig fmt: on + try self.buildValueToOptionalCString(buzz_value, dest); + } else if (zig_type.Optional.child.Pointer.child.* == .Struct) { + try self.buildValueToOptionalForeignContainerPtr(buzz_value, dest); + } else { + try self.buildValueToOptionalUserData(buzz_value, dest); + } + }, + else => unreachable, + } +} + +fn buildZigValueToBuzzValue(self: *Self, buzz_type: *o.ObjTypeDef, zig_type: ZigType, zig_value: m.MIR_op_t, dest: m.MIR_op_t) !void { + switch (zig_type) { + .Int => { + if (buzz_type.def_type == .Float) { + // This is a int represented by a buzz float value + const tmp_float = m.MIR_new_reg_op( + self.ctx, + try self.REG("tmp_float", m.MIR_T_D), + ); + + // Convert it back to an int + if (zig_type.Int.signedness == .signed) { + self.I2D(tmp_float, zig_value); + } else { + self.UI2D(tmp_float, zig_value); + } + + // And to a buzz value + self.buildValueFromDouble(tmp_float, dest); + } else { + self.buildValueFromInteger(zig_value, dest); + } + }, + .Float => { + if (zig_type.Float.bits == 64) { + self.buildValueFromDouble(zig_value, dest); + } else { + std.debug.assert(zig_type.Float.bits == 32); + self.buildValueFromFloat(zig_value, dest); + } + }, + .Bool => self.buildValueFromBoolean(zig_value, dest), + .Void => self.MOV(dest, m.MIR_new_uint_op(self.ctx, Value.Void.val)), + .Union, .Struct => unreachable, // FIXME: should call an api function build a ObjForeignContainer + .Pointer => { + // Is it a [*:0]const u8 + // zig fmt: off + if (zig_type.Pointer.child.* == .Int + and zig_type.Pointer.child.Int.bits == 8 + and zig_type.Pointer.child.Int.signedness == .unsigned) { + // zig fmt: on + try self.buildValueFromCString(zig_value, dest); + } else if (zig_type.Pointer.child.* == .Struct) { + try self.buildValueFromForeignContainerPtr(buzz_type, zig_value, dest); + } else { + try self.buildValueFromUserData(zig_value, dest); + } + }, + .Optional => { + // Is it a [*:0]const u8 + // zig fmt: off + if (zig_type.Optional.child.Pointer.child.* == .Int + and zig_type.Optional.child.Pointer.child.Int.bits == 8 + and zig_type.Optional.child.Pointer.child.Int.signedness == .unsigned) { + // zig fmt: on + try self.buildValueFromOptionalCString(zig_value, dest); + } else if (zig_type.Optional.child.Pointer.child.* == .Struct) { + try self.buildValueFromOptionalForeignContainerPtr(buzz_type, zig_value, dest); + } else { + try self.buildValueFromOptionalUserData(zig_value, dest); + } + }, + else => unreachable, + } +} - for (try_node.clauses.keys(), 0..) |type_def, idx| { - const label = clause_labels.items[idx]; - const clause = try_node.clauses.get(type_def).?; +pub fn compileZdef(self: *Self, buzz_ast: Ast, zdef: Ast.Zdef.ZdefElement) Error!*o.ObjNative { + var wrapper_name = std.ArrayList(u8).init(self.vm.gc.allocator); + defer wrapper_name.deinit(); - self.append(label); + try wrapper_name.writer().print("zdef_{s}\x00", .{zdef.zdef.name}); - // Get error payload from stack - const err_payload = try self.REG("err_paylaod", m.MIR_T_I64); - try self.buildPeek( - 0, - m.MIR_new_reg_op(self.ctx, err_payload), - ); + const dupped_symbol = self.vm.gc.allocator.dupeZ(u8, zdef.zdef.name) catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(dupped_symbol); - const matches = try self.REG("matches", m.MIR_T_I64); - try self.buildExternApiCall( - .bz_valueIs, - m.MIR_new_reg_op(self.ctx, matches), - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, err_payload), - m.MIR_new_uint_op(self.ctx, type_def.toValue().val), + const module = m.MIR_new_module(self.ctx, @ptrCast(wrapper_name.items.ptr)); + defer m.MIR_finish_module(self.ctx); + + if (BuildOptions.jit_debug) { + std.debug.print( + "Compiling zdef wrapper for `{s}` of type `{s}`\n", + .{ + zdef.zdef.name, + (zdef.zdef.type_def.toStringAlloc(self.vm.gc.allocator) catch unreachable).items, }, ); + } - try self.unwrap( - .Bool, - m.MIR_new_reg_op(self.ctx, matches), - m.MIR_new_reg_op(self.ctx, matches), - ); + // FIXME: Not everything applies to a zdef, maybe split the two states? + self.state = .{ + .ast = buzz_ast, + .module = module, + .prototypes = std.AutoHashMap(ExternApi, m.MIR_item_t).init(self.vm.gc.allocator), + .function_node = undefined, + .registers = std.AutoHashMap([*:0]const u8, usize).init(self.vm.gc.allocator), + .closure = undefined, + }; + defer self.reset(); - self.BEQ( - m.MIR_new_label_op( - self.ctx, - if (idx < try_node.clauses.keys().len - 1) - clause_labels.items[idx + 1] - else if (unconditional_label) |unconditional| - unconditional - else - raise_label, - ), - m.MIR_new_reg_op(self.ctx, matches), - m.MIR_new_uint_op(self.ctx, 0), - ); + // Build wrapper + const wrapper_item = try self.buildZdefWrapper(zdef); - // Unwind TryCtx - try self.buildExternApiCall( - .bz_popTryCtx, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - }, - ); + _ = m.MIR_new_export(self.ctx, @ptrCast(wrapper_name.items.ptr)); - _ = try self.generateNode(clause); + if (BuildOptions.jit_debug) { + var debug_path = std.ArrayList(u8).init(self.vm.gc.allocator); + defer debug_path.deinit(); + debug_path.writer().print("./dist/gen/zdef-{s}.mod.mir\u{0}", .{zdef.zdef.name}) catch unreachable; - self.JMP(out_label); - } + const debug_file = std.c.fopen(@ptrCast(debug_path.items.ptr), "w").?; + defer _ = std.c.fclose(debug_file); - if (unconditional_label) |label| { - self.append(label); + m.MIR_output_module(self.ctx, debug_file, module); + } - // Unwind TryCtx - try self.buildExternApiCall( - .bz_popTryCtx, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - }, - ); + m.MIR_load_module(self.ctx, module); - _ = try self.generateNode(try_node.unconditional_clause.?); + // Load function we're wrapping + m.MIR_load_external(self.ctx, dupped_symbol, zdef.fn_ptr.?); - self.JMP(out_label); + // Load external functions + var it_ext = self.required_ext_api.iterator(); + while (it_ext.next()) |kv| { + switch (kv.key_ptr.*) { + // TODO: don't mix those with actual api functions + .rawfn, .nativefn => {}, + else => m.MIR_load_external( + self.ctx, + kv.key_ptr.*.name(), + kv.key_ptr.*.ptr(), + ), + } } - self.append(raise_label); + // Link everything together + m.MIR_link(self.ctx, m.MIR_set_lazy_gen_interface, null); - // Unwind TryCtx - try self.buildExternApiCall( - .bz_popTryCtx, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - }, - ); + m.MIR_gen_init(self.ctx, 1); + defer m.MIR_gen_finish(self.ctx); - // Raise error again - try self.buildExternApiCall( - .bz_rethrow, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + return try self.vm.gc.allocateObject( + o.ObjNative, + .{ + .native = m.MIR_gen(self.ctx, 0, wrapper_item) orelse unreachable, }, ); +} - self.append(out_label); - - // Unwind TryCtx - try self.buildExternApiCall( - .bz_popTryCtx, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), +fn zigToMIRType(zig_type: ZigType) m.MIR_type_t { + return switch (zig_type) { + .Int => if (zig_type.Int.signedness == .signed) + switch (zig_type.Int.bits) { + 8 => m.MIR_T_I8, + 16 => m.MIR_T_I16, + 32 => m.MIR_T_I32, + 64 => m.MIR_T_I64, + else => unreachable, + } + else switch (zig_type.Int.bits) { + 8 => m.MIR_T_U8, + 16 => m.MIR_T_U16, + 32 => m.MIR_T_U32, + 64 => m.MIR_T_U64, + else => unreachable, }, - ); + .Float => switch (zig_type.Float.bits) { + 32 => m.MIR_T_F, + 64 => m.MIR_T_D, + else => unreachable, + }, + .Bool => m.MIR_T_U8, + // Optional only allowed on pointers so its either 0 or an actual ptr + .Pointer, + .Optional, + => m.MIR_T_I64, + // See https://github.com/vnmakarov/mir/issues/332 passing struct by values will need some work + .Struct => unreachable, //m.MIR_T_BLK, + else => unreachable, + }; +} - return null; +fn zigToMIRRegType(zig_type: ZigType) m.MIR_type_t { + return switch (zig_type) { + .Int, .Bool, .Pointer => m.MIR_T_I64, + .Float => switch (zig_type.Float.bits) { + 32 => m.MIR_T_F, + 64 => m.MIR_T_D, + else => unreachable, + }, + // See https://github.com/vnmakarov/mir/issues/332 passing struct by values will need some work + .Struct => unreachable, //m.MIR_T_BLK, + // Optional are only allowed on pointers + .Optional => m.MIR_T_I64, + else => { + std.debug.print("{}\n", .{zig_type}); + unreachable; + }, + }; } -fn generateThrow(self: *Self, throw_node: *n.ThrowNode) Error!?m.MIR_op_t { - if (throw_node.unconditional) { - self.state.?.return_emitted = true; - } +fn buildZdefWrapper(self: *Self, zdef_element: Ast.Zdef.ZdefElement) Error!m.MIR_item_t { + var wrapper_name = std.ArrayList(u8).init(self.vm.gc.allocator); + defer wrapper_name.deinit(); - try self.buildExternApiCall( - .bz_throw, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(throw_node.error_value)).?, - }, - ); + try wrapper_name.writer().print("zdef_{s}\x00", .{zdef_element.zdef.name}); - return null; -} + var wrapper_protocol_name = std.ArrayList(u8).init(self.vm.gc.allocator); + defer wrapper_protocol_name.deinit(); -fn generateUnwrap(self: *Self, unwrap_node: *n.UnwrapNode) Error!?m.MIR_op_t { - const value = (try self.generateNode(unwrap_node.unwrapped)).?; + try wrapper_protocol_name.writer().print("p_zdef_{s}\x00", .{zdef_element.zdef.name}); - // Remember that we need to had a terminator to this block that will jump at the end of the optionals chain - if (self.state.?.opt_jump == null) { - self.state.?.opt_jump = .{ - // Store the value on the stack, that spot will be overwritten with the final value of the optional chain - .alloca = try self.REG("opt", m.MIR_T_I64), - .current_insn = std.ArrayList(m.MIR_insn_t).init(self.vm.gc.allocator), - }; - } + const dupped_symbol = self.vm.gc.allocator.dupeZ(u8, zdef_element.zdef.name) catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(dupped_symbol); - const current_insn = m.MIR_new_insn_arr( + // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't + const ctx_name = self.vm.gc.allocator.dupeZ(u8, "ctx") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(ctx_name); + const function = m.MIR_new_func_arr( self.ctx, - m.MIR_MOV, - 2, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.opt_jump.?.alloca), - value, + @ptrCast(wrapper_name.items.ptr), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = @ptrCast(ctx_name.ptr), + .size = undefined, + }, }, ); - self.append( - current_insn, - ); - - try self.state.?.opt_jump.?.current_insn.append(current_insn); + self.state.?.function = function; - return value; -} + // Build ref to ctx arg and vm + self.state.?.ctx_reg = m.MIR_reg(self.ctx, "ctx", function.u.func); + self.state.?.vm_reg = m.MIR_new_func_reg(self.ctx, function.u.func, m.MIR_T_I64, "vm"); -fn generateForeignContainerInit(self: *Self, object_init_node: *n.ObjectInitNode) Error!?m.MIR_op_t { - const instance = m.MIR_new_reg_op( - self.ctx, - try self.REG("instance", m.MIR_T_I64), + const index = try self.REG("index", m.MIR_T_I64); + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), ); - try self.buildExternApiCall( - .bz_containerInstance, - instance, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, object_init_node.node.type_def.?.toValue().val), - }, + self.MOV( + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_mem_op( + self.ctx, + m.MIR_T_P, + @offsetOf(o.NativeCtx, "vm"), + self.state.?.ctx_reg.?, + index, + 0, + ), ); - for (object_init_node.properties.keys()) |property_name| { - const value = object_init_node.properties.get(property_name).?; + const function_def = zdef_element.zdef.type_def.resolved_type.?.Function; + const zig_function_def = zdef_element.zdef.zig_type; - try self.buildExternApiCall( - .bz_containerSet, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - instance, - m.MIR_new_uint_op(self.ctx, @as(u64, @intFromPtr(property_name.ptr))), - m.MIR_new_uint_op(self.ctx, property_name.len), - (try self.generateNode(value)).?, + // Get arguments from stack + var full_args = std.ArrayList(m.MIR_op_t).init(self.vm.gc.allocator); + defer full_args.deinit(); + + var arg_types = std.ArrayList(m.MIR_var_t).init(self.vm.gc.allocator); + defer arg_types.deinit(); + + for (zig_function_def.Fn.params) |param| { + try arg_types.append( + .{ + .type = zigToMIRType( + if (param.type) |param_type| + param_type.* + else + .{ .Void = {} }, + ), + .name = "param", + .size = undefined, }, ); } - return instance; -} + const zig_return_type = if (zig_function_def.Fn.return_type) |return_type| + return_type.* + else + ZigType{ .Void = {} }; -fn generateObjectInit(self: *Self, object_init_node: *n.ObjectInitNode) Error!?m.MIR_op_t { - if (object_init_node.node.type_def.?.def_type == .ForeignContainer) { - return self.generateForeignContainerInit(object_init_node); - } + const buzz_return_type = function_def.return_type; - const object = if (object_init_node.object != null and object_init_node.object.?.type_def.?.def_type == .Object) - (try self.generateNode(object_init_node.object.?)).? + const return_mir_type = if (zig_return_type != .Void) + zigToMIRRegType(zig_return_type) else - m.MIR_new_uint_op(self.ctx, v.Value.Null.val); + null; - const typedef = m.MIR_new_uint_op( + const result = if (return_mir_type) |rmt| + m.MIR_new_reg_op( + self.ctx, + try self.REG( + "result", + rmt, + ), + ) + else + null; + + const result_value = m.MIR_new_reg_op( self.ctx, - object_init_node.node.type_def.?.toValue().val, + try self.REG( + "result_value", + m.MIR_T_I64, + ), ); - const instance = m.MIR_new_reg_op( - self.ctx, - try self.REG("instance", m.MIR_T_I64), + // proto + try full_args.append( + m.MIR_new_ref_op( + self.ctx, + m.MIR_new_proto_arr( + self.ctx, + @ptrCast(wrapper_protocol_name.items.ptr), + if (return_mir_type != null) 1 else 0, + if (return_mir_type) |rmt| &[_]m.MIR_type_t{rmt} else null, + arg_types.items.len, + arg_types.items.ptr, + ), + ), ); - try self.buildExternApiCall( - .bz_instance, - instance, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - object, - typedef, - }, + // import + try full_args.append( + m.MIR_new_ref_op( + self.ctx, + m.MIR_new_import(self.ctx, @ptrCast(dupped_symbol)), + ), ); + if (result) |res| { + try full_args.append(res); + } + // actual args + var it = function_def.parameters.iterator(); + var idx = function_def.parameters.count(); + var zidx: usize = 0; + while (it.next() != null) : ({ + idx -= 1; + zidx += 1; + }) { + const param_value = m.MIR_new_reg_op( + self.ctx, + try self.REG("param_value", m.MIR_T_I64), + ); + const param = m.MIR_new_reg_op( + self.ctx, + try self.REG("param", zigToMIRRegType(zig_function_def.Fn.params[zidx].type.?.*)), + ); - // Push to prevent collection - try self.buildPush(instance); + try self.buildPeek(@intCast(idx - 1), param_value); + + try self.buildBuzzValueToZigValue( + function_def.parameters.get(function_def.parameters.keys()[zidx]).?, + zig_function_def.Fn.params[zidx].type.?.*, + param_value, + param, + ); - for (object_init_node.properties.keys()) |property_name| { - const value = object_init_node.properties.get(property_name).?; + try full_args.append(param); + } - try self.buildExternApiCall( - .bz_setInstanceField, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - instance, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(property_name)).toValue().val), - (try self.generateNode(value)).?, - }, + // Make the call + self.append( + m.MIR_new_insn_arr( + self.ctx, + m.MIR_CALL, + full_args.items.len, + full_args.items.ptr, + ), + ); + + // Push result on stack + if (result) |res| { + try self.buildZigValueToBuzzValue( + buzz_return_type, + zig_return_type, + res, + result_value, ); + try self.buildPush(result_value); } - try self.buildPop(instance); + // Return -1/0/1 + self.RET( + m.MIR_new_int_op( + self.ctx, + if (function_def.return_type.def_type != .Void) + 1 + else + 0, + ), + ); - return instance; -} + m.MIR_finish_func(self.ctx); -fn generateForceUnwrap(self: *Self, force_unwrap_node: *n.ForceUnwrapNode) Error!?m.MIR_op_t { - const expr = (try self.generateNode(force_unwrap_node.unwrapped)).?; + return function; +} - const out_label = m.MIR_new_label(self.ctx); +fn buildZdefUnionGetter( + self: *Self, + union_name: []const u8, + field_name: []const u8, + buzz_type: *o.ObjTypeDef, + zig_type: *const ZigType, +) Error!m.MIR_item_t { + var getter_name = std.ArrayList(u8).init(self.vm.gc.allocator); + defer getter_name.deinit(); - self.BNE( - out_label, - expr, - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), + try getter_name.writer().print( + "zdef_union_{s}_{s}_getter\x00", + .{ + union_name, + field_name, + }, ); - try self.buildExternApiCall( - .bz_throw, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString("Force unwrapped optional is null")).toValue().val), + // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't + const vm_name = self.vm.gc.allocator.dupeZ(u8, "vm") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(vm_name); + const data_name = self.vm.gc.allocator.dupeZ(u8, "data") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(data_name); + const function = m.MIR_new_func_arr( + self.ctx, + @ptrCast(getter_name.items.ptr), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = @ptrCast(vm_name.ptr), + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = @ptrCast(data_name.ptr), + .size = undefined, + }, }, ); - self.append(out_label); - - return expr; -} + self.state.?.function = function; + self.state.?.vm_reg = m.MIR_reg(self.ctx, "vm", function.u.func); -fn generateUnary(self: *Self, unary_node: *n.UnaryNode) Error!?m.MIR_op_t { - const left = (try self.generateNode(unary_node.left)).?; - const result = m.MIR_new_reg_op( + const result_value = m.MIR_new_reg_op( self.ctx, - try self.REG("result", m.MIR_T_I64), + try self.REG( + "result_value", + m.MIR_T_I64, + ), ); - switch (unary_node.operator) { - .Bnot => { - try self.unwrap(.Integer, left, result); - self.NOTS(result, result); - self.wrap(.Integer, result, result); - }, - .Bang => { - try self.unwrap(.Bool, left, result); - - const true_label = m.MIR_new_label(self.ctx); - const out_label = m.MIR_new_label(self.ctx); - - self.BEQ( - m.MIR_new_label_op(self.ctx, out_label), - result, - m.MIR_new_uint_op(self.ctx, 1), - ); - - self.MOV( - result, - m.MIR_new_uint_op(self.ctx, v.Value.True.val), - ); - - self.JMP(out_label); - - self.append(true_label); + const data_reg = m.MIR_reg(self.ctx, "data", function.u.func); - self.MOV( - result, - m.MIR_new_uint_op(self.ctx, v.Value.False.val), + // Getting an union field is essentialy casting it the concrete buzz type + switch (zig_type.*) { + .Struct, .Union => { + try self.buildExternApiCall( + .bz_containerFromSlice, + result_value, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + m.MIR_new_uint_op(self.ctx, @intFromPtr(buzz_type)), + m.MIR_new_reg_op(self.ctx, data_reg), + m.MIR_new_uint_op(self.ctx, zig_type.size()), + }, ); - - self.append(out_label); }, - .Minus => { - try self.unwrap(.Integer, left, result); + else => { + const index = try self.REG("index", m.MIR_T_I64); + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), + ); - if (unary_node.left.type_def.?.def_type == .Integer) { - self.NEGS(result, result); - } else { - self.DNEG(result, result); - } + const field_ptr = m.MIR_new_mem_op( + self.ctx, + zigToMIRType(zig_type.*), + 0, + data_reg, + index, + 0, + ); - self.wrap( - unary_node.left.type_def.?.def_type, - result, - result, + try self.buildZigValueToBuzzValue( + buzz_type, + zig_type.*, + field_ptr, + result_value, ); }, - else => unreachable, } - return result; -} - -fn generatePattern(self: *Self, pattern_node: *n.PatternNode) Error!?m.MIR_op_t { - return m.MIR_new_uint_op(self.ctx, pattern_node.constant.toValue().val); -} - -fn generateForEach(self: *Self, foreach_node: *n.ForEachNode) Error!?m.MIR_op_t { - // If iteratble is empty constant, skip the node - if (foreach_node.iterable.isConstant(foreach_node.iterable)) { - const iterable = (foreach_node.iterable.toValue(foreach_node.iterable, self.vm.gc) catch @panic("Could not compile foreach loop")).obj(); - - if (switch (iterable.obj_type) { - .List => o.ObjList.cast(iterable).?.items.items.len == 0, - .Map => o.ObjMap.cast(iterable).?.map.count() == 0, - .String => o.ObjString.cast(iterable).?.string.len == 0, - .Enum => o.ObjEnum.cast(iterable).?.cases.items.len == 0, - else => unreachable, - }) { - return null; - } - } + self.RET(result_value); - // key, value and iterable are locals of the foreach scope - // var declaration so will push value on stack - _ = try self.generateNode(&foreach_node.key.node); - // var declaration so will push value on stack - _ = try self.generateNode(&foreach_node.value.node); - const iterable = (try self.generateNode(foreach_node.iterable)).?; - try self.buildPush(iterable); + m.MIR_finish_func(self.ctx); - const key_ptr = try self.buildStackPtr(2); - const value_ptr = try self.buildStackPtr(1); + _ = m.MIR_new_export(self.ctx, @ptrCast(getter_name.items.ptr)); - const cond_label = m.MIR_new_label(self.ctx); - const out_label = m.MIR_new_label(self.ctx); + return function; +} - const previous_out_label = self.state.?.break_label; - self.state.?.break_label = out_label; - const previous_continue_label = self.state.?.continue_label; - self.state.?.continue_label = cond_label; +fn buildZdefUnionSetter( + self: *Self, + union_name: []const u8, + field_name: []const u8, + buzz_type: *o.ObjTypeDef, + zig_type: *const ZigType, +) Error!m.MIR_item_t { + var setter_name = std.ArrayList(u8).init(self.vm.gc.allocator); + defer setter_name.deinit(); - self.append(cond_label); + try setter_name.writer().print( + "zdef_union_{s}_{s}_setter\x00", + .{ + union_name, + field_name, + }, + ); - // Call appropriate `next` method - if (foreach_node.iterable.type_def.?.def_type == .Fiber) { - // TODO: fiber foreach (tricky, need to complete foreach op after it has yielded) - unreachable; - } else if (foreach_node.iterable.type_def.?.def_type == .Enum) { - try self.buildExternApiCall( - .bz_enumNext, - try self.LOAD(value_ptr), - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - iterable, - try self.LOAD(value_ptr), + // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't + const vm_name = self.vm.gc.allocator.dupeZ(u8, "vm") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(vm_name); + const data_name = self.vm.gc.allocator.dupeZ(u8, "data") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(data_name); + const new_value_name = self.vm.gc.allocator.dupeZ(u8, "new_value") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(new_value_name); + const function = m.MIR_new_func_arr( + self.ctx, + @ptrCast(setter_name.items.ptr), + 0, + null, + 3, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = @ptrCast(vm_name.ptr), + .size = undefined, }, - ); - - // If next key is null stop, otherwise do loop - self.BEQ( - m.MIR_new_label_op(self.ctx, out_label), - try self.LOAD(value_ptr), - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), - ); - } else { - // The `next` method will store the new key in the key local - try self.buildExternApiCall( - switch (foreach_node.iterable.type_def.?.def_type) { - .String => .bz_stringNext, - .List => .bz_listNext, - .Map => .bz_mapNext, - else => unreachable, + .{ + .type = m.MIR_T_P, + .name = @ptrCast(data_name.ptr), + .size = undefined, }, - try self.LOAD(value_ptr), - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - iterable, - // Pass ptr so the method can put he new key in it - key_ptr, + .{ + .type = m.MIR_T_U64, + .name = @ptrCast(new_value_name.ptr), + .size = undefined, }, - ); - - // If next key is null stop, otherwise loop - self.BEQ( - m.MIR_new_label_op(self.ctx, out_label), - try self.LOAD(key_ptr), - m.MIR_new_uint_op(self.ctx, v.Value.Null.val), - ); - } - - _ = try self.generateNode(foreach_node.block); + }, + ); - self.JMP(cond_label); + self.state.?.function = function; + self.state.?.vm_reg = m.MIR_reg(self.ctx, "vm", function.u.func); - self.append(out_label); + const data_reg = m.MIR_reg(self.ctx, "data", function.u.func); + const new_value_reg = m.MIR_new_reg_op(self.ctx, m.MIR_reg(self.ctx, "new_value", function.u.func)); + switch (zig_type.*) { + .Struct, .Union => { + const ptr_reg = m.MIR_new_reg_op(self.ctx, try self.REG("ptr_reg", m.MIR_T_I64)); + try self.buildValueToForeignContainerPtr( + new_value_reg, + ptr_reg, + ); - self.state.?.break_label = previous_out_label; - self.state.?.continue_label = previous_continue_label; + try self.buildExternApiCall( + .memcpy, + null, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, data_reg), + m.MIR_new_uint_op(self.ctx, zig_type.size()), + ptr_reg, + m.MIR_new_uint_op(self.ctx, zig_type.size()), + }, + ); + }, + else => { + const index = try self.REG("index", m.MIR_T_I64); + self.MOV( + m.MIR_new_reg_op(self.ctx, index), + m.MIR_new_uint_op(self.ctx, 0), + ); - return null; -} + const field_ptr = m.MIR_new_mem_op( + self.ctx, + zigToMIRType(zig_type.*), + 0, + data_reg, + index, + 0, + ); -fn generateBlock(self: *Self, block_node: *n.BlockNode) Error!?m.MIR_op_t { - for (block_node.statements.items) |statement| { - _ = try self.generateNode(statement); + try self.buildBuzzValueToZigValue( + buzz_type, + zig_type.*, + new_value_reg, + field_ptr, + ); + }, } - return null; -} - -fn generateFunDeclaration(self: *Self, fun_declaration_node: *n.FunDeclarationNode) Error!?m.MIR_op_t { - return try self.generateFunction(fun_declaration_node.function); -} - -fn generateVarDeclaration(self: *Self, var_declaration_node: *n.VarDeclarationNode) Error!?m.MIR_op_t { - // We should only declare locals - assert(var_declaration_node.slot_type == .Local); + m.MIR_finish_func(self.ctx); - try self.buildPush( - if (var_declaration_node.value) |value| - (try self.generateNode(value)).? - else - m.MIR_new_uint_op( - self.ctx, - v.Value.Null.val, - ), - ); + _ = m.MIR_new_export(self.ctx, @ptrCast(setter_name.items.ptr)); - return null; + return function; } -fn generateFunction(self: *Self, function_node: *n.FunctionNode) Error!?m.MIR_op_t { - const root_node: *n.FunctionNode = @ptrCast(@alignCast(self.state.?.function_node)); - - const function_def = function_node.node.type_def.?.resolved_type.?.Function; - const function_type = function_def.function_type; - - // Those are not allowed to be compiled - assert(function_type != .Extern and function_type != .Script and function_type != .ScriptEntryPoint); - - // Get fully qualified name of function - var qualified_name = try self.getFunctionQualifiedName( - function_node, - true, - ); - defer qualified_name.deinit(); - - // If this is not the root function, we need to compile this later - if (root_node != function_node) { - var nativefn_qualified_name = try self.getFunctionQualifiedName(function_node, false); - defer nativefn_qualified_name.deinit(); - - // Remember that we need to compile this function later - try self.functions_queue.put( - @ptrCast( - @alignCast(function_node), - ), - null, - ); - - // For now declare it - const native_raw = m.MIR_new_import(self.ctx, @ptrCast(qualified_name.items.ptr)); - const native = m.MIR_new_import(self.ctx, @ptrCast(nativefn_qualified_name.items.ptr)); - - // Call bz_closure - const dest = m.MIR_new_reg_op( - self.ctx, - try self.REG("result", m.MIR_T_I64), - ); - - try self.buildExternApiCall( - .bz_closure, - dest, - &[_]m.MIR_op_t{ - // ctx - m.MIR_new_reg_op(self.ctx, self.state.?.ctx_reg.?), - // function_node - m.MIR_new_uint_op(self.ctx, @intFromPtr(function_node)), - m.MIR_new_ref_op(self.ctx, native), - m.MIR_new_ref_op(self.ctx, native_raw), - }, - ); +fn buildZdefContainerGetter( + self: *Self, + offset: usize, + struct_name: []const u8, + field_name: []const u8, + buzz_type: *o.ObjTypeDef, + zig_type: *const ZigType, +) Error!m.MIR_item_t { + var getter_name = std.ArrayList(u8).init(self.vm.gc.allocator); + defer getter_name.deinit(); - return dest; - } + try getter_name.writer().print( + "zdef_struct_{s}_{s}_getter\x00", + .{ + struct_name, + field_name, + }, + ); // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't - var ctx_name = self.vm.gc.allocator.dupeZ(u8, "ctx") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(ctx_name); + const vm_name = self.vm.gc.allocator.dupeZ(u8, "vm") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(vm_name); + const data_name = self.vm.gc.allocator.dupeZ(u8, "data") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(data_name); const function = m.MIR_new_func_arr( self.ctx, - @ptrCast(qualified_name.items.ptr), + @ptrCast(getter_name.items.ptr), 1, &[_]m.MIR_type_t{m.MIR_T_U64}, - 1, + 2, &[_]m.MIR_var_t{ .{ .type = m.MIR_T_P, - .name = @ptrCast(ctx_name.ptr), + .name = @ptrCast(vm_name.ptr), + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = @ptrCast(data_name.ptr), .size = undefined, }, }, ); self.state.?.function = function; + self.state.?.vm_reg = m.MIR_reg(self.ctx, "vm", function.u.func); - // Build ref to ctx arg and vm - self.state.?.ctx_reg = m.MIR_reg(self.ctx, "ctx", function.u.func); - self.state.?.vm_reg = m.MIR_new_func_reg(self.ctx, function.u.func, m.MIR_T_I64, "vm"); + const data_reg = m.MIR_reg(self.ctx, "data", function.u.func); const index = try self.REG("index", m.MIR_T_I64); self.MOV( m.MIR_new_reg_op(self.ctx, index), m.MIR_new_uint_op(self.ctx, 0), ); - self.MOV( - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "vm"), - self.state.?.ctx_reg.?, - index, - 0, - ), + + const field_ptr = m.MIR_new_mem_op( + self.ctx, + zigToMIRType(zig_type.*), + @intCast(offset), + data_reg, + index, + 0, ); - if (function_node.arrow_expr) |arrow_expr| { - try self.buildReturn( - (try self.generateNode(arrow_expr)) orelse m.MIR_new_uint_op( - self.ctx, - v.Value.Void.val, - ), - ); + const result_value = m.MIR_new_reg_op( + self.ctx, + try self.REG( + "result_value", + m.MIR_T_I64, + ), + ); - self.state.?.return_emitted = true; - } else { - _ = try self.generateNode(function_node.body.?.toNode()); - } + try self.buildZigValueToBuzzValue( + buzz_type, + zig_type.*, + field_ptr, + result_value, + ); - if (self.state.?.function_node.node.type_def.?.resolved_type.?.Function.return_type.def_type == .Void and !self.state.?.return_emitted) { - try self.buildReturn(m.MIR_new_uint_op(self.ctx, v.Value.Void.val)); - } + self.RET(result_value); m.MIR_finish_func(self.ctx); - // Add the NativeFn version of the function - const native_fn = try self.generateNativeFn(function_node, function); - - try self.functions_queue.put( - function_node, - [_]m.MIR_item_t{ - native_fn, - self.state.?.function.?, - }, - ); + _ = m.MIR_new_export(self.ctx, @ptrCast(getter_name.items.ptr)); - return m.MIR_new_ref_op(self.ctx, function); + return function; } -fn generateNativeFn(self: *Self, function_node: *n.FunctionNode, raw_fn: m.MIR_item_t) !m.MIR_item_t { - const function_def = function_node.node.type_def.?.resolved_type.?.Function; - const function_type = function_def.function_type; - - assert(function_type != .Extern); +fn buildZdefContainerSetter( + self: *Self, + offset: usize, + struct_name: []const u8, + field_name: []const u8, + buzz_type: *o.ObjTypeDef, + zig_type: *const ZigType, +) Error!m.MIR_item_t { + var setter_name = std.ArrayList(u8).init(self.vm.gc.allocator); + defer setter_name.deinit(); - var nativefn_qualified_name = try self.getFunctionQualifiedName(function_node, false); - defer nativefn_qualified_name.deinit(); + try setter_name.writer().print( + "zdef_struct_{s}_{s}_setter\x00", + .{ + struct_name, + field_name, + }, + ); // FIXME: I don't get why we need this: a simple constant becomes rubbish as soon as we enter MIR_new_func_arr if we don't - var ctx_name = self.vm.gc.allocator.dupeZ(u8, "ctx") catch @panic("Out of memory"); - defer self.vm.gc.allocator.free(ctx_name); + const vm_name = self.vm.gc.allocator.dupeZ(u8, "vm") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(vm_name); + const data_name = self.vm.gc.allocator.dupeZ(u8, "data") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(data_name); + const new_value_name = self.vm.gc.allocator.dupeZ(u8, "new_value") catch @panic("Out of memory"); + defer self.vm.gc.allocator.free(new_value_name); const function = m.MIR_new_func_arr( self.ctx, - @ptrCast(nativefn_qualified_name.items.ptr), - 1, - &[_]m.MIR_type_t{m.MIR_T_I64}, - 1, + @ptrCast(setter_name.items.ptr), + 0, + null, + 3, &[_]m.MIR_var_t{ .{ .type = m.MIR_T_P, - .name = @ptrCast(ctx_name.ptr), + .name = @ptrCast(vm_name.ptr), + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = @ptrCast(data_name.ptr), + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = @ptrCast(new_value_name.ptr), .size = undefined, }, }, ); - const previous = self.state.?.function; self.state.?.function = function; - defer self.state.?.function = previous; + self.state.?.vm_reg = m.MIR_reg(self.ctx, "vm", function.u.func); + + const data_reg = m.MIR_reg(self.ctx, "data", function.u.func); + const new_value_reg = m.MIR_reg(self.ctx, "new_value", function.u.func); - const ctx_reg = m.MIR_reg(self.ctx, "ctx", function.u.func); const index = try self.REG("index", m.MIR_T_I64); self.MOV( m.MIR_new_reg_op(self.ctx, index), m.MIR_new_uint_op(self.ctx, 0), ); - const vm_reg = try self.REG("vm", m.MIR_T_I64); - self.MOV( - m.MIR_new_reg_op(self.ctx, vm_reg), - m.MIR_new_mem_op( - self.ctx, - m.MIR_T_P, - @offsetOf(o.NativeCtx, "vm"), - ctx_reg, - index, - 0, - ), - ); - - const should_try = function_type == .Test or (function_def.error_types != null and function_def.error_types.?.len > 0); - if (should_try) { - // Catch any error to forward them as a buzz error (push payload + return -1) - // Set it as current jump env - const try_ctx = m.MIR_new_reg_op( - self.ctx, - try self.REG("try_ctx", m.MIR_T_I64), - ); - - try self.buildExternApiCall( - .bz_setTryCtx, - try_ctx, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, vm_reg), - }, - ); - - const env = m.MIR_new_reg_op( - self.ctx, - try self.REG("env", m.MIR_T_I64), - ); - - self.ADD( - env, - try_ctx, - m.MIR_new_uint_op( - self.ctx, - @offsetOf(r.TryCtx, "env"), - ), - ); - - // setjmp - const status = m.MIR_new_reg_op( - self.ctx, - try self.REG("status", m.MIR_T_I64), - ); - try self.buildExternApiCall( - .setjmp, - status, - &[_]m.MIR_op_t{env}, - ); - - const fun_label = m.MIR_new_label(self.ctx); - - // If status is 0, go to body, else go to catch clauses - self.BEQ( - m.MIR_new_label_op(self.ctx, fun_label), - status, - m.MIR_new_uint_op(self.ctx, 0), - ); - - // Unwind TryCtx - try self.buildExternApiCall( - .bz_popTryCtx, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, vm_reg), - }, - ); - - // Payload already on stack so juste return -1; - self.RET(m.MIR_new_int_op(self.ctx, -1)); - - self.append(fun_label); - } - - // Call the raw function - const result = m.MIR_new_reg_op( - self.ctx, - try self.REG("result", m.MIR_T_I64), - ); - m.MIR_append_insn( + const field_ptr = m.MIR_new_mem_op( self.ctx, - function, - m.MIR_new_insn_arr( - self.ctx, - m.MIR_CALL, - 4, - &[_]m.MIR_op_t{ - m.MIR_new_ref_op(self.ctx, try ExternApi.rawfn.declare(self)), - m.MIR_new_ref_op(self.ctx, raw_fn), - result, - m.MIR_new_reg_op(self.ctx, ctx_reg), - }, - ), + zigToMIRType(zig_type.*), + @intCast(offset), + data_reg, + index, + 0, ); - const should_return = function_def.return_type.def_type != .Void; - - // Push its result back into the VM - if (should_return) { - try self.buildPush(result); - } else { - try self.buildPush(m.MIR_new_uint_op(self.ctx, v.Value.Void.val)); - } - - if (should_try) { - // Unwind TryCtx - try self.buildExternApiCall( - .bz_popTryCtx, - null, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, vm_reg), - }, - ); - } - - self.RET( - m.MIR_new_int_op( - self.ctx, - if (should_return) 1 else 0, - ), + try self.buildBuzzValueToZigValue( + buzz_type, + zig_type.*, + m.MIR_new_reg_op(self.ctx, new_value_reg), + field_ptr, ); m.MIR_finish_func(self.ctx); - return function; -} - -fn getFunctionQualifiedName(self: *Self, function_node: *n.FunctionNode, raw: bool) !std.ArrayList(u8) { - const function_def = function_node.node.type_def.?.resolved_type.?.Function; - const function_type = function_def.function_type; - const name = function_def.name.string; - - var qualified_name = std.ArrayList(u8).init(self.vm.gc.allocator); - - try qualified_name.appendSlice(name); - - // Main and script are not allowed to be compiled - assert(function_type != .ScriptEntryPoint and function_type != .Script); - - // Don't qualify extern functions - if (function_type != .Extern) { - try qualified_name.append('.'); - try qualified_name.writer().print("{}", .{function_node.id}); - } - if (function_type != .Extern and raw) { - try qualified_name.appendSlice(".raw"); - } - try qualified_name.append(0); + _ = m.MIR_new_export(self.ctx, @ptrCast(setter_name.items.ptr)); - return qualified_name; + return function; } // MIR helper functions @@ -6005,1147 +6124,3 @@ fn REG(self: *Self, name: [*:0]const u8, reg_type: m.MIR_type_t) !m.MIR_reg_t { return reg; } - -export fn bz_exit(code: c_int) noreturn { - std.os.exit(@truncate(@as(c_uint, @bitCast(code)))); -} - -pub const ExternApi = enum { - nativefn, - rawfn, - - bz_objStringConcat, - bz_objStringSubscript, - bz_toString, - bz_newList, - bz_listAppend, - bz_listGet, - bz_listSet, - bz_valueEqual, - bz_listConcat, - bz_newMap, - bz_mapSet, - bz_mapGet, - bz_mapConcat, - bz_valueIs, - bz_setTryCtx, - bz_popTryCtx, - bz_rethrow, - bz_throw, - bz_closeUpValues, - bz_getUpValue, - bz_setUpValue, - bz_closure, - bz_context, - bz_instance, - bz_setInstanceField, - bz_getInstanceField, - bz_getObjectField, - bz_setObjectField, - bz_getStringField, - bz_getPatternField, - bz_getFiberField, - bz_getEnumCase, - bz_getEnumCaseValue, - bz_getListField, - bz_getMapField, - bz_getEnumCaseFromValue, - bz_bindMethod, - bz_stringNext, - bz_listNext, - bz_mapNext, - bz_enumNext, - bz_clone, - bz_valueToCString, - bz_valueToUserData, - bz_userDataToValue, - bz_valueToForeignContainerPtr, - bz_stringZ, - bz_containerGet, - bz_containerSet, - bz_containerInstance, - bz_valueTypeOf, - bz_containerFromSlice, - - bz_dumpStack, - - // https://opensource.apple.com/source/libplatform/libplatform-161/include/setjmp.h.auto.html - setjmp, - // libc exit: https://man7.org/linux/man-pages/man3/exit.3.html - exit, - memcpy, - - dumpInt, - bz_valueDump, - - pub fn declare(self: ExternApi, jit: *Self) !m.MIR_item_t { - const prototype = jit.state.?.prototypes.get(self) orelse self.proto(jit.ctx); - - try jit.required_ext_api.put(self, {}); - try jit.state.?.prototypes.put( - self, - prototype, - ); - - return prototype; - } - - fn proto(self: ExternApi, ctx: m.MIR_context_t) m.MIR_item_t { - return switch (self) { - .bz_objStringSubscript => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 3, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "obj_string", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "index_value", - .size = undefined, - }, - }, - ), - .bz_closure => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 4, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "ctx", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "function_node", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "native", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "native_raw", - .size = undefined, - }, - }, - ), - .bz_toString, .bz_newList, .bz_newMap => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_listAppend => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 3, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "list", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_listGet, .bz_mapGet => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "list", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "index", - .size = undefined, - }, - }, - ), - .bz_listSet, .bz_mapSet => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 4, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "list", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "index", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_valueEqual => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "other", - .size = undefined, - }, - }, - ), - .bz_listConcat, - .bz_mapConcat, - .bz_objStringConcat, - .bz_instance, - => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 3, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "list", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "other_list", - .size = undefined, - }, - }, - ), - .bz_getEnumCaseFromValue, .bz_getEnumCase => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 3, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "enum", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_getEnumCaseValue => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "enum_instance", - .size = undefined, - }, - }, - ), - .bz_getObjectField, .bz_valueIs => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "subject", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "field", - .size = undefined, - }, - }, - ), - .bz_getListField, - .bz_getMapField, - .bz_getStringField, - .bz_getPatternField, - .bz_getFiberField, - .bz_getInstanceField, - => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 4, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "subject", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "field", - .size = undefined, - }, - .{ - .type = m.MIR_T_U8, - .name = "bind", - .size = undefined, - }, - }, - ), - .bz_setInstanceField, - .bz_setObjectField, - .bz_bindMethod, - => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 4, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "instance", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "field", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_getUpValue => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "native_ctx", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "slot", - .size = undefined, - }, - }, - ), - .bz_setUpValue => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 3, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "native_ctx", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "slot", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_closeUpValues => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "last", - .size = undefined, - }, - }, - ), - .bz_setTryCtx => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - // *TryContext - &[_]m.MIR_type_t{m.MIR_T_P}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - }, - ), - .bz_popTryCtx, .bz_rethrow => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - }, - ), - .bz_throw => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "payload", - .size = undefined, - }, - }, - ), - .bz_context => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_P}, - 4, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "native_ctx", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "function", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "new_native_ctx", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "arg_count", - .size = undefined, - }, - }, - ), - .setjmp => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "jmp_buf", - .size = undefined, - }, - }, - ), - .bz_clone => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_dumpStack => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "ctx", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "offset", - .size = undefined, - }, - }, - ), - .exit => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U8, - .name = "status", - .size = undefined, - }, - }, - ), - .bz_stringNext, - .bz_listNext, - .bz_mapNext, - .bz_enumNext, - => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 3, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "iterable", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "key", - .size = undefined, - }, - }, - ), - .rawfn => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_U64}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "ctx", - .size = undefined, - }, - }, - ), - .nativefn => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_I16}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "ctx", - .size = undefined, - }, - }, - ), - .dumpInt => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_valueDump => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - }, - ), - .bz_valueToCString => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_P}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_valueToUserData => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_P}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_userDataToValue => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_I64}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_valueToForeignContainerPtr => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_P}, - 1, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_stringZ => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_I64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "string", - .size = undefined, - }, - }, - ), - .bz_containerGet => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_I64}, - 4, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "field", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "len", - .size = undefined, - }, - }, - ), - .bz_containerSet => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 5, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "field", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "len", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "new_value", - .size = undefined, - }, - }, - ), - .bz_containerInstance => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_I64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - }, - ), - .bz_valueTypeOf => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_I64}, - 2, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_U64, - .name = "value", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - }, - ), - .bz_containerFromSlice => m.MIR_new_proto_arr( - ctx, - self.pname(), - 1, - &[_]m.MIR_type_t{m.MIR_T_I64}, - 4, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "vm", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "type_def", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "ptr", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "len", - .size = undefined, - }, - }, - ), - .memcpy => m.MIR_new_proto_arr( - ctx, - self.pname(), - 0, - null, - 4, - &[_]m.MIR_var_t{ - .{ - .type = m.MIR_T_P, - .name = "dest", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "dest_len", - .size = undefined, - }, - .{ - .type = m.MIR_T_P, - .name = "source", - .size = undefined, - }, - .{ - .type = m.MIR_T_U64, - .name = "source_len", - .size = undefined, - }, - }, - ), - }; - } - - pub fn ptr(self: ExternApi) *anyopaque { - return switch (self) { - .bz_toString => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_toString))), - .bz_objStringConcat => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_objStringConcat))), - .bz_objStringSubscript => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_objStringSubscript))), - .bz_stringNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_stringNext))), - .bz_newList => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_newList))), - .bz_listAppend => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listAppend))), - .bz_listGet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listGet))), - .bz_listSet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listSet))), - .bz_listConcat => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listConcat))), - .bz_listNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listNext))), - .bz_newMap => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_newMap))), - .bz_mapGet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_mapGet))), - .bz_mapSet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_mapSet))), - .bz_mapNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_mapNext))), - .bz_mapConcat => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_mapConcat))), - .bz_valueEqual => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueEqual))), - .bz_valueIs => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueIs))), - .bz_closure => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_closure))), - .bz_context => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_context))), - .bz_instance => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_instance))), - .bz_setInstanceField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_setInstanceField))), - .bz_getInstanceField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getInstanceField))), - .bz_rethrow => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_rethrow))), - .bz_throw => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_throw))), - .bz_bindMethod => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_bindMethod))), - .bz_getUpValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_getUpValue))), - .bz_setUpValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_setUpValue))), - .bz_closeUpValues => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_closeUpValues))), - .bz_clone => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_clone))), - .bz_dumpStack => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_dumpStack))), - .bz_getEnumCaseFromValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjEnum.bz_getEnumCaseFromValue))), - .bz_getEnumCase => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjEnum.bz_getEnumCase))), - .bz_enumNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjEnum.bz_enumNext))), - .bz_getEnumCaseValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjEnumInstance.bz_getEnumCaseValue))), - .bz_setObjectField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_setObjectField))), - .bz_getObjectField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getObjectField))), - .bz_getListField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_getListField))), - .bz_getMapField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_getMapField))), - .bz_getStringField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_getStringField))), - .bz_getPatternField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjPattern.bz_getPatternField))), - .bz_getFiberField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjFiber.bz_getFiberField))), - .bz_setTryCtx => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_setTryCtx))), - .bz_popTryCtx => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_popTryCtx))), - .bz_valueToCString => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueToCString))), - .bz_valueToUserData => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueToUserData))), - .bz_userDataToValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjUserData.bz_userDataToValue))), - .bz_valueToForeignContainerPtr => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueToForeignContainerPtr))), - .bz_stringZ => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_stringZ))), - .bz_containerGet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjForeignContainer.bz_containerGet))), - .bz_containerSet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjForeignContainer.bz_containerSet))), - .bz_containerInstance => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjForeignContainer.bz_containerInstance))), - .bz_valueTypeOf => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueTypeOf))), - .bz_containerFromSlice => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjForeignContainer.bz_containerFromSlice))), - .memcpy => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.bz_memcpy))), - .setjmp => @as( - *anyopaque, - @ptrFromInt( - @intFromPtr(&(if (builtin.os.tag == .macos or builtin.os.tag == .linux or builtin.os.tag == .windows) jmp._setjmp else jmp.setjmp)), - ), - ), - .exit => @as(*anyopaque, @ptrFromInt(@intFromPtr(&bz_exit))), - - .dumpInt => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.dumpInt))), - .bz_valueDump => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueDump))), - else => { - std.debug.print("{s}\n", .{self.name()}); - unreachable; - }, - }; - } - - pub fn name(self: ExternApi) [*:0]const u8 { - return switch (self) { - .nativefn => "NativeFn", - .rawfn => "RawFn", - - .bz_objStringConcat => "bz_objStringConcat", - .bz_objStringSubscript => "bz_objStringSubscript", - .bz_toString => "bz_toString", - .bz_newList => "bz_newList", - .bz_listAppend => "bz_listAppend", - .bz_listGet => "bz_listGet", - .bz_listSet => "bz_listSet", - .bz_valueEqual => "bz_valueEqual", - .bz_listConcat => "bz_listConcat", - .bz_newMap => "bz_newMap", - .bz_mapSet => "bz_mapSet", - .bz_mapGet => "bz_mapGet", - .bz_mapConcat => "bz_mapConcat", - .bz_valueIs => "bz_valueIs", - .bz_setTryCtx => "bz_setTryCtx", - .bz_popTryCtx => "bz_popTryCtx", - .bz_rethrow => "bz_rethrow", - .bz_throw => "bz_throw", - .bz_getUpValue => "bz_getUpValue", - .bz_setUpValue => "bz_setUpValue", - .bz_closeUpValues => "bz_closeUpValues", - .bz_closure => "bz_closure", - .bz_context => "bz_context", - .bz_instance => "bz_instance", - .bz_setInstanceField => "bz_setInstanceField", - .bz_getInstanceField => "bz_getInstanceField", - .bz_setObjectField => "bz_setObjectField", - .bz_getObjectField => "bz_getObjectField", - .bz_getStringField => "bz_getStringField", - .bz_getPatternField => "bz_getPatternField", - .bz_getFiberField => "bz_getFiberField", - .bz_getEnumCase => "bz_getEnumCase", - .bz_getEnumCaseValue => "bz_getEnumCaseValue", - .bz_getListField => "bz_getListField", - .bz_getMapField => "bz_getMapField", - .bz_getEnumCaseFromValue => "bz_getEnumCaseFromValue", - .bz_bindMethod => "bz_bindMethod", - .bz_stringNext => "bz_stringNext", - .bz_listNext => "bz_listNext", - .bz_mapNext => "bz_mapNext", - .bz_enumNext => "bz_enumNext", - .bz_clone => "bz_clone", - .bz_valueToCString => "bz_valueToCString", - .bz_valueToUserData => "bz_valueToUserData", - .bz_userDataToValue => "bz_userDataToValue", - .bz_valueToForeignContainerPtr => "bz_valueToForeignContainerPtr", - .bz_stringZ => "bz_stringZ", - .bz_containerGet => "bz_containerGet", - .bz_containerSet => "bz_containerSet", - .bz_containerInstance => "bz_containerInstance", - .bz_valueTypeOf => "bz_valueTypeOf", - .bz_containerFromSlice => "bz_containerFromSlice", - .memcpy => "bz_memcpy", - - .setjmp => if (builtin.os.tag == .macos or builtin.os.tag == .linux or builtin.os.tag == .windows) "_setjmp" else "setjmp", - .exit => "bz_exit", - - .bz_dumpStack => "bz_dumpStack", - - .dumpInt => "dumpInt", - .bz_valueDump => "bz_valueDump", - }; - } - - pub fn pname(self: ExternApi) [*:0]const u8 { - return switch (self) { - .nativefn => "p_NativeFn", - .rawfn => "p_RawFn", - - .bz_objStringConcat => "p_bz_objStringConcat", - .bz_objStringSubscript => "p_bz_objStringSubscript", - .bz_toString => "p_bz_toString", - .bz_newList => "p_bz_newList", - .bz_listAppend => "p_bz_listAppend", - .bz_listGet => "p_bz_listGet", - .bz_listSet => "p_bz_listSet", - .bz_valueEqual => "p_bz_valueEqual", - .bz_listConcat => "p_bz_listConcat", - .bz_newMap => "p_bz_newMap", - .bz_mapSet => "p_bz_mapSet", - .bz_mapGet => "p_bz_mapGet", - .bz_mapConcat => "p_bz_mapConcat", - .bz_valueIs => "p_bz_valueIs", - .bz_setTryCtx => "p_bz_setTryCtx", - .bz_popTryCtx => "p_bz_popTryCtx", - .bz_rethrow => "p_bz_rethrow", - .bz_throw => "p_bz_throw", - .bz_getUpValue => "p_bz_getUpValue", - .bz_setUpValue => "p_bz_setUpValue", - .bz_closeUpValues => "p_bz_closeUpValues", - .bz_closure => "p_bz_closure", - .bz_context => "p_bz_context", - .bz_instance => "p_bz_instance", - .bz_setInstanceField => "p_bz_setInstanceField", - .bz_getInstanceField => "p_bz_getInstanceField", - .bz_setObjectField => "p_bz_setObjectField", - .bz_getObjectField => "p_bz_getObjectField", - .bz_getStringField => "p_bz_getStringField", - .bz_getPatternField => "p_bz_getPatternField", - .bz_getFiberField => "p_bz_getFiberField", - .bz_getEnumCase => "p_bz_getEnumCase", - .bz_getEnumCaseValue => "p_bz_getEnumCaseValue", - .bz_getListField => "p_bz_getListField", - .bz_getMapField => "p_bz_getMapField", - .bz_getEnumCaseFromValue => "p_bz_getEnumCaseFromValue", - .bz_bindMethod => "p_bz_bindMethod", - .bz_stringNext => "p_bz_stringNext", - .bz_listNext => "p_bz_listNext", - .bz_mapNext => "p_bz_mapNext", - .bz_enumNext => "p_bz_enumNext", - .bz_clone => "p_bz_clone", - .bz_valueToCString => "p_bz_valueToCString", - .bz_valueToUserData => "p_bz_valueToUserData", - .bz_userDataToValue => "p_bz_userDataToValue", - .bz_valueToForeignContainerPtr => "p_bz_valueToForeignContainerPtr", - .bz_stringZ => "p_bz_stringZ", - .bz_containerGet => "p_bz_containerGet", - .bz_containerSet => "p_bz_containerSet", - .bz_containerInstance => "p_bz_containerInstance", - .bz_valueTypeOf => "p_bz_valueTypeOf", - .bz_containerFromSlice => "p_bz_containerFromSlice", - .memcpy => "p_bz_memcpy", - - .setjmp => if (builtin.os.tag == .macos or builtin.os.tag == .linux or builtin.os.windows) "p__setjmp" else "p_setjmp", - .exit => "p_exit", - - .bz_dumpStack => "p_bz_dumpStack", - - .dumpInt => "p_dumpInt", - .bz_valueDump => "p_bz_valueDump", - }; - } -}; diff --git a/src/Parser.zig b/src/Parser.zig new file mode 100644 index 00000000..de2280ce --- /dev/null +++ b/src/Parser.zig @@ -0,0 +1,7758 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const BuildOptions = @import("build_options"); +const obj = @import("obj.zig"); +const Token = @import("Token.zig"); +const Chunk = @import("Chunk.zig"); +const Value = @import("value.zig").Value; +const FFI = @import("FFI.zig"); +const Ast = @import("Ast.zig"); +const GarbageCollector = @import("memory.zig").GarbageCollector; +const Scanner = @import("scanner.zig").Scanner; +const RunFlavor = @import("vm.zig").RunFlavor; +const Reporter = @import("Reporter.zig"); +const StringParser = @import("string_parser.zig").StringParser; +const pcre = @import("pcre.zig"); + +const Self = @This(); + +extern fn dlerror() [*:0]u8; + +pub fn default_buzz_prefix() []const u8 { + // todo: maybe it's better to have multiple search paths? + unreachable; +} + +var _buzz_path_buffer: [4096]u8 = undefined; +pub fn buzz_prefix() []const u8 { + if (std.os.getenv("BUZZ_PATH")) |buzz_path| return buzz_path; + const path = std.fs.selfExePath(&_buzz_path_buffer) catch return default_buzz_prefix(); + const path1 = std.fs.path.dirname(path) orelse default_buzz_prefix(); + const path2 = std.fs.path.dirname(path1) orelse default_buzz_prefix(); + return path2; +} + +var _buzz_path_buffer2: [4096]u8 = undefined; +/// the returned string can be used only until next call to this function +pub fn buzz_lib_path() []const u8 { + const path2 = buzz_prefix(); + const sep = std.fs.path.sep_str; + return std.fmt.bufPrint(&_buzz_path_buffer2, "{s}" ++ sep ++ "lib" ++ sep ++ "buzz", .{path2}) catch unreachable; +} + +pub const CompileError = error{ + Unrecoverable, + Recoverable, +}; + +pub const Local = struct { + name: *obj.ObjString, + name_token: Ast.TokenIndex, + location: Ast.TokenIndex, + type_def: *obj.ObjTypeDef, + depth: i32, + is_captured: bool, + constant: bool, + referenced: bool = false, + + pub fn isReferenced(self: Local) bool { + // zig fmt: off + return self.referenced + or self.type_def.def_type == .Void + or self.type_def.def_type == .Placeholder + or self.name.string[0] == '$' + or (self.name.string[0] == '_' and self.name.string.len == 1); + // zig fmt: on + } +}; + +pub const Global = struct { + prefix: ?[]const u8 = null, + name: *obj.ObjString, // TODO: do i need to mark those? does it need to be an objstring? + name_token: Ast.TokenIndex, + location: Ast.TokenIndex, + type_def: *obj.ObjTypeDef, + initialized: bool = false, + exported: bool = false, + export_alias: ?[]const u8 = null, + hidden: bool = false, + constant: bool, + referenced: bool = false, + // When resolving a placeholder, the start of the resolution is the global + // If `constant` is true, we can search for any `.Assignment` link and fail then. + + pub fn isReferenced(self: Global) bool { + const function_type = if (self.type_def.def_type == .Function) + self.type_def.resolved_type.?.Function.function_type + else + null; + + // zig fmt: off + return self.referenced + or self.type_def.def_type == .Void + or self.type_def.def_type == .Placeholder + or (function_type != null and (function_type == .Extern or function_type == .Abstract or function_type == .EntryPoint or function_type == .ScriptEntryPoint or function_type != .Repl)) + or self.name.string[0] == '$' + or (self.name.string[0] == '_' and self.name.string.len == 1) + or self.exported; + // zig fmt: on + } +}; + +pub const UpValue = struct { + index: u8, + is_local: bool, +}; + +pub const Frame = struct { + enclosing: ?*Frame = null, + // TODO: make this a multiarray? + locals: [255]Local, + local_count: u8 = 0, + // TODO: make this a multiarray? + upvalues: [255]UpValue, + upvalue_count: u8 = 0, + scope_depth: u32 = 0, + // If false, `return` was omitted or within a conditionned block (if, loop, etc.) + // We only count `return` emitted within the scope_depth 0 of the current function or unconditionned else statement + function_node: Ast.Node.Index, + function: ?*obj.ObjFunction = null, + generics: ?*std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef) = null, + constants: std.ArrayList(Value), + + in_try: bool = false, + + pub fn resolveGeneric(self: Frame, name: *obj.ObjString) ?*obj.ObjTypeDef { + if (self.generics) |generics| { + if (generics.get(name)) |type_def| { + return type_def; + } + } + + return if (self.enclosing) |enclosing| + enclosing.resolveGeneric(name) + else + null; + } +}; + +pub const ObjectFrame = struct { + name: Token, + type_def: *obj.ObjTypeDef, + generics: ?*std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef) = null, +}; + +pub const ScriptImport = struct { + function: Ast.Node.Index, + globals: std.ArrayList(Global), + absolute_path: *obj.ObjString, +}; + +pub var user_library_paths: ?[][]const u8 = null; + +pub const DeclarationTerminator = enum { + Comma, + OptComma, + Semicolon, + Nothing, +}; + +pub const LoopType = enum { + While, + Do, + For, + ForEach, +}; + +pub const LoopScope = struct { + loop_type: LoopType, + loop_body_scope: usize, +}; + +pub const Precedence = enum(u8) { + None, + Assignment, // =, -=, +=, *=, /= + IsAs, // is, as? + Or, // or + And, // and + Equality, // ==, != + Comparison, // >=, <=, >, < + Term, // +, - + NullCoalescing, // ??, typeof + Bitwise, // \, &, ^ + Shift, // >>, << + Factor, // /, *, % + Unary, // +, ++, -, --, ! + Call, // call(), dot.ref, sub[script], optUnwrap? + Primary, // literal, (grouped expression), identifier, [, alist], {, ...} +}; + +pub const Error = error{ + BuzzNoDll, + ImportError, + CantCompile, + UnwrappedNull, + OutOfBound, +} || std.mem.Allocator.Error || std.fmt.BufPrintError || CompileError; + +const ParseFn = *const fn (*Self, bool) Error!Ast.Node.Index; +const InfixParseFn = *const fn (*Self, bool, Ast.Node.Index) Error!Ast.Node.Index; + +const ParseRule = struct { + prefix: ?ParseFn, + infix: ?InfixParseFn, + precedence: Precedence, +}; + +const search_paths = [_][]const u8{ + "./?.!", + "./?/main.!", + "./?/src/main.!", + "./?/src/?.!", + "/usr/share/buzz/?.!", + "/usr/share/buzz/?/main.!", + "/usr/share/buzz/?/src/main.!", + "/usr/local/share/buzz/?/src/?.!", + "/usr/local/share/buzz/?.!", + "/usr/local/share/buzz/?/main.!", + "/usr/local/share/buzz/?/src/main.!", + "/usr/local/share/buzz/?/src/?.!", + "$/?.!", + "$/?/main.!", + "$/?/src/?.!", + "$/?/src/main.!", +}; + +const lib_search_paths = [_][]const u8{ + "./lib?.!", + "./?/src/lib?.!", + "/usr/share/buzz/lib?.!", + "/usr/share/buzz/?/src/lib?.!", + "/usr/share/local/buzz/lib?.!", + "/usr/share/local/buzz/?/src/lib?.!", + "$/lib?.!", + "$/?/src/lib?.!", +}; + +const zdef_search_paths = [_][]const u8{ + "./?.!", + "/usr/lib/?.!", + "/usr/local/lib/?.!", + "./lib?.!", + "/usr/lib/lib?.!", + "/usr/local/lib/lib?.!", +}; + +const rules = [_]ParseRule{ + .{ .prefix = null, .infix = null, .precedence = .None }, // Pipe + .{ .prefix = list, .infix = subscript, .precedence = .Call }, // LeftBracket + .{ .prefix = null, .infix = null, .precedence = .None }, // RightBracket + .{ .prefix = grouping, .infix = call, .precedence = .Call }, // LeftParen + .{ .prefix = null, .infix = null, .precedence = .None }, // RightParen + .{ .prefix = map, .infix = objectInit, .precedence = .Primary }, // LeftBrace + .{ .prefix = null, .infix = null, .precedence = .None }, // RightBrace + .{ .prefix = anonymousObjectInit, .infix = dot, .precedence = .Call }, // Dot + .{ .prefix = null, .infix = null, .precedence = .None }, // Comma + .{ .prefix = null, .infix = null, .precedence = .None }, // Semicolon + .{ .prefix = null, .infix = binary, .precedence = .Comparison }, // Greater + .{ .prefix = typeExpression, .infix = binary, .precedence = .Comparison }, // Less + .{ .prefix = null, .infix = binary, .precedence = .Term }, // Plus + .{ .prefix = unary, .infix = binary, .precedence = .Term }, // Minus + .{ .prefix = null, .infix = binary, .precedence = .Factor }, // Star + .{ .prefix = null, .infix = binary, .precedence = .Factor }, // Slash + .{ .prefix = null, .infix = binary, .precedence = .Factor }, // Percent + .{ .prefix = null, .infix = gracefulUnwrap, .precedence = .Call }, // Question + .{ .prefix = unary, .infix = forceUnwrap, .precedence = .Call }, // Bang + .{ .prefix = null, .infix = null, .precedence = .None }, // Colon + .{ .prefix = null, .infix = genericResolve, .precedence = .Call }, // DoubleColon + .{ .prefix = null, .infix = null, .precedence = .None }, // Equal + .{ .prefix = null, .infix = binary, .precedence = .Equality }, // EqualEqual + .{ .prefix = null, .infix = binary, .precedence = .Equality }, // BangEqual + .{ .prefix = null, .infix = null, .precedence = .None }, // BangGreater + .{ .prefix = null, .infix = binary, .precedence = .Comparison }, // GreaterEqual + .{ .prefix = null, .infix = binary, .precedence = .Comparison }, // LessEqual + .{ .prefix = null, .infix = binary, .precedence = .NullCoalescing }, // QuestionQuestion + .{ .prefix = null, .infix = null, .precedence = .None }, // Arrow + .{ .prefix = literal, .infix = null, .precedence = .None }, // True + .{ .prefix = literal, .infix = null, .precedence = .None }, // False + .{ .prefix = literal, .infix = null, .precedence = .None }, // Null + .{ .prefix = null, .infix = null, .precedence = .None }, // Str + .{ .prefix = null, .infix = null, .precedence = .None }, // Ud + .{ .prefix = null, .infix = null, .precedence = .None }, // Int + .{ .prefix = null, .infix = null, .precedence = .None }, // Float + .{ .prefix = null, .infix = null, .precedence = .None }, // Type + .{ .prefix = null, .infix = null, .precedence = .None }, // Bool + .{ .prefix = null, .infix = null, .precedence = .None }, // Function + .{ .prefix = null, .infix = binary, .precedence = .Shift }, // ShiftRight + .{ .prefix = null, .infix = binary, .precedence = .Shift }, // ShiftLeft + .{ .prefix = null, .infix = binary, .precedence = .Bitwise }, // Xor + .{ .prefix = null, .infix = binary, .precedence = .Bitwise }, // Bor + .{ .prefix = unary, .infix = null, .precedence = .Term }, // Bnot + .{ .prefix = null, .infix = @"or", .precedence = .Or }, // Or + .{ .prefix = null, .infix = @"and", .precedence = .And }, // And + .{ .prefix = null, .infix = null, .precedence = .None }, // Return + .{ .prefix = inlineIf, .infix = null, .precedence = .None }, // If + .{ .prefix = null, .infix = null, .precedence = .None }, // Else + .{ .prefix = null, .infix = null, .precedence = .None }, // Do + .{ .prefix = null, .infix = null, .precedence = .None }, // Until + .{ .prefix = null, .infix = null, .precedence = .None }, // While + .{ .prefix = null, .infix = null, .precedence = .None }, // For + .{ .prefix = null, .infix = null, .precedence = .None }, // ForEach + .{ .prefix = null, .infix = null, .precedence = .None }, // Switch + .{ .prefix = null, .infix = null, .precedence = .None }, // Break + .{ .prefix = null, .infix = null, .precedence = .None }, // Continue + .{ .prefix = null, .infix = null, .precedence = .None }, // Default + .{ .prefix = null, .infix = null, .precedence = .None }, // In + .{ .prefix = null, .infix = is, .precedence = .IsAs }, // Is + .{ .prefix = literal, .infix = null, .precedence = .None }, // Integer + .{ .prefix = literal, .infix = null, .precedence = .None }, // FloatValue + .{ .prefix = string, .infix = null, .precedence = .None }, // String + .{ .prefix = variable, .infix = null, .precedence = .None }, // Identifier + .{ .prefix = fun, .infix = null, .precedence = .None }, // Fun + .{ .prefix = null, .infix = null, .precedence = .None }, // Object + .{ .prefix = null, .infix = null, .precedence = .None }, // Obj + .{ .prefix = null, .infix = null, .precedence = .None }, // Protocol + .{ .prefix = null, .infix = null, .precedence = .None }, // Enum + .{ .prefix = null, .infix = null, .precedence = .None }, // Throw + .{ .prefix = null, .infix = null, .precedence = .None }, // Try + .{ .prefix = null, .infix = null, .precedence = .None }, // Catch + .{ .prefix = null, .infix = null, .precedence = .None }, // Test + .{ .prefix = null, .infix = null, .precedence = .None }, // Import + .{ .prefix = null, .infix = null, .precedence = .None }, // Export + .{ .prefix = null, .infix = null, .precedence = .None }, // Const + .{ .prefix = null, .infix = null, .precedence = .None }, // Static + .{ .prefix = null, .infix = null, .precedence = .None }, // From + .{ .prefix = null, .infix = null, .precedence = .None }, // As + .{ .prefix = null, .infix = as, .precedence = .IsAs }, // AsQuestion + .{ .prefix = null, .infix = null, .precedence = .None }, // Extern + .{ .prefix = null, .infix = null, .precedence = .None }, // Eof + .{ .prefix = null, .infix = null, .precedence = .None }, // Error + .{ .prefix = literal, .infix = null, .precedence = .None }, // Void + .{ .prefix = null, .infix = null, .precedence = .None }, // Docblock + .{ .prefix = pattern, .infix = null, .precedence = .None }, // Pattern + .{ .prefix = null, .infix = null, .precedence = .None }, // pat + .{ .prefix = null, .infix = null, .precedence = .None }, // fib + .{ .prefix = asyncCall, .infix = binary, .precedence = .Term }, // & + .{ .prefix = resumeFiber, .infix = null, .precedence = .Primary }, // resume + .{ .prefix = resolveFiber, .infix = null, .precedence = .Primary }, // resolve + .{ .prefix = yield, .infix = null, .precedence = .Primary }, // yield + .{ .prefix = null, .infix = range, .precedence = .Primary }, // .. + .{ .prefix = null, .infix = null, .precedence = .None }, // any + .{ .prefix = null, .infix = null, .precedence = .None }, // zdef + .{ .prefix = typeOfExpression, .infix = null, .precedence = .Unary }, // typeof + .{ .prefix = null, .infix = null, .precedence = .None }, // var +}; + +ast: Ast, +gc: *GarbageCollector, +scanner: ?Scanner = null, +current_token: ?Ast.TokenIndex = null, +script_name: []const u8 = undefined, +// If true the script is being imported +imported: bool = false, +// True when parsing a declaration inside an export statement +exporting: bool = false, +// Cached imported functions +imports: *std.StringHashMap(ScriptImport), +test_count: u64 = 0, +// FIXME: use SinglyLinkedList instead of heap allocated ptrs +current: ?*Frame = null, +current_object: ?ObjectFrame = null, +// TODO: make this a multiarray? +globals: std.ArrayList(Global), +flavor: RunFlavor, +ffi: FFI, +reporter: Reporter, + +// Jump to patch at end of current expression with a optional unwrapping in the middle of it +opt_jumps: ?std.ArrayList(Precedence) = null, + +pub fn init( + gc: *GarbageCollector, + imports: *std.StringHashMap(ScriptImport), + imported: bool, + flavor: RunFlavor, +) Self { + var self = Self{ + .gc = gc, + .imports = imports, + .imported = imported, + .globals = std.ArrayList(Global).init(gc.allocator), + .flavor = flavor, + .reporter = Reporter{ + .allocator = gc.allocator, + .error_prefix = "Syntax", + }, + .ffi = undefined, + .ast = Ast.init(gc.allocator), + }; + + self.ffi = FFI.init(gc); + + return self; +} + +pub fn deinit(self: *Self) void { + self.globals.deinit(); + if (self.opt_jumps) |jumps| { + jumps.deinit(); + } + self.ffi.deinit(); +} + +inline fn reportErrorAtCurrent(self: *Self, error_type: Reporter.Error, message: []const u8) void { + self.reporter.reportErrorAt(error_type, self.ast.tokens.get(self.current_token.?), message); +} + +pub inline fn reportError(self: *Self, error_type: Reporter.Error, message: []const u8) void { + self.reporter.reportErrorAt(error_type, self.ast.tokens.get(self.current_token.? - 1), message); +} + +inline fn reportErrorFmt(self: *Self, error_type: Reporter.Error, comptime fmt: []const u8, args: anytype) void { + self.reporter.reportErrorFmt(error_type, self.ast.tokens.get(self.current_token.? - 1), fmt, args); +} + +pub fn advance(self: *Self) !void { + if (self.current_token != null and self.ast.tokens.items(.tag)[self.current_token.?] == .Eof) { + return; + } + + self.current_token = if (self.current_token) |ct| ct + 1 else 0; + + if (self.current_token.? >= self.ast.tokens.len) { + while (true) { + const new_token = try self.scanner.?.scanToken(); + + if (new_token.tag == .Error) { + self.current_token = if (self.current_token) |ct| ct - 1 else 0; + self.reportErrorAtCurrent( + .unknown, + new_token.literal_string orelse "Unknown error.", + ); + } + + _ = try self.ast.appendToken(new_token); + + if (new_token.tag != .Error or new_token.tag == .Eof) { + break; + } + } + } +} + +// Used when parsing several script one after the other so that Eof doesn't stop parsing +fn advancePastEof(self: *Self) !void { + self.current_token = if (self.current_token) |ct| ct + 1 else 0; + + if (self.current_token.? >= self.ast.tokens.len) { + while (true) { + const new_token = try self.scanner.?.scanToken(); + + if (new_token.tag == .Error) { + self.current_token = if (self.current_token) |ct| ct - 1 else 0; + self.reportErrorAtCurrent( + .unknown, + new_token.literal_string orelse "Unknown error.", + ); + } + + _ = try self.ast.appendToken(new_token); + + if (new_token.tag != .Error) { + break; + } + } + } +} + +pub fn consume(self: *Self, tag: Token.Type, message: []const u8) !void { + if (self.ast.tokens.items(.tag)[self.current_token.?] == tag) { + try self.advance(); + return; + } + + self.reportErrorAtCurrent(.syntax, message); +} + +// Check next token +fn check(self: *Self, tag: Token.Type) bool { + return self.ast.tokens.items(.tag)[self.current_token.?] == tag; +} + +// Check `n` tokens ahead +fn checkAhead(self: *Self, tag: Token.Type, n: usize) !bool { + // Parse tokens if we didn't already look that far ahead + while (n + 1 > self.ast.tokens.len - self.current_token.? - 1) { + while (true) { + const token = try self.scanner.?.scanToken(); + _ = try self.ast.appendToken(token); + + if (token.tag == .Eof) { + return false; + } + + if (token.tag != .Error) { + break; + } + + // If error, report it and keep checking ahead + self.reportErrorAtCurrent(.syntax, token.literal_string orelse "Unknown error."); + } + } + + return self.ast.tokens.items(.tag)[self.current_token.? + n + 1] == tag; +} + +// Check for a sequence ahead, a null in the sequence means continue until next token in the sequence is found +// Don't use null if you're not sure the following token **must** be found, otherwise it'll check the whole source +// Right now the null is only used to parse ahead a generic object type like `Person:: identifier` +fn checkSequenceAhead(self: *Self, sequence: []const ?Token.Type, limit: usize) !bool { + std.debug.assert(sequence.len > 0); + + if (!self.check(sequence[0].?)) { + return false; + } + + var i: usize = 0; + for (sequence[1..], 1..) |tag, n| { + // Avoid going to far + if (i > limit) { + return false; + } + + if (tag) |tt| { + if (!try self.checkAhead(tt, i)) { + return false; + } + + i += 1; + } else { + // Advance until next token + std.debug.assert(n < sequence.len - 1 and sequence[n + 1] != null); // There must be at least one more token in the sequence + const next_token = sequence[n + 1].?; + + while (!try self.checkAhead(next_token, i)) : (i += 1) { + // Avoid looping forever if found EOF or Error + const last = self.ast.tokens.items(.tag)[self.ast.tokens.len - 1]; + if (last == .Eof or last == .Error) { + return false; + } + } + } + } + + return true; +} + +fn match(self: *Self, tag: Token.Type) !bool { + if (!self.check(tag)) { + return false; + } + + try self.advance(); + + return true; +} + +// Insert token in ast and advance over it to avoid confusing the parser +fn insertUtilityToken(self: *Self, token: Token) !Ast.TokenIndex { + const current_token = self.ast.tokens.get(self.current_token.?); + + try self.ast.tokens.insert( + self.gc.allocator, + self.current_token.?, + token, + ); + + const utility_token = self.current_token.?; + + self.current_token = try self.ast.appendToken(current_token); + + return utility_token; +} + +// Skip tokens until we reach something that resembles a new statement +fn synchronize(self: *Self) !void { + self.reporter.panic_mode = false; + + while (self.ast.tokens.items(.tag)[self.current_token.?] != .Eof) : (try self.advance()) { + if (self.ast.tokens.items(.tag)[self.current_token.? - 1] == .Semicolon) { + return; + } + + switch (self.ast.tokens.items(.tag)[self.current_token.?]) { + .Object, + .Enum, + .Test, + .Fun, + .Const, + .If, + .While, + .Do, + .For, + .ForEach, + .Return, + .Switch, + .Throw, + .Break, + .Continue, + .Export, + .Import, + .Zdef, + => return, + else => {}, + } + } +} + +pub fn parse(self: *Self, source: []const u8, file_name: []const u8) !?Ast { + if (self.scanner != null) { + self.scanner = null; + } + + self.scanner = Scanner.init(self.gc.allocator, file_name, source); + + const function_type: obj.ObjFunction.FunctionType = if (!self.imported and self.flavor == .Repl) + .Repl + else if (self.imported) + .Script + else + .ScriptEntryPoint; + + const function_name: []const u8 = switch (function_type) { + .EntryPoint => "main", + .ScriptEntryPoint, .Script => file_name, + .Repl => "REPL", + else => "???", + }; + + const function_def = obj.ObjFunction.FunctionDef{ + .id = obj.ObjFunction.FunctionDef.nextId(), + .name = try self.gc.copyString(function_name), + .script_name = try self.gc.copyString(file_name), + .return_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), + .yield_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), + .parameters = std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef).init(self.gc.allocator), + .defaults = std.AutoArrayHashMap(*obj.ObjString, Value).init(self.gc.allocator), + .function_type = function_type, + .generic_types = std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef).init(self.gc.allocator), + }; + + const function_type_def = obj.ObjTypeDef.TypeUnion{ .Function = function_def }; + + const body_node = try self.ast.appendNode( + .{ + .tag = .Block, + .location = 0, + .end_location = undefined, + .components = .{ + .Block = try self.gc.allocator.alloc(Ast.Node.Index, 0), + }, + }, + ); + + const function_node = try self.ast.appendNode( + .{ + .tag = .Function, + .location = 0, + .end_location = undefined, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Function, + .resolved_type = function_type_def, + }, + ), + .components = .{ + .Function = .{ + .function_signature = null, + .id = Ast.Function.nextId(), + .upvalue_binding = std.AutoArrayHashMap(u8, bool).init(self.gc.allocator), + .body = body_node, + }, + }, + }, + ); + + var entry = Ast.Function.Entry{ + .test_slots = undefined, + .test_locations = undefined, + }; + + self.script_name = file_name; + + try self.beginFrame(function_type, function_node, null); + + self.reporter.had_error = false; + self.reporter.panic_mode = false; + + try self.advancePastEof(); + + while (!(try self.match(.Eof))) { + if (function_type == .Repl) { + // When running in REPL, global scope is allowed to run statements since the global scope becomes procedural + if (self.declarationOrStatement(null) catch |err| { + if (BuildOptions.debug) { + std.debug.print("Parsing failed with error {}\n", .{err}); + } + return null; + }) |decl| { + var statements = std.ArrayList(Ast.Node.Index).fromOwnedSlice( + self.gc.allocator, + self.ast.nodes.items(.components)[body_node].Block, + ); + defer statements.shrinkAndFree(statements.items.len); + + try statements.append(decl); + + self.ast.nodes.items(.components)[body_node].Block = statements.items; + } + } else { + if (self.declaration() catch |err| { + if (BuildOptions.debug) { + std.debug.print("Parsing failed with error {}\n", .{err}); + } + return null; + }) |decl| { + var statements = std.ArrayList(Ast.Node.Index).fromOwnedSlice( + self.gc.allocator, + self.ast.nodes.items(.components)[body_node].Block, + ); + defer statements.shrinkAndFree(statements.items.len); + + try statements.append(decl); + + self.ast.nodes.items(.components)[body_node].Block = statements.items; + } else { + self.reportError(.syntax, "Expected statement"); + break; + } + } + } + + // If top level, search `main` or `test` function(s) and call them + // Then put any exported globals on the stack + if (function_type == .ScriptEntryPoint) { + for (self.globals.items, 0..) |global, index| { + if (std.mem.eql(u8, global.name.string, "main") and !global.hidden and global.prefix == null) { + entry.main_slot = index; + entry.main_location = global.location; + break; + } + } + } + + var test_slots = std.ArrayList(usize).init(self.gc.allocator); + var test_locations = std.ArrayList(Ast.TokenIndex).init(self.gc.allocator); + // Create an entry point wich runs all `test` + for (self.globals.items, 0..) |global, index| { + if (global.type_def.def_type == .Function and global.type_def.resolved_type.?.Function.function_type == .Test) { + try test_slots.append(index); + try test_locations.append(global.location); + } + } + + test_slots.shrinkAndFree(test_slots.items.len); + test_locations.shrinkAndFree(test_locations.items.len); + entry.test_slots = test_slots.items; + entry.test_locations = test_locations.items; + + // If we're being imported, put all globals on the stack + if (self.imported) { + entry.exported_count = self.globals.items.len; + } + + // Check there's no more root placeholders + for (self.globals.items) |global| { + if (global.type_def.def_type == .Placeholder) { + self.reporter.reportPlaceholder(self.ast, global.type_def.resolved_type.?.Placeholder); + } + } + + self.ast.nodes.items(.components)[function_node].Function.entry = entry; + + self.ast.root = if (self.reporter.had_error) null else self.endFrame(); + + return if (self.reporter.had_error) null else self.ast; +} + +fn beginFrame(self: *Self, function_type: obj.ObjFunction.FunctionType, function_node: Ast.Node.Index, this: ?*obj.ObjTypeDef) !void { + const enclosing = self.current; + // FIXME: is this ever deallocated? + self.current = try self.gc.allocator.create(Frame); + self.current.?.* = Frame{ + .locals = [_]Local{undefined} ** 255, + .upvalues = [_]UpValue{undefined} ** 255, + .enclosing = enclosing, + .function_node = function_node, + .constants = std.ArrayList(Value).init(self.gc.allocator), + }; + + if (function_type == .Extern) { + return; + } + + // First local is reserved for an eventual `this` or cli arguments + var local: *Local = &self.current.?.locals[self.current.?.local_count]; + self.current.?.local_count += 1; + local.depth = 0; + local.is_captured = false; + + switch (function_type) { + .Method => { + std.debug.assert(this != null); + + local.type_def = this.?; + }, + .EntryPoint, .ScriptEntryPoint => { + // `args` is [str] + const list_def = obj.ObjList.ListDef.init( + self.gc.allocator, + try self.gc.type_registry.getTypeDef(.{ .def_type = .String }), + ); + + const list_union: obj.ObjTypeDef.TypeUnion = .{ .List = list_def }; + + local.type_def = try self.gc.type_registry.getTypeDef( + obj.ObjTypeDef{ + .def_type = .List, + .resolved_type = list_union, + }, + ); + }, + else => { + local.type_def = try self.gc.type_registry.getTypeDef( + obj.ObjTypeDef{ + .def_type = .Void, + }, + ); + }, + } + + const name: []const u8 = switch (function_type) { + .Method => "this", + .EntryPoint => "$args", + .ScriptEntryPoint => "$args", + else => "_", + }; + + local.name = try self.gc.copyString(name); +} + +fn endFrame(self: *Self) Ast.Node.Index { + var i: usize = 0; + while (i < self.current.?.local_count) : (i += 1) { + const local = self.current.?.locals[i]; + + // Check discarded locals + if (self.flavor != .Repl and !local.isReferenced()) { + const type_def_str = local.type_def.toStringAlloc(self.gc.allocator) catch unreachable; + defer type_def_str.deinit(); + + self.reporter.warnFmt( + .unused_argument, + self.ast.tokens.get(local.location), + "Unused local of type `{s}`", + .{ + type_def_str.items, + }, + ); + } + } + + // If global scope, check unused globals + const function_type = self.ast.nodes.items(.type_def)[self.current.?.function_node].?.resolved_type.?.Function.function_type; + if (function_type == .Script or function_type == .ScriptEntryPoint) { + for (self.globals.items) |global| { + if (!global.isReferenced()) { + const type_def_str = global.type_def.toStringAlloc(self.gc.allocator) catch unreachable; + defer type_def_str.deinit(); + + self.reporter.warnFmt( + .unused_argument, + self.ast.tokens.get(global.location), + "Unused global of type `{s}`", + .{ + type_def_str.items, + }, + ); + } + } + } + + const current_node = self.current.?.function_node; + self.current = self.current.?.enclosing; + + return current_node; +} + +fn beginScope(self: *Self) void { + self.current.?.scope_depth += 1; +} + +fn endScope(self: *Self) ![]Chunk.OpCode { + const current = self.current.?; + var closing = std.ArrayList(Chunk.OpCode).init(self.gc.allocator); + current.scope_depth -= 1; + + while (current.local_count > 0 and current.locals[current.local_count - 1].depth > current.scope_depth) { + const local = current.locals[current.local_count - 1]; + + if (local.is_captured) { + try closing.append(.OP_CLOSE_UPVALUE); + } else { + try closing.append(.OP_POP); + } + + // Check discarded locals + if (self.flavor != .Repl and !local.isReferenced()) { + const type_def_str = local.type_def.toStringAlloc(self.gc.allocator) catch unreachable; + defer type_def_str.deinit(); + + self.reporter.warnFmt( + .unused_argument, + self.ast.tokens.get(local.location), + "Unused local of type `{s}`", + .{ + type_def_str.items, + }, + ); + } + + current.local_count -= 1; + } + + closing.shrinkAndFree(closing.items.len); + return closing.items; +} + +fn closeScope(self: *Self, upto_depth: usize) ![]Chunk.OpCode { + const current = self.current.?; + var closing = std.ArrayList(Chunk.OpCode).init(self.gc.allocator); + + var local_count = current.local_count; + while (local_count > 0 and current.locals[local_count - 1].depth > upto_depth - 1) { + if (current.locals[local_count - 1].is_captured) { + try closing.append(.OP_CLOSE_UPVALUE); + } else { + try closing.append(.OP_POP); + } + + local_count -= 1; + } + + closing.shrinkAndFree(closing.items.len); + return closing.items; +} + +inline fn getRule(token: Token.Type) ParseRule { + return rules[@intFromEnum(token)]; +} + +fn parsePrecedence(self: *Self, precedence: Precedence, hanging: bool) Error!Ast.Node.Index { + // In case we are already parsing an expression, the current unwrap chain should not impact deeper expressions + // Exemple: canBeNull?.aMap[expression] <- here `expression` should not be transformed into an optional + const previous_opt_jumps = self.opt_jumps; + self.opt_jumps = null; + + // If hanging is true, that means we already read the start of the expression + if (!hanging) { + _ = try self.advance(); + } + + const prefixRule: ?ParseFn = getRule(self.ast.tokens.items(.tag)[self.current_token.? - 1]).prefix; + if (prefixRule == null) { + self.reportError(.syntax, "Expected expression."); + + // TODO: find a way to continue or catch that error + return CompileError.Unrecoverable; + } + + const canAssign: bool = @intFromEnum(precedence) <= @intFromEnum(Precedence.Assignment); + var node = try prefixRule.?(self, canAssign); + + while (@intFromEnum(getRule(self.ast.tokens.items(.tag)[self.current_token.?]).precedence) >= @intFromEnum(precedence)) { + // Patch optional jumps + if (self.opt_jumps) |jumps| { + std.debug.assert(jumps.items.len > 0); + const first_jump: Precedence = jumps.items[0]; + + if (@intFromEnum(getRule(self.ast.tokens.items(.tag)[self.current_token.?]).precedence) < @intFromEnum(first_jump)) { + jumps.deinit(); + self.opt_jumps = null; + + self.ast.nodes.items(.patch_opt_jumps)[node] = true; + + const node_type_def_ptr = &self.ast.nodes.items(.type_def)[node]; + if (node_type_def_ptr.* != null) { + node_type_def_ptr.* = try node_type_def_ptr.*.?.cloneOptional(&self.gc.type_registry); + } + } + } + + _ = try self.advance(); + + const infixRule: InfixParseFn = getRule(self.ast.tokens.items(.tag)[self.current_token.? - 1]).infix.?; + node = try infixRule(self, canAssign, node); + } + + if (self.opt_jumps) |jumps| { + jumps.deinit(); + self.opt_jumps = null; + + self.ast.nodes.items(.patch_opt_jumps)[node] = true; + + const node_type_def = self.ast.nodes.items(.type_def)[node]; + if (node_type_def != null) { + self.ast.nodes.items(.type_def)[node] = try node_type_def.?.cloneOptional(&self.gc.type_registry); + } + } + + if (canAssign and (try self.match(.Equal))) { + self.reportError(.assignable, "Invalid assignment target."); + } + + self.opt_jumps = previous_opt_jumps; + + return node; +} + +// Returns a list of break jumps to patch +fn block(self: *Self, loop_scope: ?LoopScope) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + var statements = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer statements.shrinkAndFree(statements.items.len); + while (!self.check(.RightBrace) and !self.check(.Eof)) { + if (try self.declarationOrStatement(loop_scope)) |declOrStmt| { + try statements.append(declOrStmt); + } + } + + try self.consume(.RightBrace, "Expected `}}` after block."); + + return try self.ast.appendNode( + .{ + .tag = .Block, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = null, + .components = .{ + .Block = statements.items, + }, + }, + ); +} + +fn simpleType(self: *Self, def_type: obj.ObjTypeDef.Type) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + const optional = try self.match(.Question); + + return try self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .def_type = def_type, + .optional = optional, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); +} + +fn declaration(self: *Self) Error!?Ast.Node.Index { + const global_scope = self.current.?.scope_depth == 0; + + const docblock = if (global_scope and try self.match(.Docblock)) + self.current_token.? - 1 + else + null; + + if (try self.match(.Extern)) { + const node = try self.funDeclaration(); + + self.ast.nodes.items(.docblock)[node] = docblock; + + return node; + } else if ((self.current_token == 0 or self.ast.tokens.items(.tag)[self.current_token.? - 1] != .Export) and try self.match(.Export)) { + return try self.exportStatement(); + } else { + const constant: bool = try self.match(.Const); + + var node = if (global_scope and !constant and try self.match(.Object)) + try self.objectDeclaration() + else if (global_scope and !constant and try self.match(.Protocol)) + try self.protocolDeclaration() + else if (global_scope and !constant and try self.match(.Enum)) + try self.enumDeclaration() + else if (!constant and try self.match(.Fun)) + try self.funDeclaration() + else if (!constant and try self.match(.Var)) + try self.varDeclaration( + false, + null, + .Semicolon, + false, + true, + ) + else if (try self.match(.Pat)) + try self.varDeclaration( + false, + try self.simpleType(.Pattern), + .Semicolon, + constant, + true, + ) + else if (try self.match(.Ud)) + try self.varDeclaration( + false, + try self.simpleType(.UserData), + .Semicolon, + constant, + true, + ) + else if (try self.match(.Str)) + try self.varDeclaration( + false, + try self.simpleType(.String), + .Semicolon, + constant, + true, + ) + else if (try self.match(.Int)) + try self.varDeclaration( + false, + try self.simpleType(.Integer), + .Semicolon, + constant, + true, + ) + else if (try self.match(.Float)) + try self.varDeclaration( + false, + try self.simpleType(.Float), + .Semicolon, + constant, + true, + ) + else if (try self.match(.Bool)) + try self.varDeclaration( + false, + try self.simpleType(.Bool), + .Semicolon, + constant, + true, + ) + else if (try self.match(.Type)) + try self.varDeclaration( + false, + try self.simpleType(.Type), + .Semicolon, + constant, + true, + ) + else if (try self.match(.Any)) + try self.varDeclaration( + false, + try self.simpleType(.Any), + .Semicolon, + constant, + true, + ) + // zig fmt: off + else if (self.current_token.? > 0 and self.current_token.? - 1 < self.ast.tokens.len - 1 + and self.ast.tokens.items(.tag)[self.current_token.?] == .Identifier + and self.ast.tokens.items(.lexeme)[self.current_token.?].len == 1 + and self.ast.tokens.items(.lexeme)[self.current_token.?][0] == '_') + // zig fmt: on + try self.varDeclaration( + false, + try self.simpleType(.Any), + .Semicolon, + constant, + true, + ) + else if (try self.match(.Fib)) + try self.varDeclaration( + false, + try self.parseFiberType(null), + .Semicolon, + constant, + true, + ) + else if (try self.match(.Obj)) + try self.varDeclaration( + false, + try self.parseObjType(null), + .Semicolon, + constant, + true, + ) + else if (try self.match(.LeftBracket)) + try self.listDeclaration(constant) + else if (try self.match(.LeftBrace)) + try self.mapDeclaration(constant) + else if (!constant and try self.match(.Test)) + try self.testStatement() + else if (try self.match(.Function)) + try self.varDeclaration( + false, + try self.parseFunctionType(null), + .Semicolon, + constant, + true, + ) + else if (global_scope and try self.match(.Import)) + try self.importStatement() + else if (global_scope and try self.match(.Zdef)) + try self.zdefStatement() + else if (global_scope and !constant and try self.match(.Export)) + try self.exportStatement() + else if (self.check(.Identifier)) user_decl: { + if ( // zig fmt: off + // As of now this is the only place where we need to check more than one token ahead + // Note that we would not have to do this if type were given **after** the identifier. But changing this is a pretty big left turn. + // `Type variable` + try self.checkSequenceAhead(&[_]?Token.Type{ .Identifier, .Identifier }, 2) + // `prefix.Type variable` + or try self.checkSequenceAhead(&[_]?Token.Type{ .Identifier, .Dot, .Identifier, .Identifier }, 4) + // `prefix.Type? variable` + or try self.checkSequenceAhead(&[_]?Token.Type{ .Identifier, .Dot, .Identifier, .Question, .Identifier }, 4) + // `Type? variable` + or try self.checkSequenceAhead(&[_]?Token.Type{ .Identifier, .Question, .Identifier }, 3) + // `Type::<...> variable` + or try self.checkSequenceAhead(&[_]?Token.Type{ .Identifier, .DoubleColon, .Less, null, .Greater, .Identifier }, 255 * 2) + // - Type::<...>? variable + or try self.checkSequenceAhead(&[_]?Token.Type{ .Identifier, .DoubleColon, .Less, null, .Greater, .Question, .Identifier }, 255 * 2) + // - prefix.Type::<...> variable + or try self.checkSequenceAhead(&[_]?Token.Type{ .Identifier, .Dot, .Identifier, .DoubleColon, .Less, null, .Greater, .Identifier }, 255 * 2) + // - prefix.Type::<...>? variable + or try self.checkSequenceAhead(&[_]?Token.Type{ .Identifier, .Dot, .Identifier, .DoubleColon, .Less, null, .Greater, .Question, .Identifier }, 255 * 2) + ) { + // zig fmt: on + _ = try self.advance(); // consume first identifier + break :user_decl try self.userVarDeclaration(false, constant); + } + + break :user_decl null; + } else if (global_scope and !constant and try self.match(.Export)) + try self.exportStatement() + else + null; + + if (node == null and constant) { + node = try self.varDeclaration( + false, + null, + .Semicolon, + true, + true, + ); + } + + if (node != null and docblock != null) { + if (self.ast.nodes.items(.tag)[node.?] == .FunDeclaration) { + const components = self.ast.nodes.items(.components); + components[components[node.?].FunDeclaration.function].Function.docblock = docblock; + } + self.ast.nodes.items(.docblock)[node.?] = docblock; + } + + if (self.reporter.panic_mode) { + try self.synchronize(); + } + + return node; + } +} +fn declarationOrStatement(self: *Self, loop_scope: ?LoopScope) !?Ast.Node.Index { + return try self.declaration() orelse try self.statement(false, loop_scope); +} + +// When a break statement, will return index of jump to patch +fn statement(self: *Self, hanging: bool, loop_scope: ?LoopScope) !?Ast.Node.Index { + if (try self.match(.If)) { + std.debug.assert(!hanging); + return try self.ifStatement(loop_scope); + } else if (try self.match(.For)) { + std.debug.assert(!hanging); + return try self.forStatement(); + } else if (try self.match(.ForEach)) { + std.debug.assert(!hanging); + return try self.forEachStatement(); + } else if (try self.match(.While)) { + std.debug.assert(!hanging); + return try self.whileStatement(); + } else if (try self.match(.Do)) { + std.debug.assert(!hanging); + return try self.doUntilStatement(); + } else if (try self.match(.Return)) { + std.debug.assert(!hanging); + return try self.returnStatement(); + } else if (try self.match(.Try)) { + std.debug.assert(!hanging); + return try self.tryStatement(); + } else if (try self.match(.Break)) { + std.debug.assert(!hanging); + return try self.breakStatement(loop_scope); + } else if (try self.match(.Continue)) { + std.debug.assert(!hanging); + return try self.continueStatement(loop_scope); + } else if (try self.match(.Import)) { + std.debug.assert(!hanging); + return try self.importStatement(); + } else if (try self.match(.Throw)) { + const start_location = self.current_token.? - 1; + // For now we don't care about the type. Later if we have `Error` type of data, we'll type check this + const error_value = try self.expression(false); + + try self.consume(.Semicolon, "Expected `;` after `throw` expression."); + + return try self.ast.appendNode( + .{ + .tag = .Throw, + .location = start_location, + .end_location = self.current_token.? - 1, + .components = .{ + .Throw = .{ + .expression = error_value, + .unconditional = self.current.?.scope_depth == 1, + }, + }, + }, + ); + } else { + return try self.expressionStatement(hanging); + } + + return null; +} + +fn addLocal(self: *Self, name: Ast.TokenIndex, local_type: *obj.ObjTypeDef, constant: bool) Error!usize { + if (self.current.?.local_count == 255) { + self.reportError(.locals_count, "Too many local variables in scope."); + return 0; + } + + const name_lexeme = self.ast.tokens.items(.lexeme)[name]; + const function_type = self.ast.nodes.items(.type_def)[self.current.?.function_node].?.resolved_type.?.Function.function_type; + self.current.?.locals[self.current.?.local_count] = Local{ + .name = try self.gc.copyString(name_lexeme), + .name_token = name, + .location = name, + .depth = -1, + .is_captured = false, + .type_def = local_type, + .constant = constant, + // Extern and abstract function arguments are considered referenced + .referenced = function_type == .Extern or function_type == .Abstract, + }; + + self.current.?.local_count += 1; + + return self.current.?.local_count - 1; +} + +fn addGlobal(self: *Self, name: Ast.TokenIndex, global_type: *obj.ObjTypeDef, constant: bool) Error!usize { + const lexemes = self.ast.tokens.items(.lexeme); + + // Search for an existing placeholder global with the same name + for (self.globals.items, 0..) |*global, index| { + if (global.type_def.def_type == .Placeholder and global.type_def.resolved_type.?.Placeholder.name != null and std.mem.eql(u8, lexemes[name], global.name.string)) { + global.exported = self.exporting; + + if (global_type.def_type != .Placeholder) { + try self.resolvePlaceholder(global.type_def, global_type, constant); + } + + return index; + } + } + + if (self.globals.items.len == std.math.maxInt(u24)) { + self.reportError(.globals_count, "Too many global variables."); + return 0; + } + + try self.globals.append( + Global{ + .name_token = name, + .name = try self.gc.copyString(lexemes[name]), + .location = name, + .type_def = global_type, + .constant = constant, + .exported = self.exporting, + }, + ); + + return self.globals.items.len - 1; +} + +fn resolveGeneric(self: *Self, name: *obj.ObjString) ?*obj.ObjTypeDef { + return if (self.current_object != null and self.current_object.?.generics != null) + self.current_object.?.generics.?.get(name) orelse self.current.?.resolveGeneric(name) + else + self.current.?.resolveGeneric(name); +} + +fn resolveLocal(self: *Self, frame: *Frame, name: Ast.TokenIndex) !?usize { + if (frame.local_count == 0) { + return null; + } + + const lexeme = self.ast.tokens.items(.lexeme)[name]; + + if (std.mem.eql(u8, lexeme, "_")) { + return null; + } + + var i: usize = frame.local_count - 1; + while (i >= 0) : (i -= 1) { + var local: *Local = &frame.locals[i]; + if (std.mem.eql(u8, lexeme, local.name.string)) { + if (local.depth == -1) { + self.reportErrorFmt( + .local_initializer, + "Can't read local variable `{s}` in its own initializer.", + .{lexeme}, + ); + } + + local.referenced = true; + return i; + } + + if (i == 0) break; + } + + return null; +} + +// Will consume tokens if find a prefixed identifier +pub fn resolveGlobal(self: *Self, prefix: ?[]const u8, name: []const u8) Error!?usize { + if (self.globals.items.len == 0) { + return null; + } + + if (std.mem.eql(u8, name, "_")) { + return null; + } + + var i: usize = self.globals.items.len - 1; + while (i >= 0) : (i -= 1) { + const global: *Global = &self.globals.items[i]; + if (((prefix == null and global.prefix == null) or (prefix != null and global.prefix != null and std.mem.eql(u8, prefix.?, global.prefix.?))) and std.mem.eql(u8, name, global.name.string) and !global.hidden) { + if (!global.initialized) { + self.reportErrorFmt( + .global_initializer, + "Can't read global `{s}` variable in its own initializer.", + .{global.name.string}, + ); + } + + global.referenced = true; + + return i; + // Is it an import prefix? + } else if (global.prefix != null and std.mem.eql(u8, name, global.prefix.?)) { + try self.consume(.Dot, "Expected `.` after import prefix."); + try self.consume(.Identifier, "Expected identifier after import prefix."); + return try self.resolveGlobal(global.prefix.?, self.ast.tokens.items(.lexeme)[self.current_token.? - 1]); + } + + if (i == 0) break; + } + + return null; +} + +fn resolvePlaceholderWithRelation( + self: *Self, + child: *obj.ObjTypeDef, + resolved_type: *obj.ObjTypeDef, + constant: bool, + relation: obj.PlaceholderDef.PlaceholderRelation, +) Error!void { + const child_placeholder = child.resolved_type.?.Placeholder; + + if (BuildOptions.debug_placeholders) { + std.debug.print( + "Attempts to resolve @{} child placeholder @{} ({s}) with relation {}\n", + .{ + @intFromPtr(resolved_type), + @intFromPtr(child), + if (child_placeholder.name) |name| name.string else "unknown", + child_placeholder.parent_relation.?, + }, + ); + } + + switch (relation) { + .GenericResolve => { + try self.resolvePlaceholder( + child, + try resolved_type.populateGenerics( + self.current_token.? - 1, + switch (resolved_type.def_type) { + .Function => resolved_type.resolved_type.?.Function.id, + .Object => resolved_type.resolved_type.?.Object.id, + else => null, + }, + child_placeholder.resolved_generics.?, + &self.gc.type_registry, + null, + ), + true, + ); + }, + .Optional => { + try self.resolvePlaceholder( + child, + try resolved_type.cloneOptional(&self.gc.type_registry), + false, + ); + }, + .Unwrap => { + try self.resolvePlaceholder( + child, + try resolved_type.cloneNonOptional(&self.gc.type_registry), + false, + ); + }, + .Instance => { + try self.resolvePlaceholder( + child, + try resolved_type.toInstance(self.gc.allocator, &self.gc.type_registry), + false, + ); + }, + .Parent => { + try self.resolvePlaceholder( + child, + try resolved_type.toParentType(self.gc.allocator, &self.gc.type_registry), + false, + ); + }, + .Call => { + // Can we call the parent? + if (resolved_type.def_type != .Function) { + self.reporter.reportErrorAt( + .callable, + self.ast.tokens.get(child_placeholder.where), + "Can't be called", + ); + return; + } + + try self.resolvePlaceholder( + child, + resolved_type.resolved_type.?.Function.return_type, + false, + ); + }, + .Yield => { + // Can we call the parent? + if (resolved_type.def_type != .Function) { + self.reporter.reportErrorAt( + .callable, + self.ast.tokens.get(child_placeholder.where), + "Can't be called", + ); + return; + } + + try self.resolvePlaceholder( + child, + resolved_type.resolved_type.?.Function.yield_type, + false, + ); + }, + .Subscript => { + if (resolved_type.def_type == .List) { + try self.resolvePlaceholder(child, resolved_type.resolved_type.?.List.item_type, false); + } else if (resolved_type.def_type == .Map) { + try self.resolvePlaceholder(child, try resolved_type.resolved_type.?.Map.value_type.cloneOptional(&self.gc.type_registry), false); + } else if (resolved_type.def_type == .String) { + try self.resolvePlaceholder(child, try self.gc.type_registry.getTypeDef(.{ .def_type = .String }), false); + } else { + self.reporter.reportErrorAt( + .subscriptable, + self.ast.tokens.get(child_placeholder.where), + "Can't be subscripted", + ); + return; + } + }, + .Key => { + if (resolved_type.def_type == .Map) { + try self.resolvePlaceholder(child, resolved_type.resolved_type.?.Map.key_type, false); + } else if (resolved_type.def_type == .List or resolved_type.def_type == .String) { + try self.resolvePlaceholder(child, try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }), false); + } else { + self.reporter.reportErrorAt( + .map_key_type, + self.ast.tokens.get(child_placeholder.where), + "Can't be a key", + ); + return; + } + }, + .FieldAccess => { + switch (resolved_type.def_type) { + .List => { + std.debug.assert(child_placeholder.name != null); + + if (try obj.ObjList.ListDef.member(resolved_type, self, child_placeholder.name.?.string)) |member| { + try self.resolvePlaceholder(child, member, false); + } + }, + .Map => { + std.debug.assert(child_placeholder.name != null); + + if (try obj.ObjMap.MapDef.member(resolved_type, self, child_placeholder.name.?.string)) |member| { + try self.resolvePlaceholder(child, member, false); + } + }, + .String => { + std.debug.assert(child_placeholder.name != null); + + if (try obj.ObjString.memberDef(self, child_placeholder.name.?.string)) |member| { + try self.resolvePlaceholder(child, member, false); + } + }, + .Pattern => { + std.debug.assert(child_placeholder.name != null); + + if (try obj.ObjPattern.memberDef(self, child_placeholder.name.?.string)) |member| { + try self.resolvePlaceholder(child, member, false); + } + }, + .Fiber => { + std.debug.assert(child_placeholder.name != null); + + if (try obj.ObjFiber.memberDef(self, child_placeholder.name.?.string)) |member| { + try self.resolvePlaceholder(child, member, false); + } + }, + .Object => { + // We can't create a field access placeholder without a name + std.debug.assert(child_placeholder.name != null); + + const object_def = resolved_type.resolved_type.?.Object; + + // Search for a field matching the placeholder + if (object_def.fields.get(child_placeholder.name.?.string)) |field| { + // TODO: remove? should only resolve with a field if field accessing an object instance? + try self.resolvePlaceholder(child, field, false); + } else if (object_def.methods.get(child_placeholder.name.?.string)) |method_def| { + try self.resolvePlaceholder(child, method_def, true); + } else if (object_def.static_fields.get(child_placeholder.name.?.string)) |static_def| { + try self.resolvePlaceholder(child, static_def, false); + } else { + self.reportErrorFmt( + .property_does_not_exists, + "`{s}` has no static field `{s}`", + .{ + object_def.name.string, + child_placeholder.name.?.string, + }, + ); + } + }, + .ObjectInstance => { + // We can't create a field access placeholder without a name + std.debug.assert(child_placeholder.name != null); + + const object_def = resolved_type.resolved_type.?.ObjectInstance.resolved_type.?.Object; + + // Search for a field matching the placeholder + if (object_def.fields.get(child_placeholder.name.?.string)) |field| { + try self.resolvePlaceholder(child, field, false); + } else if (object_def.methods.get(child_placeholder.name.?.string)) |method_def| { + try self.resolvePlaceholder(child, method_def, true); + } else { + self.reportErrorFmt( + .property_does_not_exists, + "`{s}` has no field `{s}`", + .{ + object_def.name.string, + child_placeholder.name.?.string, + }, + ); + } + }, + .ForeignContainer => { + // We can't create a field access placeholder without a name + std.debug.assert(child_placeholder.name != null); + + const f_def = resolved_type.resolved_type.?.ForeignContainer; + + // Search for a field matching the placeholder + if (f_def.buzz_type.get(child_placeholder.name.?.string)) |field| { + try self.resolvePlaceholder(child, field, false); + } else { + self.reportErrorFmt( + .property_does_not_exists, + "`{s}` has no field `{s}`", + .{ + f_def.name.string, + child_placeholder.name.?.string, + }, + ); + } + }, + .ProtocolInstance => { + // We can't create a field access placeholder without a name + std.debug.assert(child_placeholder.name != null); + + const protocol_def = resolved_type.resolved_type.?.ProtocolInstance.resolved_type.?.Protocol; + + // Search for a field matching the placeholder + if (protocol_def.methods.get(child_placeholder.name.?.string)) |method_def| { + try self.resolvePlaceholder(child, method_def, true); + } else { + self.reportErrorFmt( + .property_does_not_exists, + "`{s}` has no method `{s}`", + .{ + protocol_def.name.string, + child_placeholder.name.?.string, + }, + ); + } + }, + .Enum => { + // We can't create a field access placeholder without a name + std.debug.assert(child_placeholder.name != null); + + const enum_def = resolved_type.resolved_type.?.Enum; + + // Search for a case matching the placeholder + for (enum_def.cases.items) |case| { + if (std.mem.eql(u8, case, child_placeholder.name.?.string)) { + const enum_instance_def: obj.ObjTypeDef.TypeUnion = .{ .EnumInstance = resolved_type }; + + try self.resolvePlaceholder(child, try self.gc.type_registry.getTypeDef(.{ + .def_type = .EnumInstance, + .resolved_type = enum_instance_def, + }), true); + break; + } + } + }, + .EnumInstance => { + std.debug.assert(child_placeholder.name != null); + + if (std.mem.eql(u8, "value", child_placeholder.name.?.string)) { + try self.resolvePlaceholder( + child, + resolved_type.resolved_type.?.EnumInstance.resolved_type.?.Enum.enum_type, + false, + ); + } else { + self.reporter.reportErrorAt( + .property_does_not_exists, + self.ast.tokens.get(child_placeholder.where), + "Enum instance only has field `value`", + ); + return; + } + }, + else => { + self.reporter.reportErrorAt( + .field_access, + self.ast.tokens.get(child_placeholder.where), + "Doesn't support field access", + ); + return; + }, + } + }, + .Assignment => { + if (constant) { + self.reporter.reportErrorAt( + .constant, + self.ast.tokens.get(child_placeholder.where), + "Is constant.", + ); + return; + } + + // Assignment relation from a once Placeholder and now Object/Enum is creating an instance + const child_type: *obj.ObjTypeDef = try resolved_type.toInstance(self.gc.allocator, &self.gc.type_registry); + + // Is child type matching the parent? + try self.resolvePlaceholder(child, child_type, false); + }, + } +} + +// When we encounter the missing declaration we replace it with the resolved type. +// We then follow the chain of placeholders to see if their assumptions were correct. +// If not we raise a compile error. +pub fn resolvePlaceholder(self: *Self, placeholder: *obj.ObjTypeDef, resolved_type: *obj.ObjTypeDef, constant: bool) Error!void { + std.debug.assert(placeholder.def_type == .Placeholder); + + if (BuildOptions.debug_placeholders) { + std.debug.print("Attempts to resolve @{} ({s}) with @{} a {}({})\n", .{ + @intFromPtr(placeholder), + if (placeholder.resolved_type.?.Placeholder.name) |name| name.string else "unknown", + @intFromPtr(resolved_type), + resolved_type.def_type, + resolved_type.optional, + }); + } + + // Both placeholders, we have to connect the child placeholder to a root placeholder so its not orphan + if (resolved_type.def_type == .Placeholder) { + if (BuildOptions.debug_placeholders) { + std.debug.print( + "Replaced linked placeholder @{} ({s}) with rooted placeholder @{} ({s})\n", + .{ + @intFromPtr(placeholder), + if (placeholder.resolved_type.?.Placeholder.name != null) placeholder.resolved_type.?.Placeholder.name.?.string else "unknown", + @intFromPtr(resolved_type), + if (resolved_type.resolved_type.?.Placeholder.name != null) resolved_type.resolved_type.?.Placeholder.name.?.string else "unknown", + }, + ); + } + + if (resolved_type.resolved_type.?.Placeholder.parent) |parent| { + if (parent.def_type == .Placeholder) { + try parent.resolved_type.?.Placeholder.children.append(placeholder); + } else { + // Parent already resolved, resolve this now orphan placeholder + try self.resolvePlaceholderWithRelation( + resolved_type, + parent, + constant, + resolved_type.resolved_type.?.Placeholder.parent_relation.?, + ); + } + } + + // Merge both placeholder children list + // TODO: do we need this? + // try resolved_type.resolved_type.?.Placeholder.children.appendSlice(placeholder.resolved_type.?.Placeholder.children.items); + + // Don't copy obj header or it will break the linked list of objects + const o = placeholder.obj; + placeholder.* = resolved_type.*; + placeholder.obj = o; + return; + } + + const placeholder_def = placeholder.resolved_type.?.Placeholder; + + if (BuildOptions.debug_placeholders) { + std.debug.print( + "Resolved placeholder @{} {s}({}) with @{}.{}({})\n", + .{ + @intFromPtr(placeholder), + if (placeholder.resolved_type.?.Placeholder.name != null) placeholder.resolved_type.?.Placeholder.name.?.string else "unknown", + placeholder.optional, + @intFromPtr(resolved_type), + resolved_type.def_type, + resolved_type.optional, + }, + ); + } + + // Overwrite placeholder with resolved_type + // Don't copy obj header or it will break the linked list of objects + const o = placeholder.obj; + placeholder.* = resolved_type.*; + placeholder.obj = o; + // Put it in the registry so any cloneOptional/cloneNonOptional don't create new types + try self.gc.type_registry.setTypeDef(placeholder); + + // Now walk the chain of placeholders and see if they hold up + for (placeholder_def.children.items) |child| { + if (child.def_type == .Placeholder) { + try self.resolvePlaceholderWithRelation( + child, + placeholder, + constant, + child.resolved_type.?.Placeholder.parent_relation.?, + ); + } + } + + // TODO: should resolved_type be freed? + // TODO: does this work with vm.type_defs? (i guess not) +} + +fn addUpvalue(self: *Self, frame: *Frame, index: usize, is_local: bool) Error!usize { + const upvalue_count: u8 = frame.upvalue_count; + + var i: usize = 0; + while (i < upvalue_count) : (i += 1) { + const upvalue: *UpValue = &frame.upvalues[i]; + if (upvalue.index == index and upvalue.is_local == is_local) { + return i; + } + } + + if (upvalue_count == 255) { + self.reportError(.closures_count, "Too many closure variables in function."); + return 0; + } + + frame.upvalues[upvalue_count].is_local = is_local; + frame.upvalues[upvalue_count].index = @as(u8, @intCast(index)); + frame.upvalue_count += 1; + + return frame.upvalue_count - 1; +} + +fn resolveUpvalue(self: *Self, frame: *Frame, name: Ast.TokenIndex) Error!?usize { + if (frame.enclosing == null) { + return null; + } + + const local: ?usize = try self.resolveLocal(frame.enclosing.?, name); + if (local) |resolved| { + frame.enclosing.?.locals[resolved].is_captured = true; + return try self.addUpvalue(frame, resolved, true); + } + + const upvalue: ?usize = try self.resolveUpvalue(frame.enclosing.?, name); + if (upvalue) |resolved| { + return try self.addUpvalue(frame, resolved, false); + } + + return null; +} + +fn declareVariable(self: *Self, variable_type: *obj.ObjTypeDef, name_token: ?Ast.TokenIndex, constant: bool, check_name: bool) Error!usize { + const name = name_token orelse self.current_token.? - 1; + const name_lexeme = self.ast.tokens.items(.lexeme)[name]; + + if (self.current.?.scope_depth > 0) { + // Check a local with the same name doesn't exists + if (self.current.?.local_count > 0) { + var i: usize = self.current.?.local_count - 1; + while (check_name and i >= 0) : (i -= 1) { + const local: *Local = &self.current.?.locals[i]; + + if (local.depth != -1 and local.depth < self.current.?.scope_depth) { + break; + } + + if (!std.mem.eql(u8, name_lexeme, "_") and std.mem.eql(u8, name_lexeme, local.name.string)) { + self.reporter.reportWithOrigin( + .variable_already_exists, + self.ast.tokens.get(name), + self.ast.tokens.get(local.location), + "A variable named `{s}` already exists", + .{name_lexeme}, + null, + ); + } + + if (i == 0) break; + } + } + + return try self.addLocal(name, variable_type, constant); + } else { + if (check_name) { + // Check a global with the same name doesn't exists + for (self.globals.items, 0..) |*global, index| { + if (!std.mem.eql(u8, name_lexeme, "_") and std.mem.eql(u8, name_lexeme, global.name.string) and !global.hidden) { + // If we found a placeholder with that name, try to resolve it with `variable_type` + if (global.type_def.def_type == .Placeholder and global.type_def.resolved_type.?.Placeholder.name != null and std.mem.eql(u8, name_lexeme, global.type_def.resolved_type.?.Placeholder.name.?.string)) { + // A function declares a global with an incomplete typedef so that it can handle recursion + // The placeholder resolution occurs after we parsed the functions body in `funDeclaration` + if (variable_type.resolved_type != null or @intFromEnum(variable_type.def_type) < @intFromEnum(obj.ObjTypeDef.Type.ObjectInstance)) { + if (BuildOptions.debug_placeholders) { + std.debug.print( + "Global placeholder @{} resolve with @{} {s} (opt {})\n", + .{ + @intFromPtr(global.type_def), + @intFromPtr(variable_type), + (try variable_type.toStringAlloc(self.gc.allocator)).items, + variable_type.optional, + }, + ); + } + + try self.resolvePlaceholder(global.type_def, variable_type, constant); + } + + global.referenced = true; + + return index; + } else if (global.prefix == null) { + self.reportError(.variable_already_exists, "A global with the same name already exists."); + } + } + } + } + + return try self.addGlobal(name, variable_type, constant); + } +} + +fn parseVariable( + self: *Self, + identifier_consumed: bool, + variable_type: *obj.ObjTypeDef, + constant: bool, + error_message: []const u8, +) !usize { + if (!identifier_consumed) { + try self.consume(.Identifier, error_message); + } + + return try self.declareVariable( + variable_type, + null, + constant, + true, + ); +} + +inline fn markInitialized(self: *Self) void { + if (self.current.?.scope_depth == 0) { + // assert(!self.globals.items[self.globals.items.len - 1].initialized); + self.globals.items[self.globals.items.len - 1].initialized = true; + } else { + self.current.?.locals[self.current.?.local_count - 1].depth = @intCast(self.current.?.scope_depth); + } +} + +fn declarePlaceholder(self: *Self, name: Ast.TokenIndex, placeholder: ?*obj.ObjTypeDef) Error!usize { + var placeholder_type: *obj.ObjTypeDef = undefined; + + if (placeholder) |uplaceholder| { + placeholder_type = uplaceholder; + } else { + var placeholder_resolved_type: obj.ObjTypeDef.TypeUnion = .{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, name), + }; + placeholder_resolved_type.Placeholder.name = try self.gc.copyString(self.ast.tokens.items(.lexeme)[name]); + + placeholder_type = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = placeholder_resolved_type, + }, + ); + } + + std.debug.assert(!placeholder_type.optional); + + const global = try self.addGlobal( + name, + placeholder_type, + false, + ); + // markInitialized but we don't care what depth we are in + self.globals.items[global].initialized = true; + + if (BuildOptions.debug_placeholders) { + std.debug.print( + "global placeholder @{} for `{s}` at {}\n", + .{ + @intFromPtr(placeholder_type), + self.ast.tokens.items(.lexeme)[name], + global, + }, + ); + } + + return global; +} + +pub fn parseTypeDefFrom(self: *Self, source: []const u8) Error!*obj.ObjTypeDef { + const type_scanner = Scanner.init(self.gc.allocator, self.script_name, source); + // Replace parser scanner with one that only looks at that substring + const scanner = self.scanner; + self.scanner = type_scanner; + const current_token = self.current_token; + const tokens_count = self.ast.tokens.len; + + // If Eof, manually add new token + if (self.ast.tokens.items(.tag)[self.current_token.?] == .Eof) { + _ = try self.ast.appendToken(try self.scanner.?.scanToken()); + self.current_token = self.current_token.? + 1; + } else { + _ = try self.advance(); + } + + const parsed_type = try self.parseTypeDef(null, true); + + // Restore normal scanner and parser state + self.scanner = scanner; + self.current_token = current_token; + // Remove the added tokens + self.ast.tokens.shrinkRetainingCapacity(tokens_count); + + return self.ast.nodes.items(.type_def)[parsed_type].?; +} + +fn parseTypeDef( + self: *Self, + generic_types: ?std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef), + instance: bool, +) Error!Ast.Node.Index { + if (try self.match(.Str)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .String, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.Pat)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .Pattern, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.Ud)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .UserData, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.Type)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .Type, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.Void)) { + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = false, + .def_type = .Void, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.Int)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .Integer, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.Float)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .Float, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.Bool)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .Bool, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.Any)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .Any, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.Type)) { + const optional = try self.match(.Question); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = optional, + .def_type = .Type, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } else if (try self.match(.LeftBracket)) { + return self.parseListType(generic_types); + } else if (try self.match(.LeftBrace)) { + return self.parseMapType(generic_types); + } else if (try self.match(.Function) or try self.match(.Extern)) { + return try self.parseFunctionType(generic_types); + } else if (try self.match(.Fib)) { + return try self.parseFiberType(generic_types); + } else if (try self.match(.Obj)) { + const type_def_node = try self.parseObjType(generic_types); + if (instance) { + self.ast.nodes.items(.type_def)[type_def_node] = try self.ast.nodes.items(.type_def)[type_def_node].?.toInstance( + self.gc.allocator, + &self.gc.type_registry, + ); + } + + return type_def_node; + } else if ((try self.match(.Identifier))) { + const identifier = self.current_token.? - 1; + const identifier_lexeme = self.ast.tokens.items(.lexeme)[identifier]; + + var user_type_node: ?Ast.Node.Index = null; + var user_type: ?*obj.ObjTypeDef = null; + // Is it a generic type defined in enclosing functions or object? + if (self.resolveGeneric(try self.gc.copyString(identifier_lexeme))) |generic_type| { + user_type = generic_type; + } else if (generic_types != null) { + // Is it generic type defined in a function signature being parsed? + if (generic_types.?.get(try self.gc.copyString(identifier_lexeme))) |generic_type| { + user_type = generic_type; + } + } + + // Is it a user defined type (object, enum, etc.) defined in global scope? + if (user_type == null) { + user_type_node = try self.parseUserType(instance); + user_type = self.ast.nodes.items(.type_def)[user_type_node.?]; + } + + if (try self.match(.Question)) { + user_type = try user_type.?.cloneOptional(&self.gc.type_registry); + + if (user_type_node) |un| { + self.ast.nodes.items(.type_def)[un] = user_type; + } + } + + return user_type_node orelse try self.ast.appendNode( + .{ + .tag = .GenericType, + .location = identifier, + .end_location = self.current_token.? - 1, + .type_def = user_type.?, + .components = .{ + .GenericType = {}, + }, + }, + ); + } else { + self.reportErrorAtCurrent(.syntax, "Expected type definition."); + + return self.ast.appendNode( + .{ + .tag = .SimpleType, + .location = undefined, + .end_location = undefined, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = try self.match(.Question), + .def_type = .Void, + }, + ), + .components = .{ + .SimpleType = {}, + }, + }, + ); + } +} + +fn parseFiberType(self: *Self, generic_types: ?std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef)) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + try self.consume(.Less, "Expected `<` after `fib`"); + const return_type = try self.parseTypeDef(generic_types, true); + try self.consume(.Comma, "Expected `,` after fiber return type"); + const yield_type = try self.parseTypeDef(generic_types, true); + + const yield_type_def = self.ast.nodes.items(.type_def)[yield_type].?; + if (!yield_type_def.optional and yield_type_def.def_type != .Void) { + self.reportError(.yield_type, "Expected optional type or void"); + } + + try self.consume(.Greater, "Expected `>` after fiber yield type"); + + const fiber_def = obj.ObjFiber.FiberDef{ + .return_type = self.ast.nodes.items(.type_def)[return_type].?, + .yield_type = self.ast.nodes.items(.type_def)[yield_type].?, + }; + const resolved_type = obj.ObjTypeDef.TypeUnion{ + .Fiber = fiber_def, + }; + + return self.ast.appendNode( + .{ + .tag = .FiberType, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + obj.ObjTypeDef{ + .optional = try self.match(.Question), + .def_type = .Fiber, + .resolved_type = resolved_type, + }, + ), + .components = .{ + .FiberType = .{ + .return_type = return_type, + .yield_type = yield_type, + }, + }, + }, + ); +} + +fn parseListType(self: *Self, generic_types: ?std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef)) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + const item_type = try self.parseTypeDef(generic_types, true); + + try self.consume(.RightBracket, "Expected `]` after list type."); + + const list_def = obj.ObjList.ListDef.init(self.gc.allocator, self.ast.nodes.items(.type_def)[item_type].?); + const resolved_type = obj.ObjTypeDef.TypeUnion{ + .List = list_def, + }; + const list_type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = try self.match(.Question), + .def_type = .List, + .resolved_type = resolved_type, + }, + ); + + return try self.ast.appendNode( + .{ + .tag = .ListType, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = list_type_def, + .components = .{ + .ListType = .{ + .item_type = item_type, + }, + }, + }, + ); +} + +fn parseMapType(self: *Self, generic_types: ?std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef)) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const key_type = try self.parseTypeDef(generic_types, true); + + try self.consume(.Comma, "Expected `,` after key type."); + + const value_type = try self.parseTypeDef(generic_types, true); + + try self.consume(.RightBrace, "Expected `}` after value type."); + + const type_defs = self.ast.nodes.items(.type_def); + const map_def = obj.ObjMap.MapDef.init(self.gc.allocator, type_defs[key_type].?, type_defs[value_type].?); + const resolved_type = obj.ObjTypeDef.TypeUnion{ + .Map = map_def, + }; + const map_type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = try self.match(.Question), + .def_type = .Map, + .resolved_type = resolved_type, + }, + ); + + return try self.ast.appendNode( + .{ + .tag = .MapType, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = map_type_def, + .components = .{ + .MapType = .{ + .key_type = key_type, + .value_type = value_type, + }, + }, + }, + ); +} + +fn parseFunctionType(self: *Self, parent_generic_types: ?std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef)) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + const tag = self.ast.tokens.items(.tag)[start_location]; + + std.debug.assert(tag == .Function or tag == .Extern); + + const is_extern = tag == .Extern; + + if (is_extern) { + try self.consume(.Function, "Expected `Function` after `extern`."); + } + + var name_token: ?Ast.TokenIndex = null; + var name: ?*obj.ObjString = null; + if (try self.match(.Identifier)) { + name_token = self.current_token.? - 1; + name = try self.gc.copyString(self.ast.tokens.items(.lexeme)[self.current_token.? - 1]); + } + + var merged_generic_types = std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef).init(self.gc.allocator); + defer merged_generic_types.deinit(); + if (parent_generic_types != null) { + var it = parent_generic_types.?.iterator(); + while (it.next()) |kv| { + try merged_generic_types.put(kv.key_ptr.*, kv.value_ptr.*); + } + } + + var generic_types_list = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer generic_types_list.shrinkAndFree(generic_types_list.items.len); + // To avoid duplicates + var generic_types = std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef).init(self.gc.allocator); + if (try self.match(.DoubleColon)) { + try self.consume(.Less, "Expected `<` at start of generic types list."); + + var i: usize = 0; + while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { + try self.consume(.Identifier, "Expected generic type identifier"); + + const generic_identifier = self.current_token.? - 1; + const generic_identifier_lexeme = self.ast.tokens.items(.lexeme)[generic_identifier]; + if (generic_types.get(try self.gc.copyString(generic_identifier_lexeme)) == null) { + const generic = obj.ObjTypeDef.GenericDef{ + .origin = undefined, + .index = i, + }; + const resolved = obj.ObjTypeDef.TypeUnion{ .Generic = generic }; + const type_def = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Generic, + .resolved_type = resolved, + }, + ); + + try generic_types.put( + try self.gc.copyString(generic_identifier_lexeme), + type_def, + ); + + try merged_generic_types.put( + try self.gc.copyString(generic_identifier_lexeme), + type_def, + ); + + try generic_types_list.append( + try self.ast.appendNode( + .{ + .tag = .GenericType, + .location = generic_identifier, + .end_location = generic_identifier, + .type_def = type_def, + .components = .{ + .GenericType = {}, + }, + }, + ), + ); + } else { + self.reportErrorFmt( + .generic_type, + "Generic type `{s}` already defined", + .{self.ast.tokens.items(.lexeme)[self.current_token.? - 1]}, + ); + } + + if (!self.check(.Greater)) { + try self.consume(.Comma, "Expected `,` between generic types"); + } + } + + if (generic_types.count() == 0) { + self.reportError(.generic_type, "Expected at least one generic type"); + } + + try self.consume(.Greater, "Expected `>` after generic types list"); + } + + try self.consume(.LeftParen, "Expected `(` after function name."); + + var arguments = std.ArrayList(Ast.FunctionType.Argument).init(self.gc.allocator); + defer arguments.shrinkAndFree(arguments.items.len); + var parameters = std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef).init(self.gc.allocator); + var defaults = std.AutoArrayHashMap(*obj.ObjString, Value).init(self.gc.allocator); + var arity: usize = 0; + if (!self.check(.RightParen)) { + while (true) { + arity += 1; + if (arity > 255) { + self.reportErrorAtCurrent(.arguments_count, "Can't have more than 255 arguments."); + } + + const arg_type = try self.parseTypeDef(merged_generic_types, true); + const arg_type_def = self.ast.nodes.items(.type_def)[arg_type]; + try self.consume(.Identifier, "Expected argument name"); + const arg_name_token = self.current_token.? - 1; + const arg_name = self.ast.tokens.items(.lexeme)[self.current_token.? - 1]; + + var default: ?Ast.Node.Index = null; + if (try self.match(.Equal)) { + const expr = try self.expression(false); + const expr_type_def = self.ast.nodes.items(.type_def)[expr]; + + if (expr_type_def != null and expr_type_def.?.def_type == .Placeholder and arg_type_def.?.def_type == .Placeholder) { + try obj.PlaceholderDef.link( + arg_type_def.?, + expr_type_def.?, + .Assignment, + ); + } + + if (!self.ast.isConstant(expr)) { + self.reportError( + .constant_default, + "Default parameters must be constant values.", + ); + } + + default = expr; + } + + try arguments.append( + .{ + .name = arg_name_token, + .default = default, + .type = arg_type, + }, + ); + + try defaults.put( + try self.gc.copyString(arg_name), + if (default) |def| + try self.ast.toValue(def, self.gc) + else + Value.Null, + ); + try parameters.put(try self.gc.copyString(arg_name), arg_type_def.?); + + if (!try self.match(.Comma)) break; + } + } + + try self.consume(.RightParen, "Expected `)` after function parameters."); + + const return_type = if (try self.match(.Greater)) + try self.parseTypeDef(null, true) + else + null; + + const yield_type = if (try self.match(.Greater)) + try self.parseTypeDef(null, true) + else + null; + + var error_types_list = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer error_types_list.shrinkAndFree(error_types_list.items.len); + var error_types = std.ArrayList(*obj.ObjTypeDef).init(self.gc.allocator); + defer error_types.shrinkAndFree(error_types.items.len); + if (try self.match(.BangGreater)) { + while (!self.check(.Eof)) { + const error_type = try self.parseTypeDef(generic_types, true); + const error_type_def = self.ast.nodes.items(.type_def)[error_type].?; + try error_types_list.append(error_type); + try error_types.append(error_type_def); + + if (error_type_def.optional) { + self.reportError(.error_type, "Error type can't be optional"); + } + + if (!self.check(.Comma)) { + break; + } + } + } + + var function_typedef: obj.ObjTypeDef = .{ + .def_type = .Function, + .optional = try self.match(.Question), + }; + const function_def: obj.ObjFunction.FunctionDef = .{ + .id = obj.ObjFunction.FunctionDef.nextId(), + .script_name = try self.gc.copyString(self.script_name), + .name = name orelse try self.gc.copyString("anonymous"), + .return_type = if (return_type) |rt| + try self.ast.nodes.items(.type_def)[rt].?.toInstance(self.gc.allocator, &self.gc.type_registry) + else + try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), + .yield_type = if (yield_type) |yt| + try self.ast.nodes.items(.type_def)[yt].?.toInstance(self.gc.allocator, &self.gc.type_registry) + else + try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), + .parameters = parameters, + .defaults = defaults, + .function_type = if (is_extern) .Extern else .Anonymous, + .generic_types = generic_types, + .error_types = if (error_types.items.len > 0) error_types.items else null, + }; + const function_resolved_type: obj.ObjTypeDef.TypeUnion = .{ .Function = function_def }; + + function_typedef.resolved_type = function_resolved_type; + + return self.ast.appendNode( + .{ + .tag = .FunctionType, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef(function_typedef), + .components = .{ + .FunctionType = .{ + .lambda = false, + .name = name_token, + .arguments = arguments.items, + .error_types = error_types_list.items, + .generic_types = generic_types_list.items, + .return_type = return_type, + .yield_type = yield_type, + }, + }, + }, + ); +} + +// Only used to parse anonymouse object type +fn parseObjType(self: *Self, generic_types: ?std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef)) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + try self.consume(.LeftBrace, "Expected `{` after `obj`"); + + const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); + defer self.gc.allocator.free(qualifier); + var qualified_name = std.ArrayList(u8).init(self.gc.allocator); + defer qualified_name.deinit(); + try qualified_name.writer().print("{s}.anonymous", .{qualifier}); + + const object_def = obj.ObjObject.ObjectDef.init( + self.gc.allocator, + self.ast.tokens.get(start_location), + try self.gc.copyString("anonymous"), + try self.gc.copyString(qualified_name.items), + true, + ); + const resolved_type = obj.ObjTypeDef.TypeUnion{ .Object = object_def }; + var object_type = obj.ObjTypeDef{ + .def_type = .Object, + .resolved_type = resolved_type, + }; + + // Anonymous object can only have properties without default values (no methods, no static fields) + // They can't self reference since their anonymous + var field_names = std.StringHashMap(void).init(self.gc.allocator); + defer field_names.deinit(); + var fields = std.ArrayList(Ast.AnonymousObjectType.Field).init(self.gc.allocator); + defer fields.shrinkAndFree(fields.items.len); + while (!self.check(.RightBrace) and !self.check(.Eof)) { + const property_type = try self.parseTypeDef(generic_types, true); + + try self.consume(.Identifier, "Expected property name."); + const property_name = self.current_token.? - 1; + const property_name_lexeme = self.ast.tokens.items(.lexeme)[property_name]; + + if (field_names.get(property_name_lexeme) != null) { + self.reportError(.property_already_exists, "A property with that name already exists."); + } + + if (!self.check(.RightBrace) or self.check(.Comma)) { + try self.consume(.Comma, "Expected `,` after property definition."); + } + try object_type.resolved_type.?.Object.fields.put( + property_name_lexeme, + self.ast.nodes.items(.type_def)[property_type].?, + ); + try field_names.put(property_name_lexeme, {}); + try fields.append( + .{ + .name = property_name, + .type = property_type, + }, + ); + } + + try self.consume(.RightBrace, "Expected `}` after object body."); + + return try self.ast.appendNode( + .{ + .tag = .AnonymousObjectType, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef(object_type), + .components = .{ + .AnonymousObjectType = .{ + .fields = fields.items, + }, + }, + }, + ); +} + +fn parseUserType(self: *Self, instance: bool) Error!Ast.Node.Index { + const user_type_name = self.current_token.? - 1; + var var_type: ?*obj.ObjTypeDef = null; + var global_slot: ?usize = null; + + // Search for a global with that name + if (try self.resolveGlobal(null, self.ast.tokens.items(.lexeme)[user_type_name])) |slot| { + var_type = self.globals.items[slot].type_def; + global_slot = @intCast(slot); + } + + // If none found, create a placeholder + if (var_type == null) { + var placeholder_resolved_type: obj.ObjTypeDef.TypeUnion = .{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, user_type_name), + }; + + placeholder_resolved_type.Placeholder.name = try self.gc.copyString(self.ast.tokens.items(.lexeme)[user_type_name]); + + var_type = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = placeholder_resolved_type, + }, + ); + + global_slot = try self.declarePlaceholder(user_type_name, var_type.?); + } + + // Concrete generic types list + var resolved_generics = std.ArrayList(*obj.ObjTypeDef).init(self.gc.allocator); + defer resolved_generics.shrinkAndFree(resolved_generics.items.len); + var generic_nodes = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer generic_nodes.shrinkAndFree(generic_nodes.items.len); + const generic_resolve = if (try self.match(.DoubleColon)) gn: { + const generic_start = self.current_token.? - 1; + + try self.consume(.Less, "Expected generic types list after `::`"); + + var i: usize = 0; + while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { + try generic_nodes.append( + try self.parseTypeDef( + if (self.current.?.generics) |generics| + generics.* + else + null, + true, + ), + ); + + try resolved_generics.append( + self.ast.nodes.items(.type_def)[generic_nodes.items[generic_nodes.items.len - 1]].?, + ); + + if (!self.check(.Greater)) { + try self.consume(.Comma, "Expected `,` between generic types"); + } + } + + try self.consume(.Greater, "Expected `>` after generic types list"); + + if (resolved_generics.items.len == 0) { + self.reportErrorAtCurrent(.generic_type, "Expected at least one type"); + } + + var_type = try var_type.?.populateGenerics( + self.current_token.? - 1, + var_type.?.resolved_type.?.Object.id, + resolved_generics.items, + &self.gc.type_registry, + null, + ); + break :gn try self.ast.appendNode( + .{ + .tag = .GenericResolveType, + .location = generic_start, + .end_location = self.current_token.? - 1, + .type_def = var_type, + .components = .{ + .GenericResolveType = .{ + .resolved_types = generic_nodes.items, + }, + }, + }, + ); + } else null; + + return try self.ast.appendNode( + .{ + .tag = .UserType, + .location = user_type_name, + .end_location = self.current_token.? - 1, + .type_def = if (instance) + try var_type.?.toInstance(self.gc.allocator, &self.gc.type_registry) + else + var_type.?, + .components = .{ + .UserType = .{ + .generic_resolve = generic_resolve, + .identifier = user_type_name, + }, + }, + }, + ); +} + +fn parseGenericResolve(self: *Self, callee_type_def: *obj.ObjTypeDef, expr: ?Ast.Node.Index) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + var resolved_generics = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer resolved_generics.shrinkAndFree(resolved_generics.items.len); + var resolved_generics_types = std.ArrayList(*obj.ObjTypeDef).init(self.gc.allocator); + defer resolved_generics_types.shrinkAndFree(resolved_generics_types.items.len); + + try self.consume(.Less, "Expected `<` at start of generic types list"); + + while (!self.check(.Greater) and !self.check(.Eof)) { + const resolved_generic = try self.parseTypeDef(null, true); + + if (callee_type_def.def_type == .Any) { + self.reportError(.any_generic, "`any` not allowed as generic type"); + } + + try resolved_generics.append(resolved_generic); + try resolved_generics_types.append(self.ast.nodes.items(.type_def)[resolved_generic].?); + + if (!self.check(.Greater)) { + try self.consume(.Comma, "Expected `,` between generic types"); + } + } + + try self.consume(.Greater, "Expected `>` after generic types list"); + + return self.ast.appendNode( + .{ + .tag = if (expr != null) + .GenericResolve + else + .GenericResolveType, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try callee_type_def.populateGenerics( + self.current_token.? - 1, + if (callee_type_def.def_type == .Function) + callee_type_def.resolved_type.?.Function.id + else if (callee_type_def.def_type == .Object) + callee_type_def.resolved_type.?.Object.id + else + null, + resolved_generics_types.items, + &self.gc.type_registry, + null, + ), + .components = if (expr) |e| .{ + .GenericResolve = e, + } else .{ + .GenericResolveType = .{ + .resolved_types = resolved_generics.items, + }, + }, + }, + ); +} + +fn subscript(self: *Self, can_assign: bool, subscripted: Ast.Node.Index) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const subscript_type_def = self.ast.nodes.items(.type_def)[subscripted]; + const index = try self.expression(false); + const index_type_def = self.ast.nodes.items(.type_def)[index]; + + const type_defs = self.ast.nodes.items(.type_def); + if (subscript_type_def.?.def_type == .Placeholder and index_type_def.?.def_type == .Placeholder) { + try obj.PlaceholderDef.link( + type_defs[subscripted].?, + type_defs[index].?, + .Key, + ); + } + + var subscripted_type_def: ?*obj.ObjTypeDef = null; + + if (type_defs[subscripted]) |type_def| { + if (!type_def.optional) { + switch (type_def.def_type) { + .Placeholder => { + const placeholder_resolved_type: obj.ObjTypeDef.TypeUnion = .{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, self.current_token.? - 1), + }; + const placeholder = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = placeholder_resolved_type, + }, + ); + + try obj.PlaceholderDef.link(type_def, placeholder, .Subscript); + + subscripted_type_def = placeholder; + }, + .String => subscripted_type_def = type_def, + .List => subscripted_type_def = type_def.resolved_type.?.List.item_type, + .Map => subscripted_type_def = try type_def.resolved_type.?.Map.value_type.cloneOptional(&self.gc.type_registry), + else => self.reportErrorFmt( + .subscriptable, + "Type `{s}` is not subscriptable", + .{(try type_def.toStringAlloc(self.gc.allocator)).items}, + ), + } + } else { + self.reportError(.subscriptable, "Optional type is not subscriptable"); + } + } + + try self.consume(.RightBracket, "Expected `]`."); + + var value: ?Ast.Node.Index = null; + if (can_assign and (subscript_type_def == null or subscript_type_def.?.def_type != .String) and try self.match(.Equal)) { + value = try self.expression(false); + const value_type_def = self.ast.nodes.items(.type_def)[value.?]; + + if (subscript_type_def.?.def_type == .Placeholder and value_type_def.?.def_type == .Placeholder) { + try obj.PlaceholderDef.link(subscript_type_def.?, value_type_def.?, .Subscript); + } + } + + return try self.ast.appendNode( + .{ + .tag = .Subscript, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = subscripted_type_def, + .components = .{ + .Subscript = .{ + .index = index, + .value = value, + .subscripted = subscripted, + }, + }, + }, + ); +} + +pub fn expression(self: *Self, hanging: bool) Error!Ast.Node.Index { + return try self.parsePrecedence(.Assignment, hanging); +} + +fn expressionStatement(self: *Self, hanging: bool) Error!Ast.Node.Index { + const expr = try self.expression(hanging); + + try self.consume(.Semicolon, "Expected `;` after expression."); + + return try self.ast.appendNode( + .{ + .tag = .Expression, + .type_def = self.ast.nodes.items(.type_def)[expr], + .location = self.ast.nodes.items(.location)[expr], + .end_location = self.ast.nodes.items(.end_location)[expr], + .components = .{ + .Expression = expr, + }, + }, + ); +} + +fn list(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + var items = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + const explicit_item_type: ?Ast.Node.Index = null; + var item_type: ?*obj.ObjTypeDef = null; + + // A list expression can specify its type `[, ...]` + if (try self.match(.Less)) { + const item_type_node = try self.parseTypeDef(null, true); + item_type = self.ast.nodes.items(.type_def)[item_type_node]; + + try self.consume(.Greater, "Expected `>` after list type."); + } + + if (item_type == null or try self.match(.Comma)) { + var common_type: ?*obj.ObjTypeDef = null; + while (!(try self.match(.RightBracket)) and !(try self.match(.Eof))) { + const actual_item = try self.expression(false); + + try items.append(actual_item); + + if (item_type == null) { + if (common_type == null) { + common_type = self.ast.nodes.items(.type_def)[actual_item]; + } else if (self.ast.nodes.items(.type_def)[actual_item]) |actual_type_def| { + if (!common_type.?.eql(actual_type_def)) { + if (common_type.?.def_type == .ObjectInstance and actual_type_def.def_type == .ObjectInstance) { + common_type = common_type.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.both_conforms(actual_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object) orelse common_type; + common_type = try common_type.?.toInstance(self.gc.allocator, &self.gc.type_registry); + } else { + common_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Any }); + } + } + } + } + + if (!self.check(.RightBracket)) { + try self.consume(.Comma, "Expected `,` after list item."); + } + } + + if (self.ast.tokens.items(.tag)[self.current_token.? - 1] != .RightBracket) { + self.reportErrorAtCurrent(.syntax, "Expected `]`"); + } + + item_type = item_type orelse common_type; + } else { + try self.consume(.RightBracket, "Expected `]`"); + } + + // Either item type was specified with `` or the list is not empty and we could infer it + if (item_type == null) { + self.reportError(.list_item_type, "List item type can't be infered"); + + item_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); + } + + const list_def = obj.ObjList.ListDef.init(self.gc.allocator, item_type.?); + + const resolved_type = obj.ObjTypeDef.TypeUnion{ .List = list_def }; + + const list_type = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .List, + .resolved_type = resolved_type, + }, + ); + + return self.ast.appendNode( + .{ + .tag = .List, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = list_type, + .components = .{ + .List = .{ + .explicit_item_type = explicit_item_type, + .items = items.items, + }, + }, + }, + ); +} + +fn literal(self: *Self, _: bool) Error!Ast.Node.Index { + var node = Ast.Node{ + .tag = undefined, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .components = undefined, + }; + + switch (self.ast.tokens.items(.tag)[node.location]) { + .True, .False => { + node.tag = .Boolean; + node.components = .{ + .Boolean = self.ast.tokens.items(.tag)[node.location] == .True, + }; + node.type_def = try self.gc.type_registry.getTypeDef(.{ + .def_type = .Bool, + }); + }, + .Null => { + node.tag = .Null; + node.components = .{ + .Null = {}, + }; + node.type_def = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Void, + }, + ); + }, + .Void => { + node.tag = .Void; + node.components = .{ + .Void = {}, + }; + node.type_def = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Void, + }, + ); + }, + .IntegerValue => { + node.tag = .Integer; + node.components = .{ + .Integer = self.ast.tokens.items(.literal_integer)[node.location].?, + }; + node.type_def = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Integer, + }, + ); + }, + .FloatValue => { + node.tag = .Float; + node.components = .{ + .Float = self.ast.tokens.items(.literal_float)[node.location].?, + }; + node.type_def = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Float, + }, + ); + }, + else => unreachable, + } + + return try self.ast.appendNode(node); +} + +fn grouping(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const expr = try self.expression(false); + + try self.consume(.RightParen, "Expected ')' after expression."); + + return try self.ast.appendNode( + .{ + .tag = .Grouping, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = self.ast.nodes.items(.type_def)[expr], + .components = .{ + .Grouping = expr, + }, + }, + ); +} + +fn argumentList(self: *Self) ![]Ast.Call.Argument { + var arguments = std.ArrayList(Ast.Call.Argument).init(self.gc.allocator); + defer arguments.shrinkAndFree(arguments.items.len); + + var arg_count: u8 = 0; + while (!(try self.match(.RightParen)) and !(try self.match(.Eof))) { + var hanging = false; + const arg_name = if (try self.match(.Identifier)) + self.current_token.? - 1 + else + null; + + if (arg_name != null) { + if (arg_count == 0 or self.check(.Comma) or self.check(.RightParen)) { + // The identifier we just parsed might not be the argument name but the start of an expression or the expression itself + hanging = !(try self.match(.Colon)); + } else { + try self.consume(.Colon, "Expected `:` after argument name."); + } + } else if (arg_count != 0 and arg_name == null) { + self.reportError(.syntax, "Expected argument name."); + break; + } + + const is_named_expr = arg_count != 0 and arg_name != null and hanging and (self.check(.Comma) or self.check(.RightParen)); + + try arguments.append( + .{ + .name = if ((!hanging and arg_name != null) or is_named_expr) + arg_name.? + else + null, + .value = try self.expression(hanging), + }, + ); + + if (arg_count == 255) { + self.reportError(.arguments_count, "Can't have more than 255 arguments."); + + return arguments.items; + } + + arg_count += 1; + + if (!self.check(.RightParen)) { + try self.consume(.Comma, "Expected `,` after call argument"); + } + } + + return arguments.items; +} + +fn call(self: *Self, _: bool, callee: Ast.Node.Index) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + const callee_type_def = self.ast.nodes.items(.type_def)[callee]; + + const arguments = try self.argumentList(); + const catch_default = if (try self.match(.Catch)) + try self.expression(false) + else + null; + + // Node type is Function or Native return type or nothing/placeholder + var type_def = if (callee_type_def != null and callee_type_def.?.def_type == .Function) + callee_type_def.?.resolved_type.?.Function.return_type + else if (callee_type_def != null and callee_type_def.?.def_type == .Enum) + try (try callee_type_def.?.toInstance(self.gc.allocator, &self.gc.type_registry)).cloneOptional(&self.gc.type_registry) + else + null; + + // If null, create placeholder + if (type_def == null) { + if (callee_type_def == null or callee_type_def.?.def_type != .Placeholder) { + self.reporter.reportErrorAt( + .callable, + self.ast.tokens.get(self.ast.nodes.items(.location)[callee]), + "Can't be called", + ); + } else { + const placeholder_resolved_type: obj.ObjTypeDef.TypeUnion = .{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, start_location), + }; + + type_def = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = placeholder_resolved_type, + }, + ); + + try obj.PlaceholderDef.link( + callee_type_def.?, + type_def.?, + .Call, + ); + } + } + + return self.ast.appendNode( + .{ + .tag = .Call, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = type_def.?, + .components = .{ + .Call = .{ + .is_async = false, + .callee = callee, + // We do this because the callee type will change in the dot.call usecase + .callee_type_def = self.ast.nodes.items(.type_def)[callee].?, + .arguments = arguments, + .catch_default = catch_default, + }, + }, + }, + ); +} + +fn map(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + var value_type_node: ?Ast.Node.Index = null; + var value_type_def: ?*obj.ObjTypeDef = null; + var key_type_node: ?Ast.Node.Index = null; + var key_type_def: ?*obj.ObjTypeDef = null; + + // A map expression can specify its type `{, ...}` + if (try self.match(.Less)) { + key_type_node = try self.parseTypeDef(null, true); + key_type_def = self.ast.nodes.items(.type_def)[key_type_node.?]; + + try self.consume(.Comma, "Expected `,` after key type"); + + value_type_node = try self.parseTypeDef(null, true); + value_type_def = self.ast.nodes.items(.type_def)[value_type_node.?]; + + try self.consume(.Greater, "Expected `>` after map type."); + } + + var entries = std.ArrayList(Ast.Map.Entry).init(self.gc.allocator); + defer entries.shrinkAndFree(entries.items.len); + if (key_type_node == null or try self.match(.Comma)) { + var common_key_type: ?*obj.ObjTypeDef = null; + var common_value_type: ?*obj.ObjTypeDef = null; + while (!(try self.match(.RightBrace)) and !(try self.match(.Eof))) { + const key = try self.expression(false); + try self.consume(.Colon, "Expected `:` after key."); + const value = try self.expression(false); + + try entries.append( + .{ + .key = key, + .value = value, + }, + ); + + if (key_type_node == null) { + if (common_key_type == null) { + common_key_type = self.ast.nodes.items(.type_def)[key]; + } else if (self.ast.nodes.items(.type_def)[key]) |actual_type_def| { + if (!common_key_type.?.eql(actual_type_def)) { + if (common_key_type.?.def_type == .ObjectInstance and actual_type_def.def_type == .ObjectInstance) { + common_key_type = common_key_type.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.both_conforms( + actual_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object, + ) orelse common_key_type; + + common_key_type = try common_key_type.?.toInstance( + self.gc.allocator, + &self.gc.type_registry, + ); + } else { + common_key_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Any }); + } + } + } + } + + if (value_type_node == null) { + if (common_value_type == null) { + common_value_type = self.ast.nodes.items(.type_def)[value]; + } else if (self.ast.nodes.items(.type_def)[value]) |actual_type_def| { + if (!common_value_type.?.eql(actual_type_def)) { + if (common_value_type.?.def_type == .ObjectInstance and actual_type_def.def_type == .ObjectInstance) { + common_value_type = common_value_type.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.both_conforms( + actual_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object, + ) orelse common_value_type; + + common_value_type = try common_value_type.?.toInstance( + self.gc.allocator, + &self.gc.type_registry, + ); + } else { + common_value_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Any }); + } + } + } + } + + if (!self.check(.RightBrace)) { + try self.consume(.Comma, "Expected `,` after map entry."); + } + } + + key_type_def = key_type_def orelse common_key_type; + value_type_def = value_type_def orelse common_value_type; + } else { + try self.consume(.RightBrace, "Expected `}`"); + } + + if (key_type_def == null and value_type_def == null) { + self.reportError(.map_key_type, "Unknown map key and value type"); + + key_type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); + value_type_def = key_type_def; + } + + const map_def = obj.ObjMap.MapDef.init( + self.gc.allocator, + key_type_def.?, + value_type_def.?, + ); + const resolved_type = obj.ObjTypeDef.TypeUnion{ .Map = map_def }; + const map_type = try self.gc.type_registry.getTypeDef( + .{ + .optional = try self.match(.Question), + .def_type = .Map, + .resolved_type = resolved_type, + }, + ); + + return self.ast.appendNode( + .{ + .tag = .Map, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = map_type, + .components = .{ + .Map = .{ + .explicit_key_type = key_type_node, + .explicit_value_type = value_type_node, + .entries = entries.items, + }, + }, + }, + ); +} + +fn objectInit(self: *Self, _: bool, object: Ast.Node.Index) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + const obj_type_def = self.ast.nodes.items(.type_def)[object]; + var properties = std.ArrayList(Ast.ObjectInit.Property).init(self.gc.allocator); + defer properties.shrinkAndFree(properties.items.len); + var property_names = std.StringHashMap(Ast.Node.Index).init(self.gc.allocator); + defer property_names.deinit(); + + while (!self.check(.RightBrace) and !self.check(.Eof)) { + try self.consume(.Identifier, "Expected property name"); + + const property_name = self.current_token.? - 1; + const property_name_lexeme = self.ast.tokens.items(.lexeme)[property_name]; + if (property_names.get(property_name_lexeme)) |previous_decl| { + self.reporter.reportWithOrigin( + .property_already_exists, + self.ast.tokens.get(property_name), + self.ast.tokens.get(previous_decl), + "Property `{s}` was already defined", + .{property_name_lexeme}, + "Defined here", + ); + } + try property_names.put(property_name_lexeme, property_name); + + var property_placeholder: ?*obj.ObjTypeDef = null; + + // Object is placeholder, create placeholder for the property and link it + const object_type_def = self.ast.nodes.items(.type_def)[object]; + if (object_type_def != null and object_type_def.?.def_type == .Placeholder) { + var placeholder_resolved_type: obj.ObjTypeDef.TypeUnion = .{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, self.current_token.? - 1), + }; + placeholder_resolved_type.Placeholder.name = try self.gc.copyString( + self.ast.tokens.items(.lexeme)[property_name], + ); + + property_placeholder = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = placeholder_resolved_type, + }, + ); + + try obj.PlaceholderDef.link( + obj_type_def.?, + property_placeholder.?, + .FieldAccess, + ); + } + + // Named variable with the same name as property + if (self.check(.Comma) or self.check(.RightBrace)) { + try properties.append( + .{ + .name = property_name, + .value = try self.expression(true), + }, + ); + } else { + try self.consume(.Equal, "Expected `=` after property name."); + + try properties.append( + .{ + .name = property_name, + .value = try self.expression(false), + }, + ); + } + + if (!self.check(.RightBrace) or self.check(.Comma)) { + try self.consume(.Comma, "Expected `,` after field initialization."); + } + } + + try self.consume(.RightBrace, "Expected `}` after object initialization."); + + return self.ast.appendNode( + .{ + .tag = .ObjectInit, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = if (self.ast.nodes.items(.type_def)[object]) |type_def| + try type_def.toInstance(self.gc.allocator, &self.gc.type_registry) + else + null, + .components = .{ + .ObjectInit = .{ + .object = object, + .properties = properties.items, + }, + }, + }, + ); +} + +fn anonymousObjectInit(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + try self.consume(.LeftBrace, "Expected `{` after `.`"); + + const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); + defer self.gc.allocator.free(qualifier); + var qualified_name = std.ArrayList(u8).init(self.gc.allocator); + defer qualified_name.deinit(); + try qualified_name.writer().print("{s}.anonymous", .{qualifier}); + + const object_def = obj.ObjObject.ObjectDef.init( + self.gc.allocator, + self.ast.tokens.get(start_location), + try self.gc.copyString("anonymous"), + try self.gc.copyString(qualified_name.items), + true, + ); + + const resolved_type = obj.ObjTypeDef.TypeUnion{ .Object = object_def }; + + // We build the object type has we parse its instanciation + var object_type = obj.ObjTypeDef{ + .def_type = .Object, + .resolved_type = resolved_type, + }; + + // Anonymous object can only have properties without default values (no methods, no static fields) + // They can't self reference since their anonymous + var properties = std.ArrayList(Ast.ObjectInit.Property).init(self.gc.allocator); + defer properties.shrinkAndFree(properties.items.len); + var property_names = std.StringHashMap(Ast.Node.Index).init(self.gc.allocator); + defer property_names.deinit(); + + while (!self.check(.RightBrace) and !self.check(.Eof)) { + try self.consume(.Identifier, "Expected property name"); + + const property_name = self.current_token.? - 1; + const property_name_lexeme = self.ast.tokens.items(.lexeme)[property_name]; + if (property_names.get(property_name_lexeme)) |previous_decl| { + self.reporter.reportWithOrigin( + .property_already_exists, + self.ast.tokens.get(property_name), + self.ast.tokens.get(previous_decl), + "Property `{s}` was already defined", + .{property_name_lexeme}, + "Defined here", + ); + } + try property_names.put(property_name_lexeme, property_name); + + try self.consume(.Equal, "Expected `=` after property name."); + + const expr = try self.expression(false); + + try properties.append( + .{ + .name = property_name, + .value = expr, + }, + ); + try object_type.resolved_type.?.Object.fields.put( + property_name_lexeme, + self.ast.nodes.items(.type_def)[expr].?, + ); + + if (!self.check(.RightBrace) or self.check(.Comma)) { + try self.consume(.Comma, "Expected `,` after field initialization."); + } + } + + try self.consume(.RightBrace, "Expected `}` after object initialization."); + + return try self.ast.appendNode( + .{ + .tag = .ObjectInit, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try (try self.gc.type_registry.getTypeDef(object_type)).toInstance( + self.gc.allocator, + &self.gc.type_registry, + ), + .components = .{ + .ObjectInit = .{ + .object = null, + .properties = properties.items, + }, + }, + }, + ); +} + +fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Index { + const start_location = self.ast.nodes.items(.location)[callee]; + + try self.consume(.Identifier, "Expected property name after `.`"); + const member_name_token = self.current_token.? - 1; + const member_name = self.ast.tokens.items(.lexeme)[member_name_token]; + + const dot_node = try self.ast.appendNode( + .{ + .tag = .Dot, + .location = start_location, + .end_location = undefined, + .components = .{ + .Dot = .{ + .callee = callee, + .identifier = member_name_token, + .value_or_call_or_enum = undefined, + .generic_resolve = null, + .member_type_def = undefined, + .member_kind = undefined, + }, + }, + }, + ); + + // Check that name is a property + const callee_type_def = self.ast.nodes.items(.type_def)[callee]; + if (callee_type_def != null) { + const callee_def_type = callee_type_def.?.def_type; + switch (callee_def_type) { + .String => { + if (try obj.ObjString.memberDef(self, member_name)) |member_type_def| { + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(member_type_def, null) + else + null; + + self.ast.nodes.items(.components)[dot_node].Dot.generic_resolve = generic_resolve; + + const member = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr] + else + member_type_def; + + if (try self.match(.LeftParen)) { + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = member; + const components = self.ast.nodes.items(.components); + components[dot_node].Dot.member_type_def = member.?; + components[dot_node].Dot.member_kind = .Call; + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = try self.call( + can_assign, + dot_node, + ), + }; + + // Node type is the return type of the call + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[components[dot_node].Dot.value_or_call_or_enum.Call]; + } else { + // String has only native functions + self.ast.nodes.items(.components)[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = member; + } + } else { + self.reportError(.property_does_not_exists, "String property doesn't exist."); + } + }, + .Pattern => { + if (try obj.ObjPattern.memberDef(self, member_name)) |member_type_def| { + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(member_type_def, null) + else + null; + + self.ast.nodes.items(.components)[dot_node].Dot.generic_resolve = generic_resolve; + + const member = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr] + else + member_type_def; + + if (try self.match(.LeftParen)) { + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = member; + const components = self.ast.nodes.items(.components); + components[dot_node].Dot.member_type_def = member.?; + components[dot_node].Dot.member_kind = .Call; + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = try self.call( + can_assign, + dot_node, + ), + }; + + // Node type is the return type of the call + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[components[dot_node].Dot.value_or_call_or_enum.Call]; + } else { + // Pattern has only native functions members + self.ast.nodes.items(.components)[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = member; + } + } else { + self.reportError(.property_does_not_exists, "Pattern property doesn't exist."); + } + }, + .Fiber => { + if (try obj.ObjFiber.memberDef(self, member_name)) |member_type_def| { + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(member_type_def, null) + else + null; + + self.ast.nodes.items(.components)[dot_node].Dot.generic_resolve = generic_resolve; + + const member = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr] + else + member_type_def; + + if (try self.match(.LeftParen)) { + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = member; + const components = self.ast.nodes.items(.components); + components[dot_node].Dot.member_kind = .Call; + components[dot_node].Dot.member_type_def = member.?; + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = try self.call( + can_assign, + dot_node, + ), + }; + + // Node type is the return type of the call + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[components[dot_node].Dot.value_or_call_or_enum.Call]; + } else { + // Fiber has only native functions members + self.ast.nodes.items(.components)[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = member; + } + } else { + self.reportError(.property_does_not_exists, "Fiber property doesn't exist."); + } + }, + .Object => { + const obj_def: obj.ObjObject.ObjectDef = callee_type_def.?.resolved_type.?.Object; + var property_type = obj_def.static_fields.get(member_name) orelse obj_def.static_placeholders.get(member_name); + + // Not found, create a placeholder, this is a root placeholder not linked to anything + // TODO: test with something else than a name + if (property_type == null and self.current_object != null and std.mem.eql( + u8, + self.current_object.?.name.lexeme, + obj_def.name.string, + )) { + var placeholder_resolved_type: obj.ObjTypeDef.TypeUnion = .{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, member_name_token), + }; + + placeholder_resolved_type.Placeholder.name = try self.gc.copyString( + self.ast.tokens.items(.lexeme)[member_name_token], + ); + + const placeholder: *obj.ObjTypeDef = try self.gc.type_registry.getTypeDef( + .{ + .optional = false, + .def_type = .Placeholder, + .resolved_type = placeholder_resolved_type, + }, + ); + + if (BuildOptions.debug_placeholders) { + std.debug.print( + "static placeholder @{} for `{s}`\n", + .{ + @intFromPtr(placeholder), + self.ast.tokens.items(.lexeme)[member_name_token], + }, + ); + } + try callee_type_def.?.resolved_type.?.Object.static_placeholders.put(member_name, placeholder); + + property_type = placeholder; + } else if (property_type == null) { + self.reportErrorFmt( + .property_does_not_exists, + "Static property `{s}` does not exists in {s}", + .{ + member_name, + obj_def.name.string, + }, + ); + } + + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(property_type.?, null) + else + null; + + self.ast.nodes.items(.components)[dot_node].Dot.generic_resolve = generic_resolve; + + property_type = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr] + else + property_type; + + // Do we assign it ? + var components = self.ast.nodes.items(.components); + if (can_assign and try self.match(.Equal)) { + components[dot_node].Dot.member_kind = .Value; + components[dot_node].Dot.value_or_call_or_enum = .{ + .Value = try self.expression(false), + }; + self.ast.nodes.items(.type_def)[dot_node] = property_type; + } else if (try self.match(.LeftParen)) { // Do we call it + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = property_type; + components[dot_node].Dot.member_type_def = property_type.?; + const call_node = try self.call(can_assign, dot_node); + components = self.ast.nodes.items(.components); // ptr might have been invalidated + components[dot_node].Dot.member_kind = .Call; + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = call_node, + }; + + // Node type is the return type of the call + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[call_node]; + } else { // access only + components[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = property_type; + } + }, + .ForeignContainer => { + const f_def = callee_type_def.?.resolved_type.?.ForeignContainer; + + if (f_def.buzz_type.get(member_name)) |field| { + if (can_assign and try self.match(.Equal)) { + const components = self.ast.nodes.items(.components); + components[dot_node].Dot.member_kind = .Value; + components[dot_node].Dot.value_or_call_or_enum = .{ + .Value = try self.expression(false), + }; + } else { + self.ast.nodes.items(.components)[dot_node].Dot.member_kind = .Ref; + } + + self.ast.nodes.items(.type_def)[dot_node] = field; + } else { + self.reportErrorFmt( + .property_does_not_exists, + "Property `{s}` does not exists in object `{s}`", + .{ + member_name, + f_def.name.string, + }, + ); + } + }, + .ObjectInstance => { + const object = callee_type_def.?.resolved_type.?.ObjectInstance; + const obj_def = object.resolved_type.?.Object; + + var property_type = obj_def.methods.get(member_name) orelse obj_def.fields.get(member_name) orelse obj_def.placeholders.get(member_name); + + // Else create placeholder + if (property_type == null and self.current_object != null and std.mem.eql(u8, self.current_object.?.name.lexeme, obj_def.name.string)) { + var placeholder_resolved_type = obj.ObjTypeDef.TypeUnion{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, member_name_token), + }; + placeholder_resolved_type.Placeholder.name = try self.gc.copyString(member_name); + + const placeholder = try self.gc.type_registry.getTypeDef( + .{ + .optional = false, + .def_type = .Placeholder, + .resolved_type = placeholder_resolved_type, + }, + ); + + if (BuildOptions.debug_placeholders) { + std.debug.print( + "property placeholder @{} for `{s}.{s}`\n", + .{ + @intFromPtr(placeholder), + object.resolved_type.?.Object.name.string, + self.ast.tokens.items(.lexeme)[member_name_token], + }, + ); + } + try object.resolved_type.?.Object.placeholders.put(member_name, placeholder); + + property_type = placeholder; + } else if (property_type == null) { + self.reportErrorFmt( + .property_does_not_exists, + "Property `{s}` does not exists in object `{s}`", + .{ member_name, obj_def.name.string }, + ); + } + + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(property_type.?, null) + else + null; + + self.ast.nodes.items(.components)[dot_node].Dot.generic_resolve = generic_resolve; + + property_type = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr] + else + property_type; + + // If its a field or placeholder, we can assign to it + // TODO: here get info that field is constant or not + var components = self.ast.nodes.items(.components); + if (can_assign and try self.match(.Equal)) { + components[dot_node].Dot.member_kind = .Value; + components[dot_node].Dot.value_or_call_or_enum = .{ + .Value = try self.expression(false), + }; + self.ast.nodes.items(.type_def)[dot_node] = property_type; + } else if (try self.match(.LeftParen)) { // If it's a method or placeholder we can call it + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = property_type; + components[dot_node].Dot.member_kind = .Call; + components[dot_node].Dot.member_type_def = property_type.?; + const call_node = try self.call(can_assign, dot_node); + components = self.ast.nodes.items(.components); // ptr might have been invalidated + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = call_node, + }; + + // Node type is the return type of the call + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[call_node]; + } else { + components[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = property_type; + } + }, + .ProtocolInstance => { + const protocol = callee_type_def.?.resolved_type.?.ProtocolInstance; + const protocol_def = protocol.resolved_type.?.Protocol; + + var method_type = protocol_def.methods.get(member_name); + + // Else create placeholder + if (method_type == null) { + self.reportErrorFmt( + .property_does_not_exists, + "Method `{s}` does not exists in protocol `{s}`", + .{ + member_name, + protocol_def.name.string, + }, + ); + } + + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(method_type.?, null) + else + null; + + self.ast.nodes.items(.components)[dot_node].Dot.generic_resolve = generic_resolve; + + method_type = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr] + else + method_type; + + // Only call is allowed + var components = self.ast.nodes.items(.components); + if (try self.match(.LeftParen)) { + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = method_type; + components[dot_node].Dot.member_kind = .Call; + components[dot_node].Dot.member_type_def = method_type.?; + const call_node = try self.call(can_assign, dot_node); + components = self.ast.nodes.items(.components); // ptr might have been invalidated + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = call_node, + }; + + // Node type is the return type of the call + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[call_node]; + } else { + components[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = method_type; + } + }, + .Enum => { + const enum_def = callee_type_def.?.resolved_type.?.Enum; + + for (enum_def.cases.items, 0..) |case, index| { + if (std.mem.eql(u8, case, member_name)) { + const enum_instance_resolved_type = obj.ObjTypeDef.TypeUnion{ + .EnumInstance = callee_type_def.?, + }; + + const enum_instance = try self.gc.type_registry.getTypeDef( + .{ + .optional = false, + .def_type = .EnumInstance, + .resolved_type = enum_instance_resolved_type, + }, + ); + + self.ast.nodes.items(.type_def)[dot_node] = enum_instance; + const components = self.ast.nodes.items(.components); + components[dot_node].Dot.member_kind = .EnumCase; + components[dot_node].Dot.value_or_call_or_enum = .{ + .EnumCase = @intCast(index), + }; + break; + } + } + + if (self.ast.nodes.items(.type_def)[dot_node] == null) { + // TODO: reportWithOrigin + self.reportErrorFmt( + .enum_case, + "Enum case `{s}` does not exists.", + .{ + member_name, + }, + ); + } + }, + .EnumInstance => { + // Only available field is `.value` to get associated value + if (!std.mem.eql(u8, member_name, "value")) { + self.reportError(.property_does_not_exists, "Enum provides only field `value`."); + } + + self.ast.nodes.items(.components)[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = callee_type_def.?.resolved_type.?.EnumInstance.resolved_type.?.Enum.enum_type; + }, + .List => { + if (try obj.ObjList.ListDef.member(callee_type_def.?, self, member_name)) |member_type_def| { + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(member_type_def, null) + else + null; + + self.ast.nodes.items(.components)[dot_node].Dot.generic_resolve = generic_resolve; + + const member = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr] + else + member_type_def; + + var components = self.ast.nodes.items(.components); + if (try self.match(.LeftParen)) { + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = member; + components[dot_node].Dot.member_kind = .Call; + components[dot_node].Dot.member_type_def = member.?; + const call_node = try self.call(can_assign, dot_node); + components = self.ast.nodes.items(.components); // ptr might have been invalidated + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = call_node, + }; + + // Node type is the return type of the call + + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[call_node]; + } else { + components[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = member; + } + } else { + self.reportError(.property_does_not_exists, "List property doesn't exist."); + } + }, + .Map => { + if (try obj.ObjMap.MapDef.member(callee_type_def.?, self, member_name)) |member_type_def| { + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(member_type_def, null) + else + null; + + var components = self.ast.nodes.items(.components); + components[dot_node].Dot.generic_resolve = generic_resolve; + + const member = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr] + else + member_type_def; + + if (try self.match(.LeftParen)) { + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = member; + components[dot_node].Dot.member_kind = .Call; + components[dot_node].Dot.member_type_def = member.?; + const call_node = try self.call(can_assign, dot_node); + components = self.ast.nodes.items(.components); // ptr might have been invalidated + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = call_node, + }; + + // Node type is the return type of the call + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[call_node]; + } else { + components[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = member; + } + } else { + self.reportError(.property_does_not_exists, "Map property doesn't exist."); + } + }, + .Placeholder => { + // We know nothing of the field + var placeholder_resolved_type = obj.ObjTypeDef.TypeUnion{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, member_name_token), + }; + + placeholder_resolved_type.Placeholder.name = try self.gc.copyString(member_name); + + var placeholder = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = placeholder_resolved_type, + }, + ); + + try obj.PlaceholderDef.link( + callee_type_def.?, + placeholder, + .FieldAccess, + ); + + const generic_resolve = if (try self.match(.DoubleColon)) + try self.parseGenericResolve(placeholder, null) + else + null; + + var components = self.ast.nodes.items(.components); + components[dot_node].Dot.generic_resolve = generic_resolve; + + placeholder = if (generic_resolve) |gr| + self.ast.nodes.items(.type_def)[gr].? + else + placeholder; + + if (can_assign and try self.match(.Equal)) { + components[dot_node].Dot.member_kind = .Value; + components[dot_node].Dot.value_or_call_or_enum = .{ + .Value = try self.expression(false), + }; + } else if (try self.match(.LeftParen)) { + // `call` will look to the parent node for the function definition + self.ast.nodes.items(.type_def)[dot_node] = placeholder; + components[dot_node].Dot.member_kind = .Call; + components[dot_node].Dot.member_type_def = placeholder; + const call_node = try self.call(can_assign, dot_node); + components = self.ast.nodes.items(.components); // ptr might have been invalidated + components[dot_node].Dot.value_or_call_or_enum = .{ + .Call = call_node, + }; + // TODO: here maybe we invoke instead of call?? + + // Node type is the return type of the call + self.ast.nodes.items(.type_def)[dot_node] = self.ast.nodes.items(.type_def)[call_node]; + } else { + components[dot_node].Dot.member_kind = .Ref; + self.ast.nodes.items(.type_def)[dot_node] = placeholder; + } + }, + else => { + self.reporter.reportErrorFmt( + .field_access, + self.ast.tokens.get(self.ast.nodes.items(.location)[callee]), + "`{s}` is not field accessible", + .{ + (try callee_type_def.?.toStringAlloc(self.gc.allocator)).items, + }, + ); + }, + } + } + + self.ast.nodes.items(.end_location)[dot_node] = self.current_token.? - 1; + + return dot_node; +} + +fn gracefulUnwrap(self: *Self, _: bool, unwrapped: Ast.Node.Index) Error!Ast.Node.Index { + return self.unwrap(false, unwrapped); +} + +fn forceUnwrap(self: *Self, _: bool, unwrapped: Ast.Node.Index) Error!Ast.Node.Index { + return self.unwrap(true, unwrapped); +} + +fn unwrap(self: *Self, force: bool, unwrapped: Ast.Node.Index) Error!Ast.Node.Index { + const unwrapped_type_def = self.ast.nodes.items(.type_def)[unwrapped].?; + + const node = self.ast.appendNode( + .{ + .tag = if (force) .ForceUnwrap else .Unwrap, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try unwrapped_type_def.cloneNonOptional(&self.gc.type_registry), + .components = if (force) + .{ + .ForceUnwrap = .{ + .unwrapped = unwrapped, + .original_type = unwrapped_type_def, + }, + } + else + .{ + .Unwrap = .{ + .unwrapped = unwrapped, + .original_type = unwrapped_type_def, + }, + }, + }, + ); + + if (!force) { + self.opt_jumps = self.opt_jumps orelse std.ArrayList(Precedence).init(self.gc.allocator); + + try self.opt_jumps.?.append( + getRule( + self.ast.tokens.items(.tag)[self.current_token.?], + ).precedence, + ); + } + + return node; +} + +fn unary(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const operator = self.ast.tokens.items(.tag)[start_location]; + const left = try self.parsePrecedence(.Unary, false); + + return self.ast.appendNode( + .{ + .tag = .Unary, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = self.ast.nodes.items(.type_def)[left], + .components = .{ + .Unary = .{ + .operator = operator, + .expression = left, + }, + }, + }, + ); +} + +fn genericResolve(self: *Self, _: bool, expr: Ast.Node.Index) Error!Ast.Node.Index { + return try self.parseGenericResolve(self.ast.nodes.items(.type_def)[expr].?, expr); +} + +fn @"or"(self: *Self, _: bool, left: Ast.Node.Index) Error!Ast.Node.Index { + const operator = self.current_token.? - 1; + const start_location = self.ast.nodes.items(.location)[left]; + const right = try self.parsePrecedence(.And, false); + + return self.ast.appendNode( + .{ + .tag = .Binary, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ .def_type = .Bool }, + ), + .components = .{ + .Binary = .{ + .left = left, + .right = right, + .operator = self.ast.tokens.items(.tag)[operator], + }, + }, + }, + ); +} + +fn @"and"(self: *Self, _: bool, left: Ast.Node.Index) Error!Ast.Node.Index { + const start_location = self.ast.nodes.items(.location)[left]; + const operator = self.current_token.? - 1; + const right = try self.parsePrecedence(.And, false); + + return self.ast.appendNode( + .{ + .tag = .Binary, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ .def_type = .Bool }, + ), + .components = .{ + .Binary = .{ + .left = left, + .right = right, + .operator = self.ast.tokens.items(.tag)[operator], + }, + }, + }, + ); +} + +fn @"if"(self: *Self, is_statement: bool, loop_scope: ?LoopScope) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + try self.consume(.LeftParen, "Expected `(` after `if`."); + + self.beginScope(); + const condition = try self.expression(false); + const condition_type_def = self.ast.nodes.items(.type_def)[condition]; + + var unwrapped_identifier: ?Ast.TokenIndex = null; + var casted_type: ?Ast.Node.Index = null; + if (try self.match(.Arrow)) { // if (opt -> unwrapped) + _ = try self.parseVariable( + false, + try condition_type_def.?.cloneNonOptional(&self.gc.type_registry), + true, + "Expected optional unwrap identifier", + ); + self.markInitialized(); + + unwrapped_identifier = self.current_token.? - 1; + } else if (try self.match(.As)) { // if (expr as casted) + casted_type = try self.parseTypeDef(null, true); + + _ = try self.parseVariable( + false, + try self.ast.nodes.items(.type_def)[casted_type.?].?.toInstance(self.gc.allocator, &self.gc.type_registry), + true, + "Expected casted identifier", + ); + self.markInitialized(); + } + + try self.consume(.RightParen, "Expected `)` after `if` condition."); + + const body = if (is_statement) stmt: { + try self.consume(.LeftBrace, "Expected `{` after `if` condition."); + break :stmt try self.block(loop_scope); + } else try self.expression(false); + + self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + + var else_branch: ?Ast.Node.Index = null; + if (!is_statement or self.check(.Else)) { + try self.consume(.Else, "Expected `else` after inline `if` body."); + + if (try self.match(.If)) { + else_branch = try self.@"if"(is_statement, loop_scope); + } else if (is_statement) { + try self.consume(.LeftBrace, "Expected `{` after `else`."); + + self.beginScope(); + else_branch = try self.block(loop_scope); + self.ast.nodes.items(.ends_scope)[else_branch.?] = try self.endScope(); + } else { + else_branch = try self.expression(false); + } + } + + return self.ast.appendNode( + .{ + .tag = .If, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = if (!is_statement) type_def: { + const type_defs = self.ast.nodes.items(.type_def); + const body_type_def = type_defs[body]; + const else_branch_type_def = if (else_branch) |eb| type_defs[eb] else null; + var if_type_def = if (body_type_def == null or body_type_def.?.def_type == .Void) + else_branch_type_def + else + body_type_def; + + const is_optional = if_type_def.?.optional or body_type_def.?.optional or else_branch_type_def.?.optional or body_type_def.?.def_type == .Void or else_branch_type_def.?.def_type == .Void; + if (is_optional and !if_type_def.?.optional) { + break :type_def try if_type_def.?.cloneOptional(&self.gc.type_registry); + } + + break :type_def if_type_def.?; + } else null, + .components = .{ + .If = .{ + .condition = condition, + .unwrapped_identifier = unwrapped_identifier, + .casted_type = casted_type, + .body = body, + .else_branch = else_branch, + .is_statement = is_statement, + }, + }, + }, + ); +} + +fn ifStatement(self: *Self, loop_scope: ?LoopScope) Error!Ast.Node.Index { + return try self.@"if"(true, loop_scope); +} + +fn inlineIf(self: *Self, _: bool) Error!Ast.Node.Index { + return try self.@"if"(false, null); +} + +inline fn isAs(self: *Self, left: Ast.Node.Index, is_expr: bool) Error!Ast.Node.Index { + const start_location = self.ast.nodes.items(.location)[left]; + const constant = try self.parseTypeDef(null, true); + const type_def = self.ast.nodes.items(.type_def)[constant].?; + + return try self.ast.appendNode( + .{ + .tag = if (is_expr) .Is else .As, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = if (is_expr) + try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Bool, + }, + ) + else + (try type_def.cloneOptional(&self.gc.type_registry)), + .components = if (is_expr) + .{ + .Is = .{ + .left = left, + .constant = constant, + }, + } + else + .{ + .As = .{ + .left = left, + .constant = constant, + }, + }, + }, + ); +} + +fn is(self: *Self, _: bool, left: Ast.Node.Index) Error!Ast.Node.Index { + return self.isAs(left, true); +} + +fn as(self: *Self, _: bool, left: Ast.Node.Index) Error!Ast.Node.Index { + return self.isAs(left, false); +} + +fn string(self: *Self, _: bool) Error!Ast.Node.Index { + const string_token_index = self.current_token.? - 1; + const string_token = self.ast.tokens.get(string_token_index); + const current_token = self.ast.tokens.get(self.current_token.?); + + var string_parser = StringParser.init( + self, + string_token.literal_string.?, + self.script_name, + string_token.line, + string_token.column, + ); + + const string_node = if (string_token.literal_string.?.len > 0) + try string_parser.parse() + else + try self.ast.appendNode( + .{ + .tag = .String, + .location = string_token_index, + .end_location = string_token_index, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .String, + }, + ), + .components = .{ + .String = &[_]Ast.Node.Index{}, + }, + }, + ); + self.ast.nodes.items(.location)[string_node] = string_token_index; + self.ast.nodes.items(.end_location)[string_node] = self.current_token.? - 1; + + // Append again token just after the string so we don't confuse the parser + self.current_token = try self.ast.appendToken(current_token); + + return string_node; +} + +fn namedVariable(self: *Self, name: Ast.TokenIndex, can_assign: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + var var_def: ?*obj.ObjTypeDef = null; + var slot: usize = undefined; + var slot_type: Ast.SlotType = undefined; + var slot_constant = false; + if (try self.resolveLocal(self.current.?, name)) |uslot| { + var_def = self.current.?.locals[uslot].type_def; + slot = uslot; + slot_type = .Local; + slot_constant = self.current.?.locals[uslot].constant; + } else if (try self.resolveUpvalue(self.current.?, name)) |uslot| { + var_def = self.current.?.enclosing.?.locals[self.current.?.upvalues[uslot].index].type_def; + slot = uslot; + slot_type = .UpValue; + slot_constant = self.current.?.enclosing.?.locals[self.current.?.upvalues[uslot].index].constant; + } else if (try self.resolveGlobal(null, self.ast.tokens.items(.lexeme)[name])) |uslot| { + var_def = self.globals.items[uslot].type_def; + slot = uslot; + slot_type = .Global; + slot_constant = self.globals.items[uslot].constant; + } else { + slot = try self.declarePlaceholder(name, null); + var_def = self.globals.items[slot].type_def; + slot_type = .Global; + } + + const value = if (can_assign and try self.match(.Equal)) + try self.expression(false) + else + null; + + return try self.ast.appendNode( + .{ + .tag = .NamedVariable, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = var_def, + .components = .{ + .NamedVariable = .{ + .identifier = name, + .value = value, + .slot = @intCast(slot), + .slot_type = slot_type, + .slot_constant = slot_constant, + }, + }, + }, + ); +} + +fn variable(self: *Self, can_assign: bool) Error!Ast.Node.Index { + return try self.namedVariable(self.current_token.? - 1, can_assign); +} + +fn fun(self: *Self, _: bool) Error!Ast.Node.Index { + return try self.function(null, .Anonymous, null); +} + +fn function( + self: *Self, + name: ?Ast.TokenIndex, + function_type: obj.ObjFunction.FunctionType, + this: ?*obj.ObjTypeDef, +) Error!Ast.Node.Index { + var error_types = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer error_types.shrinkAndFree(error_types.items.len); + var arguments = std.ArrayList(Ast.FunctionType.Argument).init(self.gc.allocator); + defer arguments.shrinkAndFree(arguments.items.len); + var generic_types = std.ArrayList(Ast.TokenIndex).init(self.gc.allocator); + defer generic_types.shrinkAndFree(generic_types.items.len); + + const function_signature = try self.ast.appendNode( + .{ + .tag = .FunctionType, + .location = self.current_token.? - 1, + .end_location = undefined, + .type_def = null, + .components = .{ + .FunctionType = .{ + .name = name, + .return_type = null, + .yield_type = null, + .error_types = undefined, + .arguments = undefined, + .generic_types = undefined, + .lambda = false, + }, + }, + }, + ); + + const function_node = try self.ast.appendNode( + .{ + .tag = .Function, + .location = self.current_token.? - 1, + .end_location = undefined, + .components = .{ + .Function = .{ + .id = Ast.Function.nextId(), + .upvalue_binding = std.AutoArrayHashMap(u8, bool).init(self.gc.allocator), + .function_signature = function_signature, + }, + }, + }, + ); + + try self.beginFrame(function_type, function_node, this); + self.beginScope(); + + // The functiont tyepdef is created in several steps, some need already parsed information like return type + // We create the incomplete type now and enrich it. + const function_def = obj.ObjFunction.FunctionDef{ + .id = obj.ObjFunction.FunctionDef.nextId(), + .script_name = try self.gc.copyString(self.script_name), + .name = if (name) |uname| + try self.gc.copyString(self.ast.tokens.items(.lexeme)[uname]) + else + try self.gc.copyString("anonymous"), + .return_type = undefined, + .yield_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), + .parameters = std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef).init(self.gc.allocator), + .defaults = std.AutoArrayHashMap(*obj.ObjString, Value).init(self.gc.allocator), + .function_type = function_type, + .generic_types = std.AutoArrayHashMap(*obj.ObjString, *obj.ObjTypeDef).init(self.gc.allocator), + .resolved_generics = null, + }; + + const function_resolved_type = obj.ObjTypeDef.TypeUnion{ + .Function = function_def, + }; + + var function_typedef = obj.ObjTypeDef{ + .def_type = .Function, + .resolved_type = function_resolved_type, + }; + + // We replace it with a self.gc.type_registry.getTypeDef pointer at the end + const type_defs = self.ast.nodes.items(.type_def); + type_defs[function_node] = @constCast(&function_typedef); + type_defs[function_signature] = @constCast(&function_typedef); + + // So any reference to a generic in the function's body can be resolved + self.current.?.generics = @constCast(&function_typedef.resolved_type.?.Function.generic_types); + + // Parse generic & argument list + if (function_type == .Test) { + try self.consume(.String, "Expected a string after `test`."); + self.ast.nodes.items(.components)[function_node].Function.test_message = self.current_token.? - 1; + } else { + // Generics + if (try self.match(.DoubleColon)) { + try self.consume(.Less, "Expected `<` at start of generic types list."); + + var i: usize = 0; + while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { + try self.consume(.Identifier, "Expected generic type identifier"); + + const generic_identifier_token = self.current_token.? - 1; + const generic_identifier = try self.gc.copyString(self.ast.tokens.items(.lexeme)[generic_identifier_token]); + if ((self.current.?.generics == null or self.current.?.generics.?.get(generic_identifier) == null) and (self.current_object == null or self.current_object.?.generics == null or self.current_object.?.generics.?.get(generic_identifier) == null)) { + const generic = obj.ObjTypeDef.GenericDef{ + .origin = self.ast.nodes.items(.type_def)[function_node].?.resolved_type.?.Function.id, + .index = i, + }; + const resolved_type = obj.ObjTypeDef.TypeUnion{ .Generic = generic }; + + try function_typedef.resolved_type.?.Function.generic_types.put( + generic_identifier, + try self.gc.type_registry.getTypeDef( + obj.ObjTypeDef{ + .def_type = .Generic, + .resolved_type = resolved_type, + }, + ), + ); + + try generic_types.append(generic_identifier_token); + } else { + self.reportErrorFmt( + .generic_type, + "Generic type `{s}` already defined", + .{ + self.ast.tokens.items(.lexeme)[self.current_token.? - 1], + }, + ); + } + + if (!self.check(.Greater)) { + try self.consume(.Comma, "Expected `,` between generic types"); + } + } + + if (function_typedef.resolved_type.?.Function.generic_types.count() == 0) { + self.reportError( + .generic_type, + "Expected at least one generic type", + ); + } + + try self.consume(.Greater, "Expected `>` after generic types list"); + } + + self.ast.nodes.items(.components)[function_signature].FunctionType.generic_types = generic_types.items; + + try self.consume(.LeftParen, "Expected `(` after function name."); + + // Arguments + var arity: usize = 0; + if (!self.check(.RightParen)) { + while (true) { + arity += 1; + if (arity > 255) { + self.reportErrorAtCurrent( + .arguments_count, + "Can't have more than 255 arguments.", + ); + } + + const argument_type_node = try self.parseTypeDef( + function_typedef.resolved_type.?.Function.generic_types, + true, + ); + + self.ast.nodes.items(.type_def)[argument_type_node] = try (self.ast.nodes.items(.type_def)[ + argument_type_node + ]).?.toInstance( + self.gc.allocator, + &self.gc.type_registry, + ); + + const argument_type = self.ast.nodes.items(.type_def)[argument_type_node].?; + + const slot = try self.parseVariable( + false, + argument_type, + true, // function arguments are constant + "Expected parameter name", + ); + + std.debug.assert(self.current.?.scope_depth > 0); + const local = self.current.?.locals[slot]; + + try function_typedef.resolved_type.?.Function.parameters.put(local.name, local.type_def); + + self.markInitialized(); + + // Default arguments + const default = switch (function_type) { + .Function, .Method, .Anonymous, .Extern => value: { + if (try self.match(.Equal)) { + const expr = try self.expression(false); + const expr_type_def = self.ast.nodes.items(.type_def)[expr]; + + if (expr_type_def != null and expr_type_def.?.def_type == .Placeholder and argument_type.def_type == .Placeholder) { + try obj.PlaceholderDef.link( + argument_type, + expr_type_def.?, + .Assignment, + ); + } + + break :value expr; + } else if (argument_type.optional) { + try function_typedef.resolved_type.?.Function.defaults.put( + local.name, + Value.Null, + ); + } + + break :value null; + }, + else => null, + }; + + try arguments.append( + .{ + .name = local.name_token, + .type = argument_type_node, + .default = default, + }, + ); + + if (default) |dft| { + try function_typedef.resolved_type.?.Function.defaults.put( + local.name, + try self.ast.toValue(dft, self.gc), + ); + } + + if (!try self.match(.Comma)) break; + } + } + + try self.consume(.RightParen, "Expected `)` after function parameters."); + } + + self.ast.nodes.items(.components)[function_signature].FunctionType.arguments = arguments.items; + + // Parse return type + const return_type_node = if (function_type != .Test and try self.match(.Greater)) + try self.parseTypeDef( + function_typedef.resolved_type.?.Function.generic_types, + true, + ) + else + null; + + if (return_type_node) |rtn| { + function_typedef.resolved_type.?.Function.return_type = self.ast.nodes.items(.type_def)[rtn] orelse try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Void, + }, + ); + self.ast.nodes.items(.components)[function_signature].FunctionType.return_type = rtn; + } + + // Parse yield type + const yield_type_node = if (return_type_node != null and function_type.canYield() and (try self.match(.Greater))) yield: { + const yield_type_node = try self.parseTypeDef(function_typedef.resolved_type.?.Function.generic_types, true); + const yield_type = self.ast.nodes.items(.type_def)[yield_type_node].?; + + if (!yield_type.optional and yield_type.def_type != .Void) { + self.reportError(.yield_type, "Expected optional type or void"); + } + + function_typedef.resolved_type.?.Function.yield_type = try yield_type.toInstance(self.gc.allocator, &self.gc.type_registry); + + break :yield yield_type_node; + } else null; + + self.ast.nodes.items(.components)[function_signature].FunctionType.yield_type = yield_type_node; + + if (yield_type_node == null) { + function_typedef.resolved_type.?.Function.yield_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); + } + + if (return_type_node == null) { + function_typedef.resolved_type.?.Function.return_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); + } + + // Error set + if (function_type.canHaveErrorSet() and (try self.match(.BangGreater))) { + var error_typedefs = std.ArrayList(*obj.ObjTypeDef).init(self.gc.allocator); + defer error_typedefs.shrinkAndFree(error_typedefs.items.len); + + const end_token: Token.Type = if (function_type.canOmitBody()) .Semicolon else .LeftBrace; + + while (!self.check(end_token) and !self.check(.Eof)) { + const error_type_node = try self.parseTypeDef( + self.ast.nodes.items(.type_def)[function_node].?.resolved_type.?.Function.generic_types, + true, + ); + const error_type = self.ast.nodes.items(.type_def)[error_type_node].?; + + try error_types.append(error_type_node); + try error_typedefs.append(error_type); + + if (error_type.optional) { + self.reportError(.error_type, "Error type can't be optional"); + } + + if (!self.check(end_token)) { + try self.consume(.Comma, "Expected `,` after error type"); + } + } + + if (error_types.items.len > 0) { + function_typedef.resolved_type.?.Function.error_types = error_typedefs.items; + } + } + + self.ast.nodes.items(.components)[function_signature].FunctionType.error_types = error_types.items; + + // Parse body + if (try self.match(.Arrow)) { + function_typedef.resolved_type.?.Function.lambda = true; + self.ast.nodes.items(.components)[function_signature].FunctionType.lambda = true; + const expr = try self.expression(false); + const expr_type_def = self.ast.nodes.items(.type_def)[expr]; + self.ast.nodes.items(.components)[function_node].Function.body = expr; + + if (return_type_node == null and expr_type_def != null) { + function_typedef.resolved_type.?.Function.return_type = expr_type_def.?; + } + } else if (!function_type.canOmitBody()) { + if (return_type_node != null and !function_type.canOmitReturn()) { + self.reportError(.syntax, "Expected `>` after function argument list."); + } + + try self.consume(.LeftBrace, "Expected `{` before function body."); + const body = try self.block(null); + self.ast.nodes.items(.components)[function_node].Function.body = body; + } + + if (return_type_node == null and !function_typedef.resolved_type.?.Function.lambda) { + function_typedef.resolved_type.?.Function.return_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); + } + + if (function_type == .Extern) { + if (self.flavor.resolveImports()) { + // Search for a dylib/so/dll with the same name as the current script + if (try self.importLibSymbol( + self.script_name, + function_typedef.resolved_type.?.Function.name.string, + )) |native| { + self.ast.nodes.items(.components)[function_node].Function.native = native; + } else { + return error.BuzzNoDll; + } + } + } else { + // Bind upvalues + var i: usize = 0; + while (i < self.current.?.upvalue_count) : (i += 1) { + try self.ast.nodes.items(.components)[function_node].Function.upvalue_binding.put( + self.current.?.upvalues[i].index, + if (self.current.?.upvalues[i].is_local) true else false, + ); + } + } + + self.ast.nodes.items(.type_def)[function_node] = try self.gc.type_registry.getTypeDef(function_typedef); + + return self.endFrame(); +} + +fn pattern(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + const source_slice = self.ast.tokens.items(.literal_string)[start_location].?; + // Replace escaped pattern delimiter with delimiter + const source = try std.mem.replaceOwned( + u8, + self.gc.allocator, + source_slice, + "\\\"", + "\"", + ); + + var err_code: c_int = undefined; + var err_offset: usize = undefined; + const reg = pcre.compile( + source.ptr, + source.len, + // TODO: provide options to user + 0, + &err_code, + &err_offset, + null, + ); + + if (reg == null) { + var location = self.ast.tokens.get(self.current_token.? - 1).clone(); + location.column += @intCast(err_offset + 2); + location.lexeme = location.lexeme[@intCast(2 + err_offset)..@intCast(2 + err_offset + 1)]; + + var err_buf: [256]u8 = undefined; + const err_len = pcre.getErrorMessage( + err_code, + &err_buf, + err_buf.len, + ); + + self.reporter.reportErrorFmt( + .pattern, + location, + "Could not compile pattern, error at {}: {s}", + .{ + err_offset, + err_buf[0..@intCast(err_len)], + }, + ); + return CompileError.Unrecoverable; + } + + const constant = try self.gc.allocateObject( + obj.ObjPattern, + .{ + .source = source, + .pattern = reg.?, + }, + ); + + return try self.ast.appendNode( + .{ + .tag = .Pattern, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .Pattern }), + .components = .{ + .Pattern = constant, + }, + }, + ); +} + +fn asyncCall(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const callable_node = try self.parsePrecedence(.Call, false); + const callable = self.ast.nodes.get(callable_node); + const node = try self.ast.appendNode( + .{ + .tag = .AsyncCall, + .location = start_location, + .end_location = undefined, + .type_def = null, + .components = .{ + .AsyncCall = callable_node, + }, + }, + ); + + // Expression after `&` must either be a call or a dot call + if (callable.tag != .Call and (callable.tag != .Dot or callable.components.Dot.member_kind != .Call)) { + self.reporter.reportErrorAt( + .syntax, + self.ast.tokens.get(callable.location), + "Expected function call after `async`", + ); + + return node; + } + + const call_node = switch (callable.tag) { + .Call => callable_node, + .Dot => callable.components.Dot.value_or_call_or_enum.Call, + else => unreachable, + }; + + const call_components = &self.ast.nodes.items(.components)[call_node]; + + call_components.Call.is_async = true; + + const function_type = call_components.Call.callee_type_def; + + if (function_type.def_type == .Placeholder) { + // create placeholders for return and yield types and link them with .Call and .Yield + const return_placeholder_resolved_type = obj.ObjTypeDef.TypeUnion{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, self.current_token.? - 1), + }; + const return_placeholder = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = return_placeholder_resolved_type, + }, + ); + + try obj.PlaceholderDef.link(function_type, return_placeholder, .Call); + + const yield_placeholder_resolved_type = obj.ObjTypeDef.TypeUnion{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, self.current_token.? - 1), + }; + const yield_placeholder = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = yield_placeholder_resolved_type, + }, + ); + + try obj.PlaceholderDef.link(function_type, yield_placeholder, .Yield); + + const fiber_def = obj.ObjFiber.FiberDef{ + .return_type = return_placeholder, + .yield_type = yield_placeholder, + }; + + const resolved_type = obj.ObjTypeDef.TypeUnion{ + .Fiber = fiber_def, + }; + + self.ast.nodes.items(.type_def)[node] = try self.gc.type_registry.getTypeDef( + .{ + .optional = try self.match(.Question), + .def_type = .Fiber, + .resolved_type = resolved_type, + }, + ); + } else { + if (function_type.def_type != .Function) { + std.debug.print( + "function_type.def_type {}\ncall_components.Call.callee {}\ncallable.tag {}\ncall_node tag {}\n", + .{ + function_type.def_type, + self.ast.nodes.items(.tag)[call_components.Call.callee], + callable.tag, + self.ast.nodes.items(.tag)[call_node], + }, + ); + self.reporter.reportErrorAt( + .callable, + self.ast.tokens.get( + self.ast.nodes.items(.location)[self.ast.nodes.items(.components)[call_node].Call.callee], + ), + "Can't be called", + ); + } else { + const return_type = function_type.resolved_type.?.Function.return_type; + const yield_type = function_type.resolved_type.?.Function.yield_type; + + const fiber_def = obj.ObjFiber.FiberDef{ + .return_type = return_type, + .yield_type = yield_type, + }; + + const resolved_type = obj.ObjTypeDef.TypeUnion{ + .Fiber = fiber_def, + }; + + self.ast.nodes.items(.type_def)[node] = try self.gc.type_registry.getTypeDef( + .{ + .optional = try self.match(.Question), + .def_type = .Fiber, + .resolved_type = resolved_type, + }, + ); + } + } + + self.ast.nodes.items(.end_location)[node] = self.current_token.? - 1; + + return node; +} + +fn resumeFiber(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const fiber_node = try self.parsePrecedence(.Primary, false); + const node = try self.ast.appendNode( + .{ + .tag = .Resume, + .location = start_location, + .end_location = undefined, + .type_def = null, + .components = .{ + .Resume = fiber_node, + }, + }, + ); + + const fiber_type = self.ast.nodes.items(.type_def)[fiber_node]; + + if (fiber_type == null) { + unreachable; + } else if (fiber_type.?.def_type == .Placeholder) { + const yield_placeholder_resolved_type = obj.ObjTypeDef.TypeUnion{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, self.current_token.? - 1), + }; + const yield_placeholder = try (try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = yield_placeholder_resolved_type, + }, + )).cloneOptional(&self.gc.type_registry); + + try obj.PlaceholderDef.link(fiber_type.?, yield_placeholder, .Yield); + + self.ast.nodes.items(.type_def)[node] = yield_placeholder; + } else { + if (fiber_type.?.def_type != .Fiber) { + self.reporter.reportErrorAt( + .resumable, + self.ast.tokens.get(self.ast.nodes.items(.location)[fiber_node]), + "Can't be resumed", + ); + } else { + const fiber = fiber_type.?.resolved_type.?.Fiber; + + // Resume returns null if nothing was yielded and/or fiber reached its return statement + self.ast.nodes.items(.type_def)[node] = fiber.yield_type; + } + } + + self.ast.nodes.items(.end_location)[node] = self.current_token.? - 1; + + return node; +} + +fn resolveFiber(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const fiber = try self.parsePrecedence(.Primary, false); + const node = try self.ast.appendNode( + .{ + .tag = .Resolve, + .location = start_location, + .end_location = undefined, + .type_def = null, + .components = .{ + .Resolve = fiber, + }, + }, + ); + const fiber_type = self.ast.nodes.items(.type_def)[fiber]; + + if (fiber_type == null) { + unreachable; + } else if (fiber_type.?.def_type == .Placeholder) { + const return_placeholder_resolved_type = obj.ObjTypeDef.TypeUnion{ + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, self.current_token.? - 1), + }; + const return_placeholder = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = return_placeholder_resolved_type, + }, + ); + + try obj.PlaceholderDef.link(fiber_type.?, return_placeholder, .Yield); + + self.ast.nodes.items(.type_def)[node] = return_placeholder; + } else { + if (fiber_type.?.def_type != .Fiber) { + self.reporter.reportErrorAt( + .resolvable, + self.ast.tokens.get(self.ast.nodes.items(.location)[fiber]), + "Can't be resolveed", + ); + } else { + const fiber_def = fiber_type.?.resolved_type.?.Fiber; + + self.ast.nodes.items(.type_def)[node] = fiber_def.return_type; + } + } + + self.ast.nodes.items(.end_location)[node] = self.current_token.? - 1; + + return node; +} + +fn yield(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + const expr = try self.parsePrecedence(.Primary, false); + + return try self.ast.appendNode( + .{ + .tag = .Yield, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = self.ast.nodes.items(.type_def)[expr], + .components = .{ + .Yield = expr, + }, + }, + ); +} + +fn range(self: *Self, _: bool, low: Ast.Node.Index) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const int_type = try self.gc.type_registry.getTypeDef(.{ + .def_type = .Integer, + }); + + const high = try self.expression(false); + + self.markInitialized(); + + const list_def = obj.ObjList.ListDef.init(self.gc.allocator, int_type); + const resolved_type = obj.ObjTypeDef.TypeUnion{ + .List = list_def, + }; + + return try self.ast.appendNode( + .{ + .tag = .Range, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef( + .{ + .optional = false, + .def_type = .List, + .resolved_type = resolved_type, + }, + ), + .components = .{ + .Range = .{ + .high = high, + .low = low, + }, + }, + }, + ); +} + +fn typeOfExpression(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const expr = try self.parsePrecedence(.Unary, false); + + return try self.ast.appendNode( + .{ + .tag = .TypeOfExpression, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .Type }), + .components = .{ + .TypeOfExpression = expr, + }, + }, + ); +} + +fn binary(self: *Self, _: bool, left: Ast.Node.Index) Error!Ast.Node.Index { + const start_location = self.ast.nodes.items(.location)[left]; + + const operator_token = self.current_token.? - 1; + const operator = self.ast.tokens.items(.tag)[operator_token]; + const rule = getRule(operator); + + const right = try self.parsePrecedence( + @enumFromInt(@intFromEnum(rule.precedence) + 1), + false, + ); + + const type_defs = self.ast.nodes.items(.type_def); + const right_type_def = type_defs[right]; + const left_type_def = type_defs[left]; + + return try self.ast.appendNode( + .{ + .tag = .Binary, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = switch (operator) { + .QuestionQuestion => right_type_def, + + .Greater, + .Less, + .GreaterEqual, + .LessEqual, + .BangEqual, + .EqualEqual, + => try self.gc.type_registry.getTypeDef(.{ .def_type = .Bool }), + + .Plus => left_type_def orelse right_type_def, + + .ShiftLeft, + .ShiftRight, + .Ampersand, + .Bor, + .Xor, + => try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }), + + .Minus, + .Star, + .Percent, + .Slash, + => try self.gc.type_registry.getTypeDef( + .{ + .def_type = if ((left_type_def != null and left_type_def.?.def_type == .Float) or (right_type_def != null and right_type_def.?.def_type == .Float)) + .Float + else + .Integer, + }, + ), + + else => unreachable, + }, + .components = .{ + .Binary = .{ + .operator = operator, + .left = left, + .right = right, + }, + }, + }, + ); +} + +fn typeExpression(self: *Self, _: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + const type_def = try self.parseTypeDef(null, true); + + try self.consume(.Greater, "Expected `>` after type expression."); + + return try self.ast.appendNode( + .{ + .tag = .TypeExpression, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .Type }), + .components = .{ + .TypeExpression = type_def, + }, + }, + ); +} + +fn funDeclaration(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + var function_type: obj.ObjFunction.FunctionType = .Function; + + if (self.ast.tokens.items(.tag)[self.current_token.? - 1] == .Extern) { + try self.consume(.Fun, "Expected `fun` after `extern`."); + + function_type = .Extern; + } + + try self.consume(.Identifier, "Expected function name."); + const name_token = self.current_token.? - 1; + + const current_function_type_def = self.ast.nodes.items(.type_def)[self.current.?.function_node]; + const is_main = std.mem.eql(u8, self.ast.tokens.items(.lexeme)[name_token], "main") and current_function_type_def != null and current_function_type_def.?.resolved_type.?.Function.function_type == .ScriptEntryPoint; + + if (is_main) { + if (function_type == .Extern) { + self.reportError(.extern_main, "`main` can't be `extern`."); + } + + function_type = .EntryPoint; + } + + const function_node = try self.function(name_token, function_type, null); + const fun_typedef = self.ast.nodes.items(.type_def)[function_node].?; + + if (fun_typedef.resolved_type.?.Function.lambda) { + try self.consume(.Semicolon, "Expected `;` after lambda function"); + } + + if (function_type == .Extern) { + try self.consume(.Semicolon, "Expected `;` after `extern` function declaration."); + } + + // Enforce main signature + const fun_def = fun_typedef.resolved_type.?.Function; + if (is_main) { + var signature_valid = true; + if (fun_def.parameters.count() != 1 or (fun_def.return_type.def_type != .Integer and fun_def.return_type.def_type != .Void)) { + signature_valid = false; + } else { + const first_param = fun_def.parameters.get(fun_def.parameters.keys()[0]); + if (first_param == null or + !(try self.parseTypeDefFrom("[str]")).eql(first_param.?)) + { + signature_valid = false; + } + } + + if (!signature_valid) { + const main_def_str = fun_typedef.toStringAlloc(self.gc.allocator) catch @panic("Out of memory"); + defer main_def_str.deinit(); + self.reporter.reportErrorFmt( + .main_signature, + self.ast.tokens.get(self.ast.nodes.items(.location)[function_node]), + "Expected `main` signature to be `fun main([str] args) > void|int` got {s}", + .{ + main_def_str.items, + }, + ); + } + } + + const slot: usize = try self.declareVariable( + fun_typedef, + name_token, + true, + true, + ); + + self.markInitialized(); + + return try self.ast.appendNode( + .{ + .tag = .FunDeclaration, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = fun_typedef, + .components = .{ + .FunDeclaration = .{ + .function = function_node, + .slot = @intCast(slot), + .slot_type = if (self.current.?.scope_depth == 0) .Global else .Local, + }, + }, + }, + ); +} + +fn exportStatement(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + if (self.check(.Identifier) and (try self.checkAhead(.Semicolon, 0) or try self.checkAhead(.As, 0) or try self.checkAhead(.Dot, 0))) { + try self.consume(.Identifier, "Expected identifier after `export`."); + const identifier = self.current_token.? - 1; + + // Search for a global with that name + if (try self.resolveGlobal(null, self.ast.tokens.items(.lexeme)[identifier])) |slot| { + const global: *Global = &self.globals.items[slot]; + global.referenced = true; + var alias: ?Ast.TokenIndex = null; + + global.exported = true; + if (global.prefix != null or self.check(.As)) { + try self.consume(.As, "Expected `as` after prefixed global."); + try self.consume(.Identifier, "Expected identifier after `as`."); + + global.export_alias = self.ast.tokens.items(.lexeme)[self.current_token.? - 1]; + alias = self.current_token.? - 1; + } + + try self.consume(.Semicolon, "Expected `;` after export."); + + return try self.ast.appendNode( + .{ + .tag = .Export, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = global.type_def, + .components = .{ + .Export = .{ + .identifier = identifier, + .alias = alias, + .declaration = null, + }, + }, + }, + ); + } + } else { + self.exporting = true; + if (try self.declaration()) |decl| { + self.globals.items[self.globals.items.len - 1].referenced = true; + + self.exporting = false; + + return try self.ast.appendNode( + .{ + .tag = .Export, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = self.ast.nodes.items(.type_def)[decl], + .components = .{ + .Export = .{ + .identifier = null, + .alias = null, + .declaration = decl, + }, + }, + }, + ); + } + self.exporting = false; + } + + self.reportError(.syntax, "Expected identifier or declaration."); + + return try self.ast.appendNode( + .{ + .tag = .Export, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = null, + .components = .{ + .Export = .{ + .identifier = self.current_token.? - 1, + .alias = null, + .declaration = null, + }, + }, + }, + ); +} + +fn objectDeclaration(self: *Self) Error!Ast.Node.Index { + if (self.current.?.scope_depth > 0) { + self.reportError(.syntax, "Object must be defined at top-level."); + } + + const start_location = self.current_token.? - 1; + + // Conforms to protocols? + var protocols = std.AutoHashMap(*obj.ObjTypeDef, void).init(self.gc.allocator); + var protocol_nodes = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer protocol_nodes.shrinkAndFree(protocol_nodes.items.len); + var protocol_count: usize = 0; + if (try self.match(.LeftParen)) { + while (!self.check(.RightParen) and !self.check(.Eof)) : (protocol_count += 1) { + if (protocol_count > 255) { + self.reportError(.protocols_count, "Can't conform to more than 255 protocols"); + } + + try self.consume(.Identifier, "Expected protocol identifier"); + + const protocol_node = try self.parseUserType(false); + const protocol = self.ast.nodes.items(.type_def)[protocol_node].?; + + if (protocols.get(protocol) != null) { + self.reportErrorFmt( + .already_conforming_protocol, + "Already conforming to `{s}`.", + .{ + protocol.resolved_type.?.Protocol.name.string, + }, + ); + } + + try protocols.put(protocol, {}); + try protocol_nodes.append(protocol_node); + + if (!(try self.match(.Comma))) { + break; + } + } + + try self.consume(.RightParen, "Expected `)` after protocols list"); + } + + // Get object name + try self.consume(.Identifier, "Expected object name."); + const object_name_token = self.current_token.? - 1; + const object_name = self.ast.tokens.get(object_name_token).clone(); + + // Qualified name to avoid cross script collision + const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); + defer self.gc.allocator.free(qualifier); + var qualified_object_name = std.ArrayList(u8).init(self.gc.allocator); + defer qualified_object_name.deinit(); + try qualified_object_name.writer().print("{s}.{s}", .{ qualifier, object_name.lexeme }); + + // Create a placeholder for self-reference which will be resolved at the end when declaring the object + const placeholder_index = try self.declarePlaceholder(object_name_token, null); + const object_placeholder = self.globals.items[placeholder_index].type_def; + + var object_def = obj.ObjObject.ObjectDef.init( + self.gc.allocator, + object_name, + try self.gc.copyString(object_name.lexeme), + try self.gc.copyString(qualified_object_name.items), + false, + ); + + object_def.conforms_to.deinit(); + object_def.conforms_to = protocols; + + const resolved_type = obj.ObjTypeDef.TypeUnion{ .Object = object_def }; + + // Create type + var object_type = obj.ObjTypeDef{ + .def_type = .Object, + .resolved_type = resolved_type, + }; + + const object_frame = ObjectFrame{ + .name = object_name, + .type_def = object_placeholder, + .generics = &object_type.resolved_type.?.Object.generic_types, + }; + + self.current_object = object_frame; + + // Parse generic types + var generics = std.ArrayList(Ast.TokenIndex).init(self.gc.allocator); + defer generics.shrinkAndFree(generics.items.len); + if (try self.match(.DoubleColon)) { + try self.consume(.Less, "Expected generic types list after `::`"); + var i: usize = 0; + while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { + try self.consume(.Identifier, "Expected generic type identifier"); + + const generic_identifier_token = self.current_token.? - 1; + const generic_identifier = try self.gc.copyString(self.ast.tokens.items(.lexeme)[generic_identifier_token]); + if (object_type.resolved_type.?.Object.generic_types.get(generic_identifier) == null) { + const generic = obj.ObjTypeDef.GenericDef{ + .origin = object_def.id, + .index = i, + }; + const resolved_generic_type = obj.ObjTypeDef.TypeUnion{ .Generic = generic }; + try object_type.resolved_type.?.Object.generic_types.put( + generic_identifier, + try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Generic, + .resolved_type = resolved_generic_type, + }, + ), + ); + + try generics.append(generic_identifier_token); + } else { + self.reportErrorFmt( + .generic_type, + "Generic type `{s}` already defined", + .{ + self.ast.tokens.items(.lexeme)[self.current_token.? - 1], + }, + ); + } + + if (!self.check(.Greater)) { + try self.consume(.Comma, "Expected `,` between generic types"); + } + } + + if (object_type.resolved_type.?.Object.generic_types.count() == 0) { + self.reportError( + .generic_type, + "Expected at least one generic type", + ); + } + + try self.consume(.Greater, "Expected `>` after generic types list"); + } + + self.beginScope(); + + // Body + try self.consume(.LeftBrace, "Expected `{` before object body."); + + var members = std.ArrayList(Ast.ObjectDeclaration.Member).init(self.gc.allocator); + defer members.shrinkAndFree(members.items.len); + + // To avoid using the same name twice + var fields = std.StringArrayHashMap(void).init(self.gc.allocator); + defer fields.deinit(); + + // Members types + var properties_type = std.StringHashMap(*obj.ObjTypeDef).init(self.gc.allocator); + + // Docblocks + while (!self.check(.RightBrace) and !self.check(.Eof)) { + const docblock = if (try self.match(.Docblock)) + self.current_token.? - 1 + else + null; + + const static = try self.match(.Static); + + if (try self.match(.Fun)) { + const method_token = self.current_token.?; + const method_node = try self.method( + false, + if (static) + object_placeholder + else + try object_placeholder.toInstance(self.gc.allocator, &self.gc.type_registry), + ); + + const method_type_def = self.ast.nodes.items(.type_def)[method_node]; + const method_name = method_type_def.?.resolved_type.?.Function.name.string; + + if (fields.get(method_name) != null) { + self.reportError(.property_already_exists, "A member with that name already exists."); + } + + // Does a placeholder exists for this name ? + if (static) { + if (object_type.resolved_type.?.Object.static_placeholders.get(method_name)) |placeholder| { + try self.resolvePlaceholder(placeholder, method_type_def.?, true); + + // Now we know the placeholder was a method + if (BuildOptions.debug_placeholders) { + std.debug.print( + "resolved static method for `{s}`\n", + .{ + method_name, + }, + ); + } + _ = object_type.resolved_type.?.Object.static_placeholders.remove(method_name); + } + } else { + if (object_type.resolved_type.?.Object.placeholders.get(method_name)) |placeholder| { + try self.resolvePlaceholder(placeholder, method_type_def.?, true); + + // Now we know the placeholder was a method + if (BuildOptions.debug_placeholders) { + std.debug.print( + "resolved method placeholder for `{s}`\n", + .{ + method_name, + }, + ); + } + _ = object_type.resolved_type.?.Object.placeholders.remove(method_name); + } + } + + if (static) { + try object_type.resolved_type.?.Object.static_fields.put(method_name, method_type_def.?); + try object_type.resolved_type.?.Object.static_fields_locations.put( + method_name, + self.ast.tokens.get(method_token), + ); + } else { + try object_type.resolved_type.?.Object.methods.put( + method_name, + method_type_def.?, + ); + try object_type.resolved_type.?.Object.methods_locations.put( + method_name, + self.ast.tokens.get(method_token), + ); + } + + try members.append( + .{ + .name = method_token, + .docblock = docblock, + .method = true, + .method_or_default_value = method_node, + }, + ); + try fields.put(method_name, {}); + try properties_type.put(method_name, self.ast.nodes.items(.type_def)[method_node].?); + } else { + // TODO: constant object properties + // const constant = try self.match(.Const); + const property_type = try self.parseTypeDef(null, true); + const property_type_def = self.ast.nodes.items(.type_def)[property_type]; + + try self.consume(.Identifier, "Expected property name."); + const property_token = self.current_token.? - 1; + const property_name = self.ast.tokens.get(property_token); + + if (fields.get(property_name.lexeme) != null) { + self.reportError(.property_already_exists, "A member with that name already exists."); + } + + // Does a placeholder exists for this name ? + if (static) { + if (object_type.resolved_type.?.Object.static_placeholders.get(property_name.lexeme)) |placeholder| { + try self.resolvePlaceholder(placeholder, property_type_def.?, false); + + // Now we know the placeholder was a field + if (BuildOptions.debug_placeholders) { + std.debug.print( + "resolved static property placeholder for `{s}`\n", + .{ + property_name.lexeme, + }, + ); + } + _ = object_type.resolved_type.?.Object.static_placeholders.remove(property_name.lexeme); + } + } else { + if (object_type.resolved_type.?.Object.placeholders.get(property_name.lexeme)) |placeholder| { + try self.resolvePlaceholder(placeholder, property_type_def.?, false); + + // Now we know the placeholder was a field + if (BuildOptions.debug_placeholders) { + std.debug.print( + "resolved property placeholder for `{s}`\n", + .{ + property_name.lexeme, + }, + ); + } + _ = object_type.resolved_type.?.Object.placeholders.remove(property_name.lexeme); + } + } + + const default = if (try self.match(.Equal)) + try self.expression(false) + else if (property_type_def.?.optional) + try self.ast.appendNode( + .{ + .tag = .Null, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .components = .{ + .Null = {}, + }, + .type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), + }, + ) + else + null; + + if (default != null and !self.ast.isConstant(default.?)) { + self.reporter.reportErrorAt( + .constant_default, + self.ast.tokens.get(self.ast.nodes.items(.location)[default.?]), + "Default value must be constant", + ); + } + + if (static) { + if (!self.check(.RightBrace) or self.check(.Semicolon)) { + try self.consume(.Semicolon, "Expected `;` after static property definition."); + } + } else { + if (!self.check(.RightBrace) or self.check(.Comma)) { + try self.consume(.Comma, "Expected `,` after property definition."); + } + } + + if (static) { + try object_type.resolved_type.?.Object.static_fields.put( + property_name.lexeme, + self.ast.nodes.items(.type_def)[property_type].?, + ); + + try object_type.resolved_type.?.Object.static_fields_locations.put( + property_name.lexeme, + property_name, + ); + } else { + std.debug.assert(!object_type.optional); + try object_type.resolved_type.?.Object.fields.put( + property_name.lexeme, + self.ast.nodes.items(.type_def)[property_type].?, + ); + try object_type.resolved_type.?.Object.fields_locations.put( + property_name.lexeme, + property_name, + ); + + if (default != null) { + try object_type.resolved_type.?.Object.fields_defaults.put(property_name.lexeme, {}); + } + } + + try members.append( + .{ + .name = property_token, + .docblock = docblock, + .method = false, + .method_or_default_value = default, + }, + ); + try fields.put(property_name.lexeme, {}); + try properties_type.put( + property_name.lexeme, + self.ast.nodes.items(.type_def)[property_type].?, + ); + } + } + + try self.consume(.RightBrace, "Expected `}` after object body."); + + const scope_end = try self.endScope(); + + const slot = try self.declareVariable( + &object_type, // Should resolve object_name_tokenect_placeholder and be discarded + object_name_token, + true, // Object is always constant + true, + ); + + std.debug.assert(!object_type.optional); + + self.markInitialized(); + + const node = self.ast.appendNode( + .{ + .tag = .ObjectDeclaration, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = object_placeholder, + .ends_scope = scope_end, + .components = .{ + .ObjectDeclaration = .{ + .name = object_name_token, + .slot = @intCast(slot), + .protocols = protocol_nodes.items, + .generics = generics.items, + .members = members.items, + }, + }, + }, + ); + + std.debug.assert(object_placeholder.resolved_type.?.Object.placeholders.count() == 0 or object_placeholder.resolved_type.?.Object.static_placeholders.count() == 0); + + self.current_object = null; + + return node; +} + +fn method(self: *Self, abstract: bool, this: *obj.ObjTypeDef) Error!Ast.Node.Index { + try self.consume(.Identifier, "Expected method name."); + + return try self.function( + self.current_token.? - 1, + if (abstract) .Abstract else .Method, + this, + ); +} + +fn protocolDeclaration(self: *Self) Error!Ast.Node.Index { + if (self.current.?.scope_depth > 0) { + self.reportError(.syntax, "Protocol must be defined at top-level."); + } + + const start_location = self.current_token.? - 1; + + // Get protocol name + try self.consume(.Identifier, "Expected protocol name."); + const protocol_name = self.current_token.? - 1; + + // Qualified name to avoid cross script collision + const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); + defer self.gc.allocator.free(qualifier); + var qualified_protocol_name = std.ArrayList(u8).init(self.gc.allocator); + defer qualified_protocol_name.deinit(); + try qualified_protocol_name.writer().print( + "{s}.{s}", + .{ + qualifier, + self.ast.tokens.items(.lexeme)[protocol_name], + }, + ); + + // Create a placeholder for self-reference which will be resolved at the end when declaring the object + const placeholder_index = try self.declarePlaceholder(protocol_name, null); + const protocol_placeholder = self.globals.items[placeholder_index].type_def; + + const protocol_def = obj.ObjObject.ProtocolDef.init( + self.gc.allocator, + self.ast.tokens.get(protocol_name), + try self.gc.copyString(self.ast.tokens.items(.lexeme)[protocol_name]), + try self.gc.copyString(qualified_protocol_name.items), + ); + + const resolved_type = obj.ObjTypeDef.TypeUnion{ .Protocol = protocol_def }; + + // Create type + var protocol_type: obj.ObjTypeDef = .{ + .def_type = .Protocol, + .resolved_type = resolved_type, + }; + + self.beginScope(); + + // Body + try self.consume(.LeftBrace, "Expected `{` before protocol body."); + + var fields = std.StringHashMap(void).init(self.gc.allocator); + defer fields.deinit(); + var methods = std.ArrayList(Ast.ProtocolDeclaration.Method).init(self.gc.allocator); + defer methods.shrinkAndFree(methods.items.len); + while (!self.check(.RightBrace) and !self.check(.Eof)) { + const docblock = if (try self.match(.Docblock)) + self.current_token.? - 1 + else + null; + + try self.consume(.Fun, "Expected method definition"); + + const method_name_token = self.current_token.?; + const method_node = try self.method( + true, + try protocol_placeholder.toInstance(self.gc.allocator, &self.gc.type_registry), + ); + const method_type_def = self.ast.nodes.items(.type_def)[method_node]; + + try self.consume(.Semicolon, "Expected `;` after method definition"); + + const method_name = method_type_def.?.resolved_type.?.Function.name.string; + + if (fields.get(method_name) != null) { + self.reportError(.property_already_exists, "A method with that name already exists."); + } + + try protocol_type.resolved_type.?.Protocol.methods.put( + method_name, + method_type_def.?, + ); + + // FIXME: we should not need this, the VM has a reference to the AST + try protocol_type.resolved_type.?.Protocol.methods_locations.put( + method_name, + self.ast.tokens.get(method_name_token), + ); + + try fields.put(method_name, {}); + try methods.append( + .{ + .docblock = docblock, + .method = method_node, + }, + ); + } + + try self.consume(.RightBrace, "Expected `}` after protocol body."); + + const scope_end = try self.endScope(); + + const slot = try self.declareVariable( + &protocol_type, // Should resolve protocol_placeholder and be discarded + protocol_name, + true, // Protocol is always constant + true, + ); + + std.debug.assert(!protocol_type.optional); + + self.markInitialized(); + + return try self.ast.appendNode( + .{ + .tag = .ProtocolDeclaration, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = protocol_placeholder, + .ends_scope = scope_end, + .components = .{ + .ProtocolDeclaration = .{ + .name = protocol_name, + .slot = @intCast(slot), + .methods = methods.items, + }, + }, + }, + ); +} + +fn enumDeclaration(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + if (self.current.?.scope_depth > 0) { + self.reportError(.syntax, "Enum must be defined at top-level."); + } + + const enum_case_type_node = + if (try self.match(.LeftParen)) + try self.parseTypeDef(null, true) + else + null; + + if (enum_case_type_node != null) { + try self.consume(.RightParen, "Expected `)` after enum type."); + } + + const enum_case_type = if (enum_case_type_node) |enum_type| + try self.ast.nodes.items(.type_def)[enum_type].?.toInstance(self.gc.allocator, &self.gc.type_registry) + else + try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }); + + try self.consume(.Identifier, "Expected enum name."); + const enum_name = self.current_token.? - 1; + const enum_name_lexeme = self.ast.tokens.items(.lexeme)[enum_name]; + + // Qualified name to avoid cross script collision + const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); + defer self.gc.allocator.free(qualifier); + var qualified_name = std.ArrayList(u8).init(self.gc.allocator); + defer qualified_name.deinit(); + try qualified_name.writer().print( + "{s}.{s}", + .{ qualifier, enum_name_lexeme }, + ); + + const enum_def = obj.ObjEnum.EnumDef.init( + self.gc.allocator, + try self.gc.copyString(enum_name_lexeme), + try self.gc.copyString(qualified_name.items), + enum_case_type, + ); + + const enum_resolved = obj.ObjTypeDef.TypeUnion{ .Enum = enum_def }; + + const enum_type = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Enum, + .resolved_type = enum_resolved, + }, + ); + + const slot: usize = try self.declareVariable( + enum_type, + enum_name, + true, + true, + ); + self.markInitialized(); + + try self.consume(.LeftBrace, "Expected `{` before enum body."); + + var cases = std.ArrayList(Ast.Enum.Case).init(self.gc.allocator); + defer cases.shrinkAndFree(cases.items.len); + var picked = std.ArrayList(bool).init(self.gc.allocator); + var case_index: i32 = 0; + while (!self.check(.RightBrace) and !self.check(.Eof)) : (case_index += 1) { + if (case_index > 255) { + self.reportError(.enum_cases_count, "Too many enum cases."); + } + + const docblock = if (try self.match(.Docblock)) + self.current_token.? - 1 + else + null; + + try self.consume(.Identifier, "Expected case name."); + const case_name_token = self.current_token.? - 1; + const case_name = self.ast.tokens.items(.lexeme)[case_name_token]; + + if (enum_case_type_node != null and (enum_case_type.def_type != .String or self.check(.Equal))) { + try self.consume(.Equal, "Expected `=` after case name."); + + try cases.append( + .{ + .name = case_name_token, + .docblock = docblock, + .value = try self.expression(false), + }, + ); + try picked.append(true); + } else { + if (enum_case_type.def_type == .Integer) { + try cases.append( + .{ + .name = case_name_token, + .docblock = docblock, + .value = try self.ast.appendNode( + .{ + .tag = .Integer, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }), + .components = .{ + .Integer = case_index, + }, + }, + ), + }, + ); + } else { + try cases.append( + .{ + .name = case_name_token, + .docblock = docblock, + .value = try self.ast.appendNode( + .{ + .tag = .StringLiteral, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .String }), + .components = .{ + .StringLiteral = try self.gc.copyString(case_name), + }, + }, + ), + }, + ); + } + + try picked.append(false); + } + + try enum_type.resolved_type.?.Enum.cases.append(case_name); + + if (!self.check(.RightBrace)) { + try self.consume(.Comma, "Expected `,` after case definition."); + } + } + + try self.consume(.RightBrace, "Expected `}` after enum body."); + + if (case_index == 0) { + self.reportError(.enum_cases_count, "Enum must have at least one case."); + } + + const node = try self.ast.appendNode( + .{ + .tag = .Enum, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = enum_type, + .components = .{ + .Enum = .{ + .name = enum_name, + .case_type = enum_case_type_node, + .slot = @intCast(slot), + .cases = cases.items, + }, + }, + }, + ); + + // Generate the enum constant + var @"enum" = try self.gc.allocateObject( + obj.ObjEnum, + obj.ObjEnum.init(self.gc.allocator, enum_type), + ); + + var obj_cases = std.ArrayList(Value).init(self.gc.allocator); + defer obj_cases.shrinkAndFree(obj_cases.items.len); + for (cases.items, 0..) |case, idx| { + try obj_cases.append( + if (case.value) |case_value| + try self.ast.toValue(case_value, self.gc) + else if (enum_type.def_type == .Integer) + Value.fromInteger(@intCast(idx)) + else if (enum_type.def_type == .String) + (try self.gc.copyString(self.ast.tokens.items(.lexeme)[case.name])).toValue() + else + unreachable, + ); + } + + @"enum".cases = obj_cases.items; + + enum_type.resolved_type.?.Enum.value = @"enum"; + self.ast.nodes.items(.value)[node] = @"enum".toValue(); + + return node; +} + +fn varDeclaration( + self: *Self, + identifier_consumed: bool, + parsed_type: ?Ast.Node.Index, + terminator: DeclarationTerminator, + constant: bool, + should_assign: bool, +) Error!Ast.Node.Index { + var var_type = if (parsed_type) |ptype| + try self.ast.nodes.items(.type_def)[ptype].?.toInstance( + self.gc.allocator, + &self.gc.type_registry, + ) + else + try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Void, + }, + ); + + const slot: usize = try self.parseVariable( + identifier_consumed, + var_type, + constant, + "Expected variable name.", + ); + + const name = self.current_token.? - 1; + const start_location = name; + + const value = if (should_assign and try self.match(.Equal)) + try self.expression(false) + else + null; + const value_type_def = if (value) |val| + self.ast.nodes.items(.type_def)[val] + else + null; + + if (should_assign and value == null and (parsed_type == null or !self.ast.nodes.items(.type_def)[parsed_type.?].?.optional)) { + self.reporter.reportErrorAt( + .syntax, + self.ast.tokens.get(self.current_token.? - 1), + "Expected variable initial value", + ); + } + + if (var_type.def_type == .Placeholder and value != null and value_type_def != null and value_type_def.?.def_type == .Placeholder) { + try obj.PlaceholderDef.link(var_type, value_type_def.?, .Assignment); + } + + if (parsed_type == null and value != null and value_type_def != null) { + var_type = value_type_def.?; + + if (self.current.?.scope_depth == 0) { + self.globals.items[slot].type_def = var_type; + } else { + self.current.?.locals[slot].type_def = var_type; + } + } else if (parsed_type == null) { + self.reporter.reportErrorAt( + .inferred_type, + self.ast.tokens.get(start_location), + "Could not infer variable type.", + ); + } + + switch (terminator) { + .OptComma => _ = try self.match(.Comma), + .Comma => try self.consume(.Comma, "Expected `,` after variable declaration."), + .Semicolon => try self.consume(.Semicolon, "Expected `;` after variable declaration."), + .Nothing => {}, + } + + self.markInitialized(); + + return try self.ast.appendNode( + .{ + .tag = .VarDeclaration, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = var_type, + .components = .{ + .VarDeclaration = .{ + .name = name, + .value = value, + .type = parsed_type, + .constant = constant, + .slot = @intCast(slot), + .slot_type = if (self.current.?.scope_depth > 0) .Local else .Global, + }, + }, + }, + ); +} + +// Same as varDeclaration but does not parse anything (useful when we need to declare a variable that need to exists but is not exposed to the user) +fn implicitVarDeclaration(self: *Self, name: Ast.TokenIndex, parsed_type: *obj.ObjTypeDef, constant: bool) Error!Ast.Node.Index { + const var_type = try parsed_type.toInstance(self.gc.allocator, &self.gc.type_registry); + const slot = try self.declareVariable(var_type, name, constant, true); + self.markInitialized(); + + return try self.ast.appendNode( + .{ + .tag = .VarDeclaration, + .location = self.current_token.? - 1, + .end_location = self.current_token.? - 1, + .type_def = var_type, + .components = .{ + .VarDeclaration = .{ + .name = name, + .value = null, + .type = null, + .constant = constant, + .slot = @intCast(slot), + .slot_type = if (self.current.?.scope_depth > 0) .Local else .Global, + }, + }, + }, + ); +} + +fn listDeclaration(self: *Self, constant: bool) Error!Ast.Node.Index { + const current_function_type = self.ast.nodes.items(.type_def)[self.current.?.function_node].?.resolved_type.?.Function.function_type; + + if (self.check(.Less) and (self.current.?.scope_depth > 0 or current_function_type == .Repl)) { + // Its a list expression + return try self.expressionStatement(true); + } + + return try self.varDeclaration( + false, + try self.parseListType(null), + .Semicolon, + constant, + true, + ); +} + +fn mapDeclaration(self: *Self, constant: bool) Error!Ast.Node.Index { + const current_function_type = self.ast.nodes.items(.type_def)[self.current.?.function_node].?.resolved_type.?.Function.function_type; + + if (self.check(.Less) and (self.current.?.scope_depth > 0 or current_function_type == .Repl)) { + // Its a map expression + return try self.expressionStatement(true); + } + + return try self.varDeclaration( + false, + try self.parseMapType(null), + .Semicolon, + constant, + true, + ); +} + +// `test` is just like a function but we don't parse arguments and we don't care about its return type +fn testStatement(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + // We can't consume the name because declareVariable will do it + const name_token = self.current_token.?; + + var function_def_placeholder = obj.ObjTypeDef{ + .def_type = .Function, + }; + + self.test_count += 1; + + const slot = try self.declareVariable( + &function_def_placeholder, + name_token, + true, + true, + ); + + self.markInitialized(); + + const function_node = try self.function(name_token, .Test, null); + const function_type_def = self.ast.nodes.items(.type_def)[function_node]; + + if (function_type_def) |type_def| { + self.globals.items[slot].type_def = type_def; + } + + return try self.ast.appendNode( + .{ + .tag = .VarDeclaration, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = function_type_def, + .components = .{ + .VarDeclaration = .{ + .name = name_token, + .value = function_node, + .type = self.ast.nodes.items(.components)[function_node].Function.function_signature, + .constant = true, + .slot = @intCast(slot), + .slot_type = .Global, + }, + }, + }, + ); +} +fn searchPaths(self: *Self, file_name: []const u8) ![][]const u8 { + var paths = std.ArrayList([]const u8).init(self.gc.allocator); + defer paths.shrinkAndFree(paths.items.len); + + for (search_paths) |path| { + const filled = try std.mem.replaceOwned(u8, self.gc.allocator, path, "?", file_name); + defer self.gc.allocator.free(filled); + const suffixed = try std.mem.replaceOwned(u8, self.gc.allocator, filled, "!", "buzz"); + defer self.gc.allocator.free(suffixed); + const prefixed = try std.mem.replaceOwned(u8, self.gc.allocator, suffixed, "$", buzz_lib_path()); + + try paths.append(prefixed); + } + + return paths.items; +} + +fn searchLibPaths(self: *Self, file_name: []const u8) !std.ArrayList([]const u8) { + var paths = std.ArrayList([]const u8).init(self.gc.allocator); + + for (lib_search_paths) |path| { + const filled = try std.mem.replaceOwned(u8, self.gc.allocator, path, "?", file_name); + defer self.gc.allocator.free(filled); + const suffixed = try std.mem.replaceOwned( + u8, + self.gc.allocator, + filled, + "!", + switch (builtin.os.tag) { + .linux, .freebsd, .openbsd => "so", + .windows => "dll", + .macos, .tvos, .watchos, .ios => "dylib", + else => unreachable, + }, + ); + defer self.gc.allocator.free(suffixed); + const prefixed = try std.mem.replaceOwned(u8, self.gc.allocator, suffixed, "$", buzz_lib_path()); + + try paths.append(prefixed); + } + + for (user_library_paths orelse &[_][]const u8{}) |path| { + var filled = std.ArrayList(u8).init(self.gc.allocator); + + try filled.writer().print( + "{s}{s}{s}.{s}", + .{ + path, + if (!std.mem.endsWith(u8, path, "/")) "/" else "", + file_name, + switch (builtin.os.tag) { + .linux, .freebsd, .openbsd => "so", + .windows => "dll", + .macos, .tvos, .watchos, .ios => "dylib", + else => unreachable, + }, + }, + ); + + filled.shrinkAndFree(filled.items.len); + + try paths.append(filled.items); + + var prefixed_filled = std.ArrayList(u8).init(self.gc.allocator); + + try prefixed_filled.writer().print( + "{s}{s}lib{s}.{s}", + .{ + path, + if (!std.mem.endsWith(u8, path, "/")) "/" else "", + file_name, + switch (builtin.os.tag) { + .linux, .freebsd, .openbsd => "so", + .windows => "dll", + .macos, .tvos, .watchos, .ios => "dylib", + else => unreachable, + }, + }, + ); + + prefixed_filled.shrinkAndFree(prefixed_filled.items.len); + + try paths.append(prefixed_filled.items); + } + + paths.shrinkAndFree(paths.items.len); + return paths; +} + +fn searchZdefLibPaths(self: *Self, file_name: []const u8) !std.ArrayList([]const u8) { + var paths = std.ArrayList([]const u8).init(self.gc.allocator); + + for (zdef_search_paths) |path| { + const filled = try std.mem.replaceOwned(u8, self.gc.allocator, path, "?", file_name); + defer self.gc.allocator.free(filled); + const suffixed = try std.mem.replaceOwned( + u8, + self.gc.allocator, + filled, + "!", + switch (builtin.os.tag) { + .linux, .freebsd, .openbsd => "so", + .windows => "dll", + .macos, .tvos, .watchos, .ios => "dylib", + else => unreachable, + }, + ); + try paths.append(suffixed); + } + + for (Self.user_library_paths orelse &[_][]const u8{}) |path| { + var filled = std.ArrayList(u8).init(self.gc.allocator); + + try filled.writer().print( + "{s}{s}{s}.{s}", + .{ + path, + if (!std.mem.endsWith(u8, path, "/")) "/" else "", + file_name, + switch (builtin.os.tag) { + .linux, .freebsd, .openbsd => "so", + .windows => "dll", + .macos, .tvos, .watchos, .ios => "dylib", + else => unreachable, + }, + }, + ); + + filled.shrinkAndFree(filled.items.len); + + try paths.append(filled.items); + + var prefixed_filled = std.ArrayList(u8).init(self.gc.allocator); + + try prefixed_filled.writer().print( + "{s}{s}lib{s}.{s}", + .{ + path, + if (!std.mem.endsWith(u8, path, "/")) "/" else "", + file_name, + switch (builtin.os.tag) { + .linux, .freebsd, .openbsd => "so", + .windows => "dll", + .macos, .tvos, .watchos, .ios => "dylib", + else => unreachable, + }, + }, + ); + + prefixed_filled.shrinkAndFree(prefixed_filled.items.len); + + try paths.append(prefixed_filled.items); + } + + return paths; +} + +fn importScript( + self: *Self, + file_name: []const u8, + prefix: ?[]const u8, + imported_symbols: *std.StringHashMap(Ast.Node.Index), +) Error!?ScriptImport { + var import = self.imports.get(file_name); + + if (import == null) { + const paths = try self.searchPaths(file_name); + defer { + for (paths) |path| { + self.gc.allocator.free(path); + } + self.gc.allocator.free(paths); + } + + // Find and read file + var file: ?std.fs.File = null; + var absolute_path: ?[]const u8 = null; + var owned = false; + for (paths) |path| { + if (std.fs.path.isAbsolute(path)) { + file = std.fs.openFileAbsolute(path, .{}) catch null; + if (file != null) { + absolute_path = path; + break; + } + } else { + file = std.fs.cwd().openFile(path, .{}) catch null; + if (file != null) { + absolute_path = std.fs.cwd().realpathAlloc(self.gc.allocator, path) catch { + return Error.ImportError; + }; + owned = true; + break; + } + } + } + + if (file == null) { + var search_report = std.ArrayList(u8).init(self.gc.allocator); + defer search_report.deinit(); + var writer = search_report.writer(); + + for (paths) |path| { + try writer.print(" no file `{s}`\n", .{path}); + } + + self.reportErrorFmt( + .script_not_found, + "buzz script `{s}` not found:\n{s}", + .{ + file_name, + search_report.items, + }, + ); + + return null; + } + + defer file.?.close(); + defer { + if (owned) { + self.gc.allocator.free(absolute_path.?); + } + } + + // TODO: put source strings in a ArenaAllocator that frees everything at the end of everything + const source = try self.gc.allocator.alloc( + u8, + (file.?.stat() catch { + return Error.ImportError; + }).size, + ); + // defer self.gc.allocator.free(source); + + _ = file.?.readAll(source) catch { + return Error.ImportError; + }; + + var parser = Self.init( + self.gc, + self.imports, + true, + self.flavor, + ); + defer parser.deinit(); + + // We need to share the token and node list with the import so TokenIndex and Node.Index are usable + parser.ast = self.ast; + parser.current_token = self.current_token; + const previous_root = self.ast.root; + + if (try parser.parse(source, file_name)) |ast| { + self.ast = ast; + self.ast.nodes.items(.components)[self.ast.root.?].Function.import_root = true; + + import = ScriptImport{ + .function = self.ast.root.?, + .globals = std.ArrayList(Global).init(self.gc.allocator), + .absolute_path = try self.gc.copyString(absolute_path.?), + }; + + for (parser.globals.items) |*global| { + if (global.exported) { + global.*.exported = false; + + if (global.export_alias) |export_alias| { + global.*.name = try self.gc.copyString(export_alias); + global.*.export_alias = null; + } + } else { + global.*.hidden = true; + } + + global.*.prefix = prefix; + + try import.?.globals.append(global.*); + } + + try self.imports.put(file_name, import.?); + } + + // Caught up this parser with the import parser status + self.ast.root = previous_root; + // Last token of imported script is Eof, we discard it + // Move scanned token just after import statement to replace the imported script Eof + // FIXME: this is letting in place the token just before the import list of tokens + self.ast.tokens.set(parser.current_token.?, self.ast.tokens.get(self.current_token.?)); + self.current_token = parser.current_token; + } + + if (import) |imported| { + const selective_import = imported_symbols.count() > 0; + for (imported.globals.items) |*global| { + if (!global.hidden) { + if (imported_symbols.get(global.name.string) != null) { + _ = imported_symbols.remove(global.name.string); + } else if (selective_import) { + global.hidden = true; + } + + // Search for name collision + if ((try self.resolveGlobal(prefix, global.name.string)) != null) { + self.reporter.reportWithOrigin( + .shadowed_global, + self.ast.tokens.get(self.current_token.? - 1), + self.ast.tokens.get(global.location), + "Shadowed global `{s}`", + .{global.name.string}, + null, + ); + } + + global.*.prefix = prefix; + } + + // TODO: we're forced to import all and hide some because globals are indexed and not looked up by name at runtime + // Only way to avoid this is to go back to named globals at runtime. Then again, is it worth it? + try self.globals.append(global.*); + } + } else { + // TODO: when it cannot load dynamic library, the error is the same + self.reportErrorFmt(.compile, "Could not compile import or import external dynamic library `{s}`", .{file_name}); + } + + return import; +} + +// TODO: when to close the lib? +fn importLibSymbol(self: *Self, full_file_name: []const u8, symbol: []const u8) !?*obj.ObjNative { + // Remove .buzz extension, this occurs if this is the script being run or if the script was imported like so `import lib/std.buzz` + // We consider that any other extension is silly from the part of the user + const file_name = if (std.mem.endsWith(u8, full_file_name, ".buzz")) + full_file_name[0..(full_file_name.len - 5)] + else + full_file_name; + + const file_basename = std.fs.path.basename(file_name); + const paths = try self.searchLibPaths(file_basename); + defer { + for (paths.items) |path| { + self.gc.allocator.free(path); + } + paths.deinit(); + } + + var lib: ?std.DynLib = null; + for (paths.items) |path| { + lib = std.DynLib.open(path) catch null; + if (lib != null) { + break; + } + } + + if (lib) |*dlib| { + // Convert symbol names to zig slices + const ssymbol = try self.gc.allocator.dupeZ(u8, symbol); + defer self.gc.allocator.free(ssymbol); + + // Lookup symbol NativeFn + const opaque_symbol_method = dlib.lookup(*anyopaque, ssymbol); + + if (opaque_symbol_method == null) { + self.reportErrorFmt( + .symbol_not_found, + "Could not find symbol `{s}` in lib `{s}`", + .{ + symbol, + file_name, + }, + ); + return null; + } + + // Create a ObjNative with it + return try self.gc.allocateObject( + obj.ObjNative, + .{ + .native = opaque_symbol_method.?, + }, + ); + } + + var search_report = std.ArrayList(u8).init(self.gc.allocator); + defer search_report.deinit(); + var writer = search_report.writer(); + + for (paths.items) |path| { + try writer.print(" no file `{s}`\n", .{path}); + } + + self.reportErrorFmt( + .library_not_found, + "External library `{s}` not found: {s}{s}\n", + .{ + file_basename, + if (builtin.link_libc) + std.mem.sliceTo(dlerror(), 0) + else + "", + search_report.items, + }, + ); + + return null; +} + +fn importStatement(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + var imported_symbols = std.StringHashMap(Ast.TokenIndex).init(self.gc.allocator); + var symbols = std.ArrayList(Ast.TokenIndex).init(self.gc.allocator); + defer symbols.shrinkAndFree(symbols.items.len); + + while ((try self.match(.Identifier)) and !self.check(.Eof)) { + const symbol = self.ast.tokens.items(.lexeme)[self.current_token.? - 1]; + + if (imported_symbols.get(symbol)) |loc| { + self.reporter.reportWithOrigin( + .import_already_exists, + self.ast.tokens.get(self.current_token.? - 1), + self.ast.tokens.get(loc), + "Import symbol `{s}` already imported", + .{symbol}, + "Imported here", + ); + } + try imported_symbols.put(self.ast.tokens.items(.lexeme)[self.current_token.? - 1], self.current_token.? - 1); + try symbols.append(self.current_token.? - 1); + + if (!self.check(.From) or self.check(.Comma)) { // Allow trailing comma + try self.consume(.Comma, "Expected `,` after identifier."); + } + } + + var prefix: ?Ast.TokenIndex = null; + if (imported_symbols.count() > 0) { + try self.consume(.From, "Expected `from` after import identifier list."); + } + + try self.consume(.String, "Expected import path."); + + const path_token = self.current_token.? - 1; + const path = self.ast.tokens.get(self.current_token.? - 1); + if (path.lexeme.len <= 1 or path.literal_string.?.len <= 0) { + self.reporter.reportErrorAt( + .empty_import, + path, + "Import path can't be empty", + ); + } + const file_name: []const u8 = if (path.lexeme.len <= 1 or path.literal_string.?.len <= 0) invalid: { + self.reporter.reportErrorAt( + .empty_import, + path, + "Import path can't be empty", + ); + + break :invalid "invalid"; + } else path.lexeme[1..(path.lexeme.len - 1)]; + + if (imported_symbols.count() == 0 and try self.match(.As)) { + try self.consume(.Identifier, "Expected identifier after `as`."); + prefix = self.current_token.? - 1; + } + + try self.consume(.Semicolon, "Expected `;` after import."); + + const import = if (!self.reporter.had_error) + try self.importScript( + file_name, + if (prefix) |pr| self.ast.tokens.items(.lexeme)[pr] else null, + &imported_symbols, + ) + else + null; + + if (imported_symbols.count() > 0) { + var it = imported_symbols.iterator(); + while (it.next()) |kv| { + self.reportErrorFmt(.unknown_import, "Unknown import `{s}`.", .{kv.key_ptr.*}); + } + } + + return try self.ast.appendNode( + .{ + .tag = .Import, + .location = start_location, + .end_location = self.current_token.? - 1, + .components = .{ + .Import = .{ + .prefix = prefix, + .imported_symbols = symbols.items, + .path = path_token, + .import = import, + }, + }, + }, + ); +} + +fn zdefStatement(self: *Self) Error!Ast.Node.Index { + if (!BuildOptions.jit) { + self.reportError(.zdef, "zdef can't be used, this instance of buzz was built with JIT compiler disabled"); + } + + const start_location = self.current_token.? - 1; + + try self.consume(.LeftParen, "Expected `(` after `zdef`."); + try self.consume(.String, "Expected identifier after `zdef(`."); + const lib_name = self.current_token.? - 1; + try self.consume(.Comma, "Expected `,` after lib name."); + try self.consume(.String, "Expected zdef declaration."); + const source = self.current_token.? - 1; + try self.consume(.RightParen, "Expected `)` to close zdef"); + try self.consume(.Semicolon, "Expected `;`"); + + const zdefs = (self.ffi.parse( + self, + self.ast.tokens.get(source), + false, + ) catch { + return Error.CantCompile; + }) orelse &[_]*FFI.Zdef{}; + + var elements = std.ArrayList(Ast.Zdef.ZdefElement).init(self.gc.allocator); + for (zdefs) |zdef| { + var fn_ptr: ?*anyopaque = null; + var slot: usize = undefined; + + std.debug.assert(self.current.?.scope_depth == 0); + const zdef_name_token = try self.insertUtilityToken(Token.identifier(zdef.name)); + slot = try self.declareVariable( + zdef.type_def, + zdef_name_token, + true, + true, + ); + // self.current_token.? - 1 = zdef_name_token; + self.markInitialized(); + + const lib_name_str = self.ast.tokens.items(.literal_string)[lib_name].?; + + // If zig_type is struct, we just push the objtypedef itself on the stack + // Otherwise we try to build a wrapper around the imported function + if (zdef.zig_type == .Fn) { + // Load the lib + const paths = try self.searchZdefLibPaths(lib_name_str); + defer { + for (paths.items) |path| { + self.gc.allocator.free(path); + } + paths.deinit(); + } + + var lib: ?std.DynLib = null; + for (paths.items) |path| { + lib = std.DynLib.open(path) catch null; + if (lib != null) { + break; + } + } + + if (lib) |*dlib| { + // Convert symbol names to zig slices + const symbol = try self.gc.allocator.dupeZ(u8, zdef.name); + defer self.gc.allocator.free(symbol); + + // Lookup symbol + const opaque_symbol_method = dlib.lookup(*anyopaque, symbol); + + if (opaque_symbol_method == null) { + self.reportErrorFmt( + .symbol_not_found, + "Could not find symbol `{s}` in lib `{s}`", + .{ + symbol, + lib_name_str, + }, + ); + } + + fn_ptr = opaque_symbol_method; + } else { + var search_report = std.ArrayList(u8).init(self.gc.allocator); + defer search_report.deinit(); + var writer = search_report.writer(); + + for (paths.items) |path| { + try writer.print(" no file `{s}`\n", .{path}); + } + + self.reportErrorFmt( + .library_not_found, + "External library `{s}` not found: {s}{s}\n", + .{ + lib_name_str, + if (builtin.link_libc) + std.mem.sliceTo(dlerror(), 0) + else + "", + search_report.items, + }, + ); + } + } + + try elements.append( + .{ + .fn_ptr = fn_ptr, + .slot = @intCast(slot), + .zdef = zdef, + }, + ); + } + + elements.shrinkAndFree(elements.items.len); + + return try self.ast.appendNode( + .{ + .tag = .Zdef, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = null, + .components = .{ + .Zdef = .{ + .lib_name = lib_name, + .source = source, + .elements = elements.items, + }, + }, + }, + ); +} + +// FIXME: this is almost the same as parseUserType! +fn userVarDeclaration(self: *Self, _: bool, constant: bool) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + const identifier = self.current_token.? - 1; + var var_type: ?*obj.ObjTypeDef = null; + + const inferred_declaration = self.check(.Equal) and constant; + var generic_resolve: ?Ast.Node.Index = null; + + // If next token is `=`, means the identifier wasn't a user type but the variable name + // and the type needs to be inferred + if (!inferred_declaration) { + const user_type_name = self.current_token.? - 1; + + // Is it a generic type defined in enclosing functions or object? + if (self.resolveGeneric(try self.gc.copyString(self.ast.tokens.items(.lexeme)[self.current_token.? - 1]))) |generic_type| { + var_type = generic_type; + } else if (self.current.?.generics != null) { + // Is it generic type defined in a function signature being parsed? + if (self.current.?.generics.?.get(try self.gc.copyString(self.ast.tokens.items(.lexeme)[self.current_token.? - 1]))) |generic_type| { + var_type = generic_type; + } + } + + // Search for a global with that name + if (var_type == null) { + if (try self.resolveGlobal(null, self.ast.tokens.items(.lexeme)[user_type_name])) |slot| { + var_type = self.globals.items[slot].type_def; + } + } + + // If none found, create a placeholder + if (var_type == null) { + var placeholder_resolved_type: obj.ObjTypeDef.TypeUnion = .{ + // TODO: token is wrong but what else can we put here? + .Placeholder = obj.PlaceholderDef.init(self.gc.allocator, user_type_name), + }; + + placeholder_resolved_type.Placeholder.name = try self.gc.copyString( + self.ast.tokens.items(.lexeme)[user_type_name], + ); + + var_type = try self.gc.type_registry.getTypeDef( + .{ + .def_type = .Placeholder, + .resolved_type = placeholder_resolved_type, + }, + ); + + _ = try self.declarePlaceholder(user_type_name, var_type); + } + + // Concrete generic types list + generic_resolve = if (try self.match(.DoubleColon)) gn: { + const generic_start = self.current_token.? - 1; + + try self.consume(.Less, "Expected generic types list after `::`"); + + var resolved_generics = std.ArrayList(*obj.ObjTypeDef).init(self.gc.allocator); + defer resolved_generics.shrinkAndFree(resolved_generics.items.len); + var generic_nodes = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer generic_nodes.shrinkAndFree(generic_nodes.items.len); + var i: usize = 0; + while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { + try generic_nodes.append( + try self.parseTypeDef( + if (self.current.?.generics) |generics| + generics.* + else + null, + true, + ), + ); + + try resolved_generics.append( + self.ast.nodes.items(.type_def)[generic_nodes.items[generic_nodes.items.len - 1]].?, + ); + + if (!self.check(.Greater)) { + try self.consume(.Comma, "Expected `,` between generic types"); + } + } + + try self.consume(.Greater, "Expected `>` after generic types list"); + + if (resolved_generics.items.len == 0) { + self.reportErrorAtCurrent(.generic_type, "Expected at least one type"); + } + + // Shouldn't we populate only in codegen? + var_type = try var_type.?.populateGenerics( + self.current_token.? - 1, + var_type.?.resolved_type.?.Object.id, + resolved_generics.items, + &self.gc.type_registry, + null, + ); + + break :gn try self.ast.appendNode( + .{ + .tag = .GenericResolveType, + .location = generic_start, + .end_location = self.current_token.? - 1, + .type_def = var_type, + .components = .{ + .GenericResolveType = .{ + .resolved_types = generic_nodes.items, + }, + }, + }, + ); + } else null; + + if (try self.match(.Question)) { + var_type = try var_type.?.cloneOptional(&self.gc.type_registry); + } + } + + const user_type_node = try self.ast.appendNode( + .{ + .tag = .UserType, + .location = start_location, + .end_location = self.current_token.? - 1, + .type_def = var_type, + .components = .{ + .UserType = .{ + .identifier = identifier, + .generic_resolve = generic_resolve, + }, + }, + }, + ); + + return try self.varDeclaration( + inferred_declaration, + user_type_node, + .Semicolon, + constant, + true, + ); +} + +fn forStatement(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + try self.consume(.LeftParen, "Expected `(` after `for`."); + + self.beginScope(); + + // Should be either VarDeclaration or expression + var init_declarations = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer init_declarations.shrinkAndFree(init_declarations.items.len); + while (!self.check(.Semicolon) and !self.check(.Eof)) { + try init_declarations.append( + try self.varDeclaration( + false, + try self.parseTypeDef(null, true), + .Nothing, + false, + true, + ), + ); + + self.markInitialized(); + + if (!self.check(.Semicolon)) { + try self.consume(.Comma, "Expected `,` after for loop variable"); + } + } + + try self.consume(.Semicolon, "Expected `;` after for loop variables."); + + const condition = try self.expression(false); + + try self.consume(.Semicolon, "Expected `;` after for loop condition."); + + var post_loop = std.ArrayList(Ast.Node.Index).init(self.gc.allocator); + defer post_loop.shrinkAndFree(post_loop.items.len); + while (!self.check(.RightParen) and !self.check(.Eof)) { + try post_loop.append(try self.expression(false)); + + if (!self.check(.RightParen)) { + try self.consume(.Comma, "Expected `,` after for loop expression"); + } + } + + try self.consume(.RightParen, "Expected `)` after `for` expressions."); + + try self.consume(.LeftBrace, "Expected `{` after `for` definition."); + + self.beginScope(); + const body = try self.block( + .{ + .loop_type = .For, + .loop_body_scope = self.current.?.scope_depth, + }, + ); + self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + + return try self.ast.appendNode( + .{ + .tag = .For, + .location = start_location, + .end_location = self.current_token.? - 1, + .components = .{ + .For = .{ + .init_declarations = init_declarations.items, + .condition = condition, + .post_loop = post_loop.items, + .body = body, + }, + }, + .ends_scope = try self.endScope(), + }, + ); +} + +fn forEachStatement(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + try self.consume(.LeftParen, "Expected `(` after `foreach`."); + + self.beginScope(); + + var key = try self.varDeclaration( + false, + try self.parseTypeDef(null, true), + .Nothing, + false, + false, + ); + + var value = if (try self.match(.Comma)) + try self.varDeclaration( + false, + try self.parseTypeDef(null, true), + .Nothing, + false, + false, + ) + else + null; + + // If key is omitted, prepare the slot anyway + const key_omitted = value == null; + if (key_omitted) { + value = key; + + key = try self.implicitVarDeclaration( + try self.insertUtilityToken(Token.identifier("$key")), + try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), + false, + ); + + // Switch slots so that key is before value + const kv_components = self.ast.nodes.items(.components); + const value_slot = kv_components[key].VarDeclaration.slot; + const key_slot = kv_components[value.?].VarDeclaration.slot; + const value_components = kv_components[key]; + const key_components = kv_components[value.?]; + kv_components[key] = key_components; + kv_components[value.?] = value_components; + + const value_local = self.current.?.locals[key_slot]; + self.current.?.locals[key_slot] = self.current.?.locals[value_slot]; + self.current.?.locals[value_slot] = value_local; + } + + try self.consume(.In, "Expected `in` after `foreach` variables."); + + // Local not usable by user but needed so that locals are correct + const iterable_slot = try self.addLocal( + try self.insertUtilityToken(Token.identifier("$iterable")), + undefined, + true, + ); + + const iterable = try self.expression(false); + + self.current.?.locals[iterable_slot].type_def = self.ast.nodes.items(.type_def)[iterable].?; + + self.markInitialized(); + + try self.consume(.RightParen, "Expected `)` after `foreach`."); + + try self.consume(.LeftBrace, "Expected `{` after `foreach` definition."); + + self.beginScope(); + const body = try self.block( + .{ + .loop_type = .ForEach, + .loop_body_scope = self.current.?.scope_depth, + }, + ); + self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + + return try self.ast.appendNode( + .{ + .tag = .ForEach, + .location = start_location, + .end_location = self.current_token.? - 1, + .components = .{ + .ForEach = .{ + .key = key, + .value = value.?, + .iterable = iterable, + .body = body, + .key_omitted = key_omitted, + }, + }, + .ends_scope = try self.endScope(), + }, + ); +} + +fn whileStatement(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + try self.consume(.LeftParen, "Expected `(` after `while`."); + + const condition = try self.expression(false); + + try self.consume(.RightParen, "Expected `)` after `while` condition."); + + try self.consume(.LeftBrace, "Expected `{` after `if` condition."); + + self.beginScope(); + const body = try self.block( + .{ + .loop_type = .While, + .loop_body_scope = self.current.?.scope_depth, + }, + ); + self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + + return try self.ast.appendNode( + .{ + .tag = .While, + .location = start_location, + .end_location = self.current_token.? - 1, + .components = .{ + .While = .{ + .condition = condition, + .body = body, + }, + }, + }, + ); +} + +fn doUntilStatement(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + try self.consume(.LeftBrace, "Expected `{` after `do`."); + + self.beginScope(); + const body = try self.block( + .{ + .loop_type = .Do, + .loop_body_scope = self.current.?.scope_depth, + }, + ); + self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + + try self.consume(.Until, "Expected `until` after `do` block."); + + try self.consume(.LeftParen, "Expected `(` after `until`."); + + const condition = try self.expression(false); + + try self.consume(.RightParen, "Expected `)` after `until` condition."); + + return try self.ast.appendNode( + .{ + .tag = .DoUntil, + .location = start_location, + .end_location = self.current_token.? - 1, + .components = .{ + .DoUntil = .{ + .condition = condition, + .body = body, + }, + }, + }, + ); +} + +fn returnStatement(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + if (self.current.?.scope_depth == 0) { + self.reportError(.syntax, "Can't use `return` at top-level."); + } + + const value = + if (!try self.match(.Semicolon)) + try self.expression(false) + else + null; + + if (value != null) { + try self.consume(.Semicolon, "Expected `;` after return value."); + } + + return try self.ast.appendNode( + .{ + .tag = .Return, + .location = start_location, + .end_location = self.current_token.? - 1, + .components = .{ + .Return = .{ + .value = value, + .unconditional = self.current.?.scope_depth == 1, + }, + }, + }, + ); +} + +fn tryStatement(self: *Self) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + if (self.current.?.in_try) { + self.reportError(.nested_try, "Nested `try` statement are not allowed"); + } + + self.current.?.in_try = true; + + try self.consume(.LeftBrace, "Expected `{` after `try`"); + + self.beginScope(); + const body = try self.block(null); + self.ast.nodes.items(.ends_scope)[body] = try self.endScope(); + + var clauses = std.ArrayList(Ast.Try.Clause).init(self.gc.allocator); + defer clauses.shrinkAndFree(clauses.items.len); + var unconditional_clause: ?Ast.Node.Index = null; + // either catch with no type of catch any + while (try self.match(.Catch)) { + if (try self.match(.LeftParen)) { + if (unconditional_clause != null) { + self.reportError(.syntax, "Catch clause not allowed after unconditional catch"); + } + + self.beginScope(); + + const type_def = try self.parseTypeDef(null, true); + + _ = try self.parseVariable( + false, + self.ast.nodes.items(.type_def)[type_def].?, + true, // function arguments are constant + "Expected error identifier", + ); + + const identifier = self.current_token.? - 1; + self.markInitialized(); + + try self.consume(.RightParen, "Expected `)` after error identifier"); + try self.consume(.LeftBrace, "Expected `{`"); + + const catch_block = try self.block(null); + self.ast.nodes.items(.ends_scope)[catch_block] = try self.endScope(); + + try clauses.append( + .{ + .identifier = identifier, + .type_def = type_def, + .body = catch_block, + }, + ); + } else if (unconditional_clause == null) { + try self.consume(.LeftBrace, "Expected `{` after `catch`"); + + self.beginScope(); + unconditional_clause = try self.block(null); + self.ast.nodes.items(.ends_scope)[unconditional_clause.?] = try self.endScope(); + } else { + self.reportError(.syntax, "Expected `(` after `catch`"); + } + } + + self.current.?.in_try = false; + + return try self.ast.appendNode( + .{ + .tag = .Try, + .location = start_location, + .end_location = self.current_token.? - 1, + .components = .{ + .Try = .{ + .body = body, + .clauses = clauses.items, + .unconditional_clause = unconditional_clause, + }, + }, + }, + ); +} + +fn breakStatement(self: *Self, loop_scope: ?LoopScope) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + if (loop_scope == null) { + self.reportError(.syntax, "break is not allowed here."); + } + + try self.consume(.Semicolon, "Expected `;` after `break`."); + + return try self.ast.appendNode( + .{ + .tag = .Break, + .location = start_location, + .end_location = self.current_token.? - 1, + .ends_scope = if (loop_scope != null) try self.closeScope(loop_scope.?.loop_body_scope) else null, + .components = .{ + .Break = {}, + }, + }, + ); +} + +fn continueStatement(self: *Self, loop_scope: ?LoopScope) Error!Ast.Node.Index { + const start_location = self.current_token.? - 1; + + if (loop_scope == null) { + self.reportError(.syntax, "continue is not allowed here."); + } + + try self.consume(.Semicolon, "Expected `;` after `continue`."); + + return try self.ast.appendNode( + .{ + .tag = .Continue, + .location = start_location, + .end_location = self.current_token.? - 1, + .ends_scope = if (loop_scope != null) + try self.closeScope(loop_scope.?.loop_body_scope) + else + null, + .components = .{ + .Continue = {}, + }, + }, + ); +} diff --git a/src/reporter.zig b/src/Reporter.zig similarity index 97% rename from src/reporter.zig rename to src/Reporter.zig index 04c81ea6..38b300c5 100644 --- a/src/reporter.zig +++ b/src/Reporter.zig @@ -1,10 +1,11 @@ const std = @import("std"); const assert = std.debug.assert; -const Token = @import("token.zig").Token; +const Token = @import("Token.zig"); const o = @import("obj.zig"); const ObjTypeDef = o.ObjTypeDef; const PlaceholderDef = o.PlaceholderDef; const Scanner = @import("scanner.zig").Scanner; +const Ast = @import("Ast.zig"); const Self = @This(); @@ -102,6 +103,7 @@ pub const Error = enum(u8) { unused_argument = 89, inferred_type = 90, empty_import = 91, + import_already_exists = 92, }; // Inspired by https://github.com/zesterer/ariadne @@ -599,17 +601,17 @@ pub fn reportTypeCheck( } // Got to the root placeholder and report it -pub fn reportPlaceholder(self: *Self, placeholder: PlaceholderDef) void { +pub fn reportPlaceholder(self: *Self, ast: Ast, placeholder: PlaceholderDef) void { if (placeholder.parent) |parent| { if (parent.def_type == .Placeholder) { - self.reportPlaceholder(parent.resolved_type.?.Placeholder); + self.reportPlaceholder(ast, parent.resolved_type.?.Placeholder); } } else { // Should be a root placeholder with a name assert(placeholder.name != null); self.reportErrorFmt( .undefined, - placeholder.where, + ast.tokens.get(placeholder.where), "`{s}` is not defined", .{placeholder.name.?.string}, ); @@ -653,7 +655,7 @@ test "multiple error on one line" { .location = Token{ .source = source, .script_name = "test", - .token_type = .Identifier, + .tag = .Identifier, .lexeme = "callSomething", .line = 2, .column = 5, @@ -665,7 +667,7 @@ test "multiple error on one line" { .location = Token{ .source = source, .script_name = "test", - .token_type = .Identifier, + .tag = .Identifier, .lexeme = "true", .line = 2, .column = 19, @@ -677,7 +679,7 @@ test "multiple error on one line" { .location = Token{ .source = source, .script_name = "test", - .token_type = .Identifier, + .tag = .Identifier, .lexeme = "complex", .line = 2, .column = 25, @@ -689,7 +691,7 @@ test "multiple error on one line" { .location = Token{ .source = source, .script_name = "test", - .token_type = .Identifier, + .tag = .Identifier, .lexeme = "print", .line = 9, .column = 13, diff --git a/src/token.zig b/src/Token.zig similarity index 59% rename from src/token.zig rename to src/Token.zig index eccaf4bb..fe666085 100644 --- a/src/token.zig +++ b/src/Token.zig @@ -1,83 +1,81 @@ const std = @import("std"); const mem = std.mem; -pub const Token = struct { - const Self = @This(); +const Self = @This(); - // Since we can parse multiple file, we have to keep a reference to the source in which this token occured - source: []const u8, - script_name: []const u8, - token_type: TokenType, - lexeme: []const u8, - // Literal is either a string or a number - literal_string: ?[]const u8 = null, - literal_float: ?f64 = null, - literal_integer: ?i32 = null, - line: usize, - column: usize, - offset: usize = 0, +// Since we can parse multiple file, we have to keep a reference to the source in which this token occured +source: []const u8, // FIXME: probably stupid of me to keep this in every token struct +script_name: []const u8, +tag: Type, +lexeme: []const u8, +// Literal is either a string or a number +literal_string: ?[]const u8 = null, +literal_float: ?f64 = null, +literal_integer: ?i32 = null, +line: usize, +column: usize, +offset: usize = 0, - pub fn eql(self: Self, other: Self) bool { - // zig fmt: off - return self.token_type == other.token_type - and self.line == other.line - and self.column == other.column - and self.offset == other.offset - and std.mem.eql(u8, self.source, other.source) - and std.mem.eql(u8, self.script_name, other.script_name); +pub fn eql(self: Self, other: Self) bool { + // zig fmt: off + return self.tag == other.tag + and self.line == other.line + and self.column == other.column + and self.offset == other.offset + and std.mem.eql(u8, self.source, other.source) + and std.mem.eql(u8, self.script_name, other.script_name); // zig fmt: on - } - - pub fn identifier(name: []const u8) Self { - return .{ - .token_type = .Identifier, - .lexeme = name, - .line = 0, - .column = 0, - .source = "", - .script_name = "", - .literal_string = name, - }; - } +} - pub fn clone(self: Self) Self { - return .{ - .token_type = self.token_type, - .lexeme = self.lexeme, - .source = self.source, - .script_name = self.script_name, - .literal_string = self.literal_string, - .literal_float = self.literal_float, - .literal_integer = self.literal_integer, - .line = self.line, - .column = self.column, - }; - } +pub fn identifier(name: []const u8) Self { + return .{ + .tag = .Identifier, + .lexeme = name, + .line = 0, + .column = 0, + .source = "", + .script_name = "", + .literal_string = name, + }; +} - // Return `n` lines around the token line in its source - pub fn getLines(self: Self, allocator: mem.Allocator, before: usize, after: usize) !std.ArrayList([]const u8) { - var lines = std.ArrayList([]const u8).init(allocator); - const before_index = if (self.line > 0) self.line - @min(before, self.line) else self.line; - const after_index = if (self.line > 0) self.line + after else self.line; +pub fn clone(self: Self) Self { + return .{ + .tag = self.tag, + .lexeme = self.lexeme, + .source = self.source, + .script_name = self.script_name, + .literal_string = self.literal_string, + .literal_float = self.literal_float, + .literal_integer = self.literal_integer, + .line = self.line, + .column = self.column, + }; +} - var it = std.mem.split(u8, self.source, "\n"); - var current: usize = 0; - while (it.next()) |line| : (current += 1) { - if (current >= before_index and current <= after_index) { - try lines.append(line); - } +// Return `n` lines around the token line in its source +pub fn getLines(self: Self, allocator: mem.Allocator, before: usize, after: usize) !std.ArrayList([]const u8) { + var lines = std.ArrayList([]const u8).init(allocator); + const before_index = if (self.line > 0) self.line - @min(before, self.line) else self.line; + const after_index = if (self.line > 0) self.line + after else self.line; - if (current > after_index) { - return lines; - } + var it = std.mem.split(u8, self.source, "\n"); + var current: usize = 0; + while (it.next()) |line| : (current += 1) { + if (current >= before_index and current <= after_index) { + try lines.append(line); } - return lines; + if (current > after_index) { + return lines; + } } -}; + + return lines; +} // WARNING: don't reorder without reordering `rules` in parser.zig -pub const TokenType = enum { +pub const Type = enum { Pipe, // | LeftBracket, // [ RightBracket, // ] @@ -159,7 +157,7 @@ pub const TokenType = enum { Static, // static From, // from As, // as - AsBang, // as? + AsQuestion, // as? Extern, // extern Eof, // EOF Error, // Error @@ -180,7 +178,7 @@ pub const TokenType = enum { }; pub const keywords = std.ComptimeStringMap( - TokenType, + Type, .{ .{ "ud", .Ud }, .{ "void", .Void }, diff --git a/src/builtin/list.zig b/src/builtin/list.zig index 952978ce..223dc923 100644 --- a/src/builtin/list.zig +++ b/src/builtin/list.zig @@ -6,11 +6,8 @@ const ObjTypeDef = _obj.ObjTypeDef; const ObjClosure = _obj.ObjClosure; const NativeCtx = _obj.NativeCtx; const VM = @import("../vm.zig").VM; -const _value = @import("../value.zig"); +const Value = @import("../value.zig").Value; const buzz_api = @import("../buzz_api.zig"); -const Value = _value.Value; -const valueEql = _value.valueEql; -const valueToString = _value.valueToString; pub fn append(ctx: *NativeCtx) c_int { const list_value: Value = ctx.vm.peek(1); @@ -118,7 +115,7 @@ fn lessThan(context: SortContext, lhs: Value, rhs: Value) bool { pub fn sort(ctx: *NativeCtx) c_int { var self = ObjList.cast(ctx.vm.peek(1).obj()).?; // fun compare(T lhs, T rhs) > bool - var sort_closure = ObjClosure.cast(ctx.vm.peek(0).obj()).?; + const sort_closure = ObjClosure.cast(ctx.vm.peek(0).obj()).?; std.sort.insertion( Value, @@ -142,7 +139,7 @@ pub fn indexOf(ctx: *NativeCtx) c_int { var index: ?usize = null; var i: usize = 0; for (self.items.items) |item| { - if (valueEql(needle, item)) { + if (needle.eql(item)) { index = i; break; } @@ -170,15 +167,15 @@ pub fn clone(ctx: *NativeCtx) c_int { } pub fn join(ctx: *NativeCtx) c_int { - var self: *ObjList = ObjList.cast(ctx.vm.peek(1).obj()).?; - var separator: *ObjString = ObjString.cast(ctx.vm.peek(0).obj()).?; + const self = ObjList.cast(ctx.vm.peek(1).obj()).?; + const separator = ObjString.cast(ctx.vm.peek(0).obj()).?; var result = std.ArrayList(u8).init(ctx.vm.gc.allocator); var writer = result.writer(); defer result.deinit(); for (self.items.items, 0..) |item, i| { - valueToString(&writer, item) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("could not stringify item") catch null; + item.toString(&writer) catch { + const err = ctx.vm.gc.copyString("could not stringify item") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; @@ -186,7 +183,7 @@ pub fn join(ctx: *NativeCtx) c_int { if (i + 1 < self.items.items.len) { writer.writeAll(separator.string) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("could not join list") catch null; + const err = ctx.vm.gc.copyString("could not join list") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; @@ -195,7 +192,7 @@ pub fn join(ctx: *NativeCtx) c_int { } ctx.vm.push((ctx.vm.gc.copyString(result.items) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("could not join list") catch null; + const err = ctx.vm.gc.copyString("could not join list") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; @@ -210,14 +207,14 @@ pub fn sub(ctx: *NativeCtx) c_int { const upto = ctx.vm.peek(0).integerOrNull(); if (start < 0 or start >= self.items.items.len) { - var err: ?*ObjString = ctx.vm.gc.copyString("`start` is out of bound") catch null; + const err = ctx.vm.gc.copyString("`start` is out of bound") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; } if (upto != null and upto.? < 0) { - var err: ?*ObjString = ctx.vm.gc.copyString("`len` must greater or equal to 0") catch null; + const err = ctx.vm.gc.copyString("`len` must greater or equal to 0") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; @@ -227,19 +224,19 @@ pub fn sub(ctx: *NativeCtx) c_int { @as(usize, @intCast(start + upto.?)) else self.items.items.len; - var substr: []Value = self.items.items[@as(usize, @intCast(start))..limit]; + const substr = self.items.items[@as(usize, @intCast(start))..limit]; var list = ctx.vm.gc.allocateObject(ObjList, ObjList{ .type_def = self.type_def, .methods = self.methods.clone() catch { - var err: ?*ObjString = ctx.vm.gc.copyString("Could not get sub list") catch null; + const err = ctx.vm.gc.copyString("Could not get sub list") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; }, .items = std.ArrayList(Value).init(ctx.vm.gc.allocator), }) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("Could not get sub list") catch null; + const err = ctx.vm.gc.copyString("Could not get sub list") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; @@ -248,7 +245,7 @@ pub fn sub(ctx: *NativeCtx) c_int { ctx.vm.push(list.toValue()); list.items.appendSlice(substr) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("Could not get sub list") catch null; + const err = ctx.vm.gc.copyString("Could not get sub list") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; diff --git a/src/builtin/map.zig b/src/builtin/map.zig index 6d3fb27a..5ccfb7ab 100644 --- a/src/builtin/map.zig +++ b/src/builtin/map.zig @@ -12,8 +12,8 @@ const _value = @import("../value.zig"); const buzz_api = @import("../buzz_api.zig"); const Value = _value.Value; const floatToInteger = _value.floatToInteger; -const valueEql = _value.valueEql; -const valueToString = _value.valueToString; +const eql = _value.eql; +const toString = _value.toString; pub fn clone(ctx: *NativeCtx) c_int { const self = ObjMap.cast(ctx.vm.peek(0).obj()).?; @@ -292,19 +292,19 @@ pub fn keys(ctx: *NativeCtx) c_int { var result = std.ArrayList(Value).init(ctx.vm.gc.allocator); for (map_keys) |key| { result.append(key) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("could not get map keys") catch null; + const err = ctx.vm.gc.copyString("could not get map keys") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.False); return -1; }; } - var list_def: ObjList.ListDef = ObjList.ListDef.init( + const list_def = ObjList.ListDef.init( ctx.vm.gc.allocator, self.type_def.resolved_type.?.Map.key_type, ); - var list_def_union: ObjTypeDef.TypeUnion = .{ + const list_def_union: ObjTypeDef.TypeUnion = .{ .List = list_def, }; @@ -313,7 +313,7 @@ pub fn keys(ctx: *NativeCtx) c_int { .optional = false, .resolved_type = list_def_union, }) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("could not get map keys") catch null; + const err = ctx.vm.gc.copyString("could not get map keys") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.False); return -1; @@ -326,7 +326,7 @@ pub fn keys(ctx: *NativeCtx) c_int { ObjList, ObjList.init(ctx.vm.gc.allocator, list_def_type), ) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("could not get map keys") catch null; + const err = ctx.vm.gc.copyString("could not get map keys") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.False); return -1; @@ -347,27 +347,27 @@ pub fn values(ctx: *NativeCtx) c_int { const map_values: []Value = self.map.values(); var result = std.ArrayList(Value).init(ctx.vm.gc.allocator); result.appendSlice(map_values) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("could not get map values") catch null; + const err = ctx.vm.gc.copyString("could not get map values") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.False); return -1; }; - var list_def: ObjList.ListDef = ObjList.ListDef.init( + const list_def = ObjList.ListDef.init( ctx.vm.gc.allocator, self.type_def.resolved_type.?.Map.value_type, ); - var list_def_union: ObjTypeDef.TypeUnion = .{ + const list_def_union: ObjTypeDef.TypeUnion = .{ .List = list_def, }; - var list_def_type: *ObjTypeDef = ctx.vm.gc.type_registry.getTypeDef(ObjTypeDef{ + const list_def_type = ctx.vm.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .List, .optional = false, .resolved_type = list_def_union, }) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("could not get map values") catch null; + const err = ctx.vm.gc.copyString("could not get map values") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.False); return -1; @@ -377,7 +377,7 @@ pub fn values(ctx: *NativeCtx) c_int { ObjList, ObjList.init(ctx.vm.gc.allocator, list_def_type), ) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("could not get map values") catch null; + const err = ctx.vm.gc.copyString("could not get map values") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.False); return -1; diff --git a/src/builtin/pattern.zig b/src/builtin/pattern.zig index dd439efd..a9d81479 100644 --- a/src/builtin/pattern.zig +++ b/src/builtin/pattern.zig @@ -78,7 +78,7 @@ fn rawMatchAll(self: *ObjPattern, vm: *VM, subject: *ObjString) !?*ObjList { var offset: usize = 0; while (true) { if (try rawMatch(self, vm, subject, &offset)) |matches| { - var was_null = results == null; + const was_null = results == null; results = results orelse try vm.gc.allocateObject( ObjList, ObjList.init(vm.gc.allocator, matches.type_def), diff --git a/src/builtin/str.zig b/src/builtin/str.zig index 09426600..224c6dc2 100644 --- a/src/builtin/str.zig +++ b/src/builtin/str.zig @@ -53,16 +53,16 @@ pub fn utf8Valid(ctx: *NativeCtx) c_int { pub fn utf8Codepoints(ctx: *NativeCtx) c_int { const str: *ObjString = ObjString.cast(ctx.vm.peek(0).obj()).?; - var list_def: ObjList.ListDef = ObjList.ListDef.init( + const list_def = ObjList.ListDef.init( ctx.vm.gc.allocator, ctx.vm.gc.type_registry.getTypeDef(.{ .def_type = .String }) catch @panic("Could not create list"), ); - var list_def_union: ObjTypeDef.TypeUnion = .{ + const list_def_union: ObjTypeDef.TypeUnion = .{ .List = list_def, }; - var list_def_type: *ObjTypeDef = ctx.vm.gc.type_registry.getTypeDef(ObjTypeDef{ + const list_def_type = ctx.vm.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .List, .optional = false, .resolved_type = list_def_union, @@ -107,7 +107,7 @@ pub fn repeat(ctx: *NativeCtx) c_int { return 1; } - var err: ?*ObjString = ctx.vm.gc.copyString("`n` should be an integer") catch null; + const err = ctx.vm.gc.copyString("`n` should be an integer") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; @@ -119,7 +119,7 @@ pub fn byte(ctx: *NativeCtx) c_int { const index_i = index.integer(); if (index_i < 0 or index_i >= self.string.len) { - var err: ?*ObjString = ctx.vm.gc.copyString("Out of bound access to str") catch null; + const err = ctx.vm.gc.copyString("Out of bound access to str") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; @@ -185,21 +185,21 @@ pub fn sub(ctx: *NativeCtx) c_int { const upto = ctx.vm.peek(0).integerOrNull(); if (start < 0 or start >= self.string.len) { - var err: ?*ObjString = ctx.vm.gc.copyString("`start` is out of bound") catch null; + const err = ctx.vm.gc.copyString("`start` is out of bound") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; } if (upto != null and upto.? < 0) { - var err: ?*ObjString = ctx.vm.gc.copyString("`len` must greater or equal to 0") catch null; + const err = ctx.vm.gc.copyString("`len` must greater or equal to 0") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; } const limit: usize = if (upto != null and @as(usize, @intCast(start + upto.?)) < self.string.len) @intCast(start + upto.?) else self.string.len; - var substr: []const u8 = self.string[@as(usize, @intCast(start))..limit]; + const substr: []const u8 = self.string[@as(usize, @intCast(start))..limit]; ctx.vm.push( (ctx.vm.gc.copyString(substr) catch @panic("Could not create string")).toValue(), @@ -213,19 +213,19 @@ pub fn split(ctx: *NativeCtx) c_int { const separator: *ObjString = ObjString.cast(ctx.vm.peek(0).obj()).?; // std.mem.split(u8, self.string, separator.string); - var list_def: ObjList.ListDef = ObjList.ListDef.init( + const list_def = ObjList.ListDef.init( ctx.vm.gc.allocator, ctx.vm.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .String, }) catch @panic("Could not create string"), ); - var list_def_union: ObjTypeDef.TypeUnion = .{ + const list_def_union: ObjTypeDef.TypeUnion = .{ .List = list_def, }; // TODO: reuse already allocated similar typedef - var list_def_type: *ObjTypeDef = ctx.vm.gc.type_registry.getTypeDef(ObjTypeDef{ + const list_def_type = ctx.vm.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .List, .optional = false, .resolved_type = list_def_union, @@ -260,7 +260,7 @@ pub fn split(ctx: *NativeCtx) c_int { pub fn encodeBase64(ctx: *NativeCtx) c_int { const str = ObjString.cast(ctx.vm.peek(0).obj()).?; - var encoded = ctx.vm.gc.allocator.alloc(u8, std.base64.standard.Encoder.calcSize(str.string.len)) catch @panic("Could not create string"); + const encoded = ctx.vm.gc.allocator.alloc(u8, std.base64.standard.Encoder.calcSize(str.string.len)) catch @panic("Could not create string"); defer ctx.vm.gc.allocator.free(encoded); var new_string = ctx.vm.gc.copyString( @@ -276,16 +276,16 @@ pub fn decodeBase64(ctx: *NativeCtx) c_int { const str = ObjString.cast(ctx.vm.peek(0).obj()).?; const size = std.base64.standard.Decoder.calcSizeForSlice(str.string) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("Could not decode string") catch null; + const err = ctx.vm.gc.copyString("Could not decode string") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; }; - var decoded = ctx.vm.gc.allocator.alloc(u8, size) catch @panic("Could not create string"); + const decoded = ctx.vm.gc.allocator.alloc(u8, size) catch @panic("Could not create string"); defer ctx.vm.gc.allocator.free(decoded); std.base64.standard.Decoder.decode(decoded, str.string) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("Could not decode string") catch null; + const err = ctx.vm.gc.copyString("Could not decode string") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; @@ -388,7 +388,7 @@ pub fn bin(ctx: *NativeCtx) c_int { for (0..result.len) |i| { result[i] = std.fmt.parseInt(u8, str.string[(i * 2)..(i * 2 + 2)], 16) catch { - var err: ?*ObjString = ctx.vm.gc.copyString("String does not contain valid hex values") catch null; + const err = ctx.vm.gc.copyString("String does not contain valid hex values") catch null; ctx.vm.push(if (err) |uerr| uerr.toValue() else Value.fromBoolean(false)); return -1; diff --git a/src/buzz_api.zig b/src/buzz_api.zig index a10b0411..44c346b3 100644 --- a/src/buzz_api.zig +++ b/src/buzz_api.zig @@ -5,20 +5,18 @@ const _vm = @import("vm.zig"); const VM = _vm.VM; const TryCtx = _vm.TryCtx; const _obj = @import("obj.zig"); -const _value = @import("value.zig"); +const Value = @import("value.zig").Value; const memory = @import("memory.zig"); -const _parser = @import("parser.zig"); -const _codegen = @import("codegen.zig"); +const Parser = @import("Parser.zig"); +const CodeGen = @import("Codegen.zig"); const BuildOptions = @import("build_options"); const jmp = @import("jmp.zig").jmp; -const FunctionNode = @import("node.zig").FunctionNode; const dumpStack = @import("disassembler.zig").dumpStack; const ZigType = @import("zigtypes.zig").Type; -const Token = @import("token.zig").Token; +const Token = @import("Token.zig"); +const Ast = @import("Ast.zig"); -const Value = _value.Value; -const valueToStringAlloc = _value.valueToStringAlloc; -const valueEql = _value.valueEql; +const eql = Value.eql; const Obj = _obj.Obj; const ObjString = _obj.ObjString; const ObjPattern = _obj.ObjPattern; @@ -40,8 +38,6 @@ const ObjForeignContainer = _obj.ObjForeignContainer; const NativeFn = _obj.NativeFn; const NativeCtx = _obj.NativeCtx; const TypeRegistry = memory.TypeRegistry; -const Parser = _parser.Parser; -const CodeGen = _codegen.CodeGen; const GarbageCollector = memory.GarbageCollector; var gpa = std.heap.GeneralPurposeAllocator(.{ @@ -144,7 +140,7 @@ fn valueDump(value: Value, vm: *VM, seen: *std.AutoHashMap(*_obj.Obj, void), dep if (value.isNull()) { std.debug.print("null", .{}); } else if (!value.isObj() or seen.get(value.obj()) != null) { - const string = valueToStringAlloc(vm.gc.allocator, value) catch std.ArrayList(u8).init(vm.gc.allocator); + const string = value.toStringAlloc(vm.gc.allocator) catch std.ArrayList(u8).init(vm.gc.allocator); defer string.deinit(); std.debug.print("{s}", .{string.items}); @@ -161,7 +157,7 @@ fn valueDump(value: Value, vm: *VM, seen: *std.AutoHashMap(*_obj.Obj, void), dep .Fiber, .EnumInstance, => { - const string = valueToStringAlloc(vm.gc.allocator, value) catch std.ArrayList(u8).init(vm.gc.allocator); + const string = value.toStringAlloc(vm.gc.allocator) catch std.ArrayList(u8).init(vm.gc.allocator); defer string.deinit(); std.debug.print("{s}", .{string.items}); @@ -219,7 +215,7 @@ fn valueDump(value: Value, vm: *VM, seen: *std.AutoHashMap(*_obj.Obj, void), dep std.debug.print("enum({s}) {s} {{ ", .{ enum_type_def.name.string, enumeration.name.string }); for (enum_type_def.cases.items, 0..) |case, i| { std.debug.print("{s} -> ", .{case}); - valueDump(enumeration.cases.items[i], vm, seen, depth); + valueDump(enumeration.cases[i], vm, seen, depth); std.debug.print(", ", .{}); } std.debug.print("}}", .{}); @@ -426,7 +422,7 @@ export fn bz_objStringSubscript(vm: *VM, obj_string: Value, index_value: Value) } export fn bz_toString(vm: *VM, value: Value) Value { - const str = valueToStringAlloc(vm.gc.allocator, value) catch { + const str = value.toStringAlloc(vm.gc.allocator) catch { @panic("Could not convert value to string"); }; defer str.deinit(); @@ -496,16 +492,16 @@ export fn bz_collect(self: *VM) bool { } export fn bz_newList(vm: *VM, of_type: Value) Value { - var list_def: ObjList.ListDef = ObjList.ListDef.init( + const list_def: ObjList.ListDef = ObjList.ListDef.init( vm.gc.allocator, ObjTypeDef.cast(of_type.obj()).?, ); - var list_def_union: ObjTypeDef.TypeUnion = .{ + const list_def_union: ObjTypeDef.TypeUnion = .{ .List = list_def, }; - var list_def_type: *ObjTypeDef = vm.gc.type_registry.getTypeDef(ObjTypeDef{ + const list_def_type: *ObjTypeDef = vm.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .List, .optional = false, .resolved_type = list_def_union, @@ -594,7 +590,7 @@ export fn bz_userDataToValue(userdata: *ObjUserData) Value { } export fn bz_newVM(self: *VM) ?*VM { - var vm = self.gc.allocator.create(VM) catch { + const vm = self.gc.allocator.create(VM) catch { return null; }; var gc = self.gc.allocator.create(GarbageCollector) catch { @@ -619,7 +615,13 @@ export fn bz_deinitVM(_: *VM) void { // self.deinit(); } -export fn bz_compile(self: *VM, source: ?[*]const u8, source_len: usize, file_name: ?[*]const u8, file_name_len: usize) ?*ObjFunction { +export fn bz_compile( + self: *VM, + source: ?[*]const u8, + source_len: usize, + file_name: ?[*]const u8, + file_name_len: usize, +) ?*ObjFunction { if (source == null or file_name_len == 0 or source_len == 0 or file_name_len == 0) { return null; } @@ -645,23 +647,68 @@ export fn bz_compile(self: *VM, source: ?[*]const u8, source_len: usize, file_na strings.deinit(); } - if (parser.parse(source.?[0..source_len], file_name.?[0..file_name_len]) catch null) |function_node| { - return function_node.toByteCode(function_node, &codegen, null) catch null; + if (parser.parse(source.?[0..source_len], file_name.?[0..file_name_len]) catch null) |ast| { + return codegen.generate(ast) catch null; } else { return null; } } -export fn bz_interpret(self: *VM, function: *ObjFunction) bool { - self.interpret(function, null) catch { +export fn bz_interpret(self: *VM, ast: *anyopaque, function: *ObjFunction) bool { + self.interpret(@as(*Ast, @ptrCast(@alignCast(ast))).*, function, null) catch { return false; }; return true; } +export fn bz_run( + self: *VM, + source: ?[*]const u8, + source_len: usize, + file_name: ?[*]const u8, + file_name_len: usize, +) bool { + if (source == null or file_name_len == 0 or source_len == 0 or file_name_len == 0) { + return false; + } + + var imports = std.StringHashMap(Parser.ScriptImport).init(self.gc.allocator); + var strings = std.StringHashMap(*ObjString).init(self.gc.allocator); + var parser = Parser.init( + self.gc, + &imports, + false, + self.flavor, + ); + var codegen = CodeGen.init( + self.gc, + &parser, + self.flavor, + null, + ); + defer { + codegen.deinit(); + imports.deinit(); + parser.deinit(); + strings.deinit(); + } + + if (parser.parse(source.?[0..source_len], file_name.?[0..file_name_len]) catch null) |ast| { + if (codegen.generate(ast) catch null) |function| { + self.interpret(ast, function, null) catch { + return false; + }; + + return true; + } + } + + return false; +} + fn calleeIsCompiled(value: Value) bool { - var obj: *Obj = value.obj(); + const obj: *Obj = value.obj(); return switch (obj.obj_type) { .Bound => bound: { const bound = ObjBoundMethod.cast(obj).?; @@ -986,14 +1033,14 @@ export fn bz_getEnumCase(vm: *VM, enum_value: Value, case_name_value: Value) Val export fn bz_getEnumCaseValue(enum_instance_value: Value) Value { const instance = ObjEnumInstance.cast(enum_instance_value.obj()).?; - return instance.enum_ref.cases.items[instance.case]; + return instance.enum_ref.cases[instance.case]; } export fn bz_getEnumCaseFromValue(vm: *VM, enum_value: Value, case_value: Value) Value { const enum_ = ObjEnum.cast(enum_value.obj()).?; - for (enum_.cases.items, 0..) |case, index| { - if (valueEql(case, case_value)) { + for (enum_.cases, 0..) |case, index| { + if (eql(case, case_value)) { var enum_case: *ObjEnumInstance = vm.gc.allocateObject(ObjEnumInstance, ObjEnumInstance{ .enum_ref = enum_, .case = @intCast(index), @@ -1019,7 +1066,7 @@ export fn bz_toObjNative(value: Value) *ObjNative { } export fn bz_valueEqual(self: Value, other: Value) Value { - return Value.fromBoolean(_value.valueEql(self, other)); + return Value.fromBoolean(self.eql(other)); } export fn bz_valueTypeOf(self: Value, vm: *VM) Value { @@ -1048,13 +1095,13 @@ export fn bz_mapGet(map: Value, key: Value) Value { } export fn bz_valueIs(self: Value, type_def: Value) Value { - return Value.fromBoolean(_value.valueIs(type_def, self)); + return Value.fromBoolean(type_def.is(self)); } export fn bz_setTryCtx(self: *VM) *TryCtx { // It would be better that this was in an ALLOCA, but with it memory keeps slowing leaking // Maybe the jmp throws off the stack? - var try_ctx = self.gc.allocator.create(TryCtx) catch @panic("Could not create try context"); + const try_ctx = self.gc.allocator.create(TryCtx) catch @panic("Could not create try context"); try_ctx.* = .{ .previous = self.current_fiber.try_context, .env = undefined, @@ -1142,7 +1189,7 @@ export fn bz_context(ctx: *NativeCtx, closure_value: Value, new_ctx: *NativeCtx, }; if (closure != null and closure.?.function.native_raw == null and closure.?.function.native == null) { - ctx.vm.mir_jit.?.compileFunction(closure.?) catch @panic("Failed compiling function"); + ctx.vm.jit.?.compileFunction(ctx.vm.current_ast, closure.?) catch @panic("Failed compiling function"); } return if (closure) |cls| cls.function.native_raw.? else native.?.native; @@ -1150,12 +1197,12 @@ export fn bz_context(ctx: *NativeCtx, closure_value: Value, new_ctx: *NativeCtx, export fn bz_closure( ctx: *NativeCtx, - function_node: *FunctionNode, + function_node: Ast.Node.Index, native: *anyopaque, native_raw: *anyopaque, ) Value { // Set native pointers in objfunction - var obj_function = function_node.function.?; + var obj_function = ctx.vm.current_ast.nodes.items(.components)[function_node].Function.function.?; obj_function.native = native; obj_function.native_raw = native_raw; @@ -1171,9 +1218,9 @@ export fn bz_closure( // On stack to prevent collection ctx.vm.push(closure.toValue()); - ctx.vm.mir_jit.?.compiled_closures.put(closure, {}) catch @panic("Could not get closure"); + ctx.vm.jit.?.compiled_closures.put(closure, {}) catch @panic("Could not get closure"); - var it = function_node.upvalue_binding.iterator(); + var it = ctx.vm.current_ast.nodes.items(.components)[function_node].Function.upvalue_binding.iterator(); while (it.next()) |kv| { const is_local = kv.value_ptr.*; const index = kv.key_ptr.*; @@ -1554,7 +1601,7 @@ export fn bz_writeZigValueToBuffer( @as(u8, 1) else @as(u8, 0)); - var bytes = std.mem.asBytes(&unwrapped); + const bytes = std.mem.asBytes(&unwrapped); buffer.replaceRange(at, bytes.len, bytes) catch @panic("Out of memory"); }, @@ -1563,13 +1610,13 @@ export fn bz_writeZigValueToBuffer( switch (ztype.Int.bits) { 64 => { const unwrapped = ObjUserData.cast(value.obj()).?.userdata; - var bytes = std.mem.asBytes(&unwrapped); + const bytes = std.mem.asBytes(&unwrapped); buffer.replaceRange(at, bytes.len, bytes) catch @panic("Out of memory"); }, 1...32 => { const unwrapped = value.integer(); - var bytes = std.mem.asBytes(&unwrapped)[0..(ztype.Int.bits / 8)]; + const bytes = std.mem.asBytes(&unwrapped)[0..(ztype.Int.bits / 8)]; buffer.replaceRange(at, bytes.len, bytes) catch @panic("Out of memory"); }, @@ -1579,13 +1626,13 @@ export fn bz_writeZigValueToBuffer( .Float => switch (ztype.Float.bits) { 32 => { const unwrapped = @as(f32, @floatCast(value.float())); - var bytes = std.mem.asBytes(&unwrapped); + const bytes = std.mem.asBytes(&unwrapped); buffer.replaceRange(at, bytes.len, bytes) catch @panic("Out of memory"); }, 64 => { const unwrapped = value.float(); - var bytes = std.mem.asBytes(&unwrapped); + const bytes = std.mem.asBytes(&unwrapped); buffer.replaceRange(at, bytes.len, bytes) catch @panic("Out of memory"); }, @@ -1607,7 +1654,7 @@ export fn bz_writeZigValueToBuffer( .Opaque, => { const unwrapped = ObjUserData.cast(value.obj()).?.userdata; - var bytes = std.mem.asBytes(&unwrapped); + const bytes = std.mem.asBytes(&unwrapped); buffer.replaceRange(at, bytes.len, bytes) catch @panic("Out of memory"); }, diff --git a/src/codegen.zig b/src/codegen.zig deleted file mode 100644 index 2f1d3bd7..00000000 --- a/src/codegen.zig +++ /dev/null @@ -1,270 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const mem = std.mem; -const Allocator = mem.Allocator; -const assert = std.debug.assert; - -const _chunk = @import("chunk.zig"); -const _obj = @import("obj.zig"); -const _vm = @import("vm.zig"); -const RunFlavor = _vm.RunFlavor; -const _value = @import("value.zig"); -const _disassembler = @import("disassembler.zig"); -const _parser = @import("parser.zig"); -const _node = @import("node.zig"); -const _token = @import("token.zig"); -const GarbageCollector = @import("memory.zig").GarbageCollector; -const Reporter = @import("reporter.zig"); -const BuildOptions = @import("build_options"); -const MIRJIT = @import("mirjit.zig"); -const ParseNode = _node.ParseNode; -const FunctionNode = _node.FunctionNode; -const ObjFunction = _obj.ObjFunction; -const Parser = _parser.Parser; -const OpCode = _chunk.OpCode; -const Value = _value.Value; -const Chunk = _chunk.Chunk; -const Token = _token.Token; -const ObjTypeDef = _obj.ObjTypeDef; -const PlaceholderDef = _obj.PlaceholderDef; -const TypeRegistry = _obj.TypeRegistry; - -pub const Frame = struct { - enclosing: ?*Frame = null, - function_node: *FunctionNode, - function: ?*ObjFunction = null, - return_counts: bool = false, - return_emitted: bool = false, - - try_should_handle: ?std.AutoHashMap(*ObjTypeDef, Token) = null, -}; - -pub const CodeGen = struct { - const Self = @This(); - - current: ?*Frame = null, - gc: *GarbageCollector, - flavor: RunFlavor, - // Jump to patch at end of current expression with a optional unwrapping in the middle of it - opt_jumps: ?std.ArrayList(usize) = null, - // Used to generate error messages - parser: *Parser, - mir_jit: ?*MIRJIT, - - reporter: Reporter, - - pub fn init( - gc: *GarbageCollector, - parser: *Parser, - flavor: RunFlavor, - mir_jit: ?*MIRJIT, - ) Self { - return .{ - .gc = gc, - .parser = parser, - .flavor = flavor, - .reporter = Reporter{ - .allocator = gc.allocator, - .error_prefix = "Compile", - }, - .mir_jit = mir_jit, - }; - } - - pub fn deinit(_: *Self) void {} - - pub inline fn currentCode(self: *Self) usize { - return self.current.?.function.?.chunk.code.items.len; - } - - pub fn generate(self: *Self, root: *FunctionNode) anyerror!?*ObjFunction { - self.reporter.had_error = false; - self.reporter.panic_mode = false; - - if (BuildOptions.debug) { - var out = std.ArrayList(u8).init(self.gc.allocator); - defer out.deinit(); - - try root.node.toJson(&root.node, &out.writer()); - - try std.io.getStdOut().writer().print("\n{s}", .{out.items}); - } - - const function = try root.node.toByteCode(&root.node, self, null); - - return if (self.reporter.had_error) null else function; - } - - pub fn emit(self: *Self, location: Token, code: u32) !void { - try self.current.?.function.?.chunk.write(code, location); - } - - pub fn emitTwo(self: *Self, location: Token, a: u8, b: u24) !void { - try self.emit(location, (@as(u32, @intCast(a)) << 24) | @as(u32, @intCast(b))); - } - - // OP_ | arg - pub fn emitCodeArg(self: *Self, location: Token, code: OpCode, arg: u24) !void { - try self.emit( - location, - (@as(u32, @intCast(@intFromEnum(code))) << 24) | @as(u32, @intCast(arg)), - ); - } - - // OP_ | a | b - pub fn emitCodeArgs(self: *Self, location: Token, code: OpCode, a: u8, b: u16) !void { - try self.emit( - location, - (@as(u32, @intCast(@intFromEnum(code))) << 24) | (@as(u32, @intCast(a)) << 16) | (@as(u32, @intCast(b))), - ); - } - - pub fn emitOpCode(self: *Self, location: Token, code: OpCode) !void { - try self.emit(location, @as(u32, @intCast(@intFromEnum(code))) << 24); - } - - pub fn emitLoop(self: *Self, location: Token, loop_start: usize) !void { - const offset: usize = self.currentCode() - loop_start + 1; - if (offset > 16777215) { - self.reportError(.loop_body_too_large, "Loop body too large."); - } - - try self.emitCodeArg(location, .OP_LOOP, @as(u24, @intCast(offset))); - } - - pub fn emitJump(self: *Self, location: Token, instruction: OpCode) !usize { - try self.emitCodeArg(location, instruction, 0xffffff); - - return self.currentCode() - 1; - } - - pub fn patchJumpOrLoop(self: *Self, offset: usize, loop_start: ?usize) !void { - const original: u32 = self.current.?.function.?.chunk.code.items[offset]; - const instruction: u8 = @intCast(original >> 24); - const code: OpCode = @enumFromInt(instruction); - - if (code == .OP_LOOP) { // Patching a continue statement - assert(loop_start != null); - const loop_offset: usize = offset - loop_start.? + 1; - if (loop_offset > 16777215) { - self.reportError(.loop_body_too_large, "Loop body too large."); - } - - self.current.?.function.?.chunk.code.items[offset] = - (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(loop_offset)); - } else { // Patching a break statement - self.patchJump(offset); - } - } - - pub fn patchJump(self: *Self, offset: usize) void { - assert(offset < self.currentCode()); - - const jump: usize = self.currentCode() - offset - 1; - - if (jump > 16777215) { - self.reportError(.jump_too_large, "Jump too large."); - } - - const original: u32 = self.current.?.function.?.chunk.code.items[offset]; - const instruction: u8 = @intCast(original >> 24); - - self.current.?.function.?.chunk.code.items[offset] = - (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(jump)); - } - - pub fn patchTry(self: *Self, offset: usize) void { - assert(offset < self.currentCode()); - - const jump: usize = self.currentCode(); - - if (jump > 16777215) { - self.reportError( - .block_too_large, - "Try block too large.", - ); - } - - const original: u32 = self.current.?.function.?.chunk.code.items[offset]; - const instruction: u8 = @intCast(original >> 24); - - self.current.?.function.?.chunk.code.items[offset] = - (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(jump)); - } - - pub fn emitList( - self: *Self, - location: Token, - ) !usize { - try self.emitCodeArg(location, .OP_LIST, 0xffffff); - - return self.currentCode() - 1; - } - - pub fn patchList(self: *Self, offset: usize, constant: u24) !void { - const original: u32 = self.current.?.function.?.chunk.code.items[offset]; - const instruction: u8 = @intCast(original >> 24); - - self.current.?.function.?.chunk.code.items[offset] = - (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(constant)); - } - - pub fn emitMap(self: *Self, location: Token) !usize { - try self.emitCodeArg(location, .OP_MAP, 0xffffff); - - return self.currentCode() - 1; - } - - pub fn patchMap(self: *Self, offset: usize, map_type_constant: u24) !void { - const original: u32 = self.current.?.function.?.chunk.code.items[offset]; - const instruction: u8 = @intCast(original >> 24); - - self.current.?.function.?.chunk.code.items[offset] = - (@as(u32, @intCast(instruction)) << 24) | @as(u32, @intCast(map_type_constant)); - } - - pub fn emitReturn(self: *Self, location: Token) !void { - try self.emitOpCode(location, .OP_VOID); - try self.emitOpCode(location, .OP_RETURN); - } - - pub fn emitConstant(self: *Self, location: Token, value: Value) !void { - try self.emitCodeArg(location, .OP_CONSTANT, try self.makeConstant(value)); - } - - pub fn makeConstant(self: *Self, value: Value) !u24 { - var constant: u24 = try self.current.?.function.?.chunk.addConstant(null, value); - if (constant > Chunk.max_constants) { - self.reportError("Too many constants in one chunk."); - return 0; - } - - return constant; - } - - pub fn identifierConstant(self: *Self, name: []const u8) !u24 { - return try self.makeConstant( - Value.fromObj((try self.gc.copyString(name)).toObj()), - ); - } - - // Unlocated error, should not be used - fn reportError(self: *Self, error_type: Reporter.Error, message: []const u8) void { - if (self.reporter.panic_mode) { - return; - } - - self.reporter.report( - error_type, - Token{ - .token_type = .Error, - .source = "", - .script_name = "", - .lexeme = "", - .line = 0, - .column = 0, - }, - message, - ); - } -}; diff --git a/src/disassembler.zig b/src/disassembler.zig index b7bec3f3..d1788ae5 100644 --- a/src/disassembler.zig +++ b/src/disassembler.zig @@ -1,14 +1,13 @@ const std = @import("std"); const print = std.debug.print; -const _chunk = @import("chunk.zig"); -const _value = @import("value.zig"); +const Chunk = @import("Chunk.zig"); +const Value = @import("value.zig").Value; const _obj = @import("obj.zig"); const _vm = @import("vm.zig"); const global_allocator = @import("buzz_api.zig").allocator; const VM = _vm.VM; -const Chunk = _chunk.Chunk; -const OpCode = _chunk.OpCode; +const OpCode = Chunk.OpCode; const ObjFunction = _obj.ObjFunction; pub fn disassembleChunk(chunk: *Chunk, name: []const u8) !void { @@ -27,7 +26,7 @@ fn invokeInstruction(code: OpCode, chunk: *Chunk, offset: usize) !usize { const arg_count: u8 = @intCast(chunk.code.items[offset + 1] >> 24); const catch_count: u24 = @intCast(0x00ffffff & chunk.code.items[offset + 1]); - var value_str = try _value.valueToStringAlloc(global_allocator, chunk.constants.items[constant]); + var value_str = try chunk.constants.items[constant].toStringAlloc(global_allocator); defer value_str.deinit(); print("{s}\t{s}({} args, {} catches)", .{ @@ -92,7 +91,7 @@ fn triInstruction(code: OpCode, chunk: *Chunk, offset: usize) usize { fn constantInstruction(code: OpCode, chunk: *Chunk, offset: usize) !usize { const constant: u24 = @intCast(0x00ffffff & chunk.code.items[offset]); - var value_str = try _value.valueToStringAlloc(global_allocator, chunk.constants.items[constant]); + var value_str = try chunk.constants.items[constant].toStringAlloc(global_allocator); defer value_str.deinit(); print( @@ -152,9 +151,9 @@ pub fn dumpStack(vm: *VM) void { print("\u{001b}[2m", .{}); // Dimmed print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n", .{}); - var value: [*]_value.Value = @ptrCast(vm.current_fiber.stack[0..]); + var value: [*]Value = @ptrCast(vm.current_fiber.stack[0..]); while (@intFromPtr(value) < @intFromPtr(vm.current_fiber.stack_top)) { - var value_str = _value.valueToStringAlloc(global_allocator, value[0]) catch unreachable; + var value_str = value[0].toStringAlloc(global_allocator) catch unreachable; defer value_str.deinit(); if (vm.currentFrame().?.slots == value) { @@ -187,11 +186,12 @@ pub fn dumpStack(vm: *VM) void { pub fn disassembleInstruction(chunk: *Chunk, offset: usize) !usize { print("\n{:0>3} ", .{offset}); + const lines = chunk.ast.tokens.items(.line); - if (offset > 0 and chunk.lines.items[offset].line == chunk.lines.items[offset - 1].line) { + if (offset > 0 and lines[chunk.lines.items[offset]] == lines[chunk.lines.items[offset - 1]]) { print("| ", .{}); } else { - print("{:0>3} ", .{chunk.lines.items[offset].line}); + print("{:0>3} ", .{lines[chunk.lines.items[offset]]}); } const full_instruction: u32 = chunk.code.items[offset]; @@ -311,10 +311,10 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) !usize { => triInstruction(instruction, chunk, offset), .OP_CLOSURE => closure: { - var constant: u24 = arg; + const constant: u24 = arg; var off_offset: usize = offset + 1; - var value_str = try _value.valueToStringAlloc(global_allocator, chunk.constants.items[constant]); + var value_str = try chunk.constants.items[constant].toStringAlloc(global_allocator); defer value_str.deinit(); print( @@ -326,12 +326,12 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) !usize { }, ); - var function: *ObjFunction = ObjFunction.cast(chunk.constants.items[constant].obj()).?; + const function: *ObjFunction = ObjFunction.cast(chunk.constants.items[constant].obj()).?; var i: u8 = 0; while (i < function.upvalue_count) : (i += 1) { - var is_local: bool = chunk.code.items[off_offset] == 1; + const is_local: bool = chunk.code.items[off_offset] == 1; off_offset += 1; - var index: u8 = @intCast(chunk.code.items[off_offset]); + const index: u8 = @intCast(chunk.code.items[off_offset]); off_offset += 1; print( "\n{:0>3} | \t{s} {}\n", diff --git a/src/ext/clap b/src/ext/clap index f49b9470..9c23bcb5 160000 --- a/src/ext/clap +++ b/src/ext/clap @@ -1 +1 @@ -Subproject commit f49b94700e0761b7514abdca0e4f0e7f3f938a93 +Subproject commit 9c23bcb5aebe0c2542b4de4472f60959974e2222 diff --git a/src/jit_extern_api.zig b/src/jit_extern_api.zig new file mode 100644 index 00000000..f071e724 --- /dev/null +++ b/src/jit_extern_api.zig @@ -0,0 +1,1150 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const m = @import("mir.zig"); +const api = @import("lib/buzz_api.zig"); +const JIT = @import("Jit.zig"); +const jmp = @import("jmp.zig").jmp; + +export fn bz_exit(code: c_int) noreturn { + std.os.exit(@truncate(@as(c_uint, @bitCast(code)))); +} + +pub const ExternApi = enum { + nativefn, + rawfn, + + bz_objStringConcat, + bz_objStringSubscript, + bz_toString, + bz_newList, + bz_listAppend, + bz_listGet, + bz_listSet, + bz_valueEqual, + bz_listConcat, + bz_newMap, + bz_mapSet, + bz_mapGet, + bz_mapConcat, + bz_valueIs, + bz_setTryCtx, + bz_popTryCtx, + bz_rethrow, + bz_throw, + bz_closeUpValues, + bz_getUpValue, + bz_setUpValue, + bz_closure, + bz_context, + bz_instance, + bz_setInstanceField, + bz_getInstanceField, + bz_getObjectField, + bz_setObjectField, + bz_getStringField, + bz_getPatternField, + bz_getFiberField, + bz_getEnumCase, + bz_getEnumCaseValue, + bz_getListField, + bz_getMapField, + bz_getEnumCaseFromValue, + bz_bindMethod, + bz_stringNext, + bz_listNext, + bz_mapNext, + bz_enumNext, + bz_clone, + bz_valueToCString, + bz_valueToUserData, + bz_userDataToValue, + bz_valueToForeignContainerPtr, + bz_stringZ, + bz_containerGet, + bz_containerSet, + bz_containerInstance, + bz_valueTypeOf, + bz_containerFromSlice, + + bz_dumpStack, + + // https://opensource.apple.com/source/libplatform/libplatform-161/include/setjmp.h.auto.html + setjmp, + // libc exit: https://man7.org/linux/man-pages/man3/exit.3.html + exit, + memcpy, + + dumpInt, + bz_valueDump, + + pub fn declare(self: ExternApi, jit: *JIT) !m.MIR_item_t { + const prototype = jit.state.?.prototypes.get(self) orelse self.proto(jit.ctx); + + try jit.required_ext_api.put(self, {}); + try jit.state.?.prototypes.put( + self, + prototype, + ); + + return prototype; + } + + fn proto(self: ExternApi, ctx: m.MIR_context_t) m.MIR_item_t { + return switch (self) { + .bz_objStringSubscript => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 3, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "obj_string", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "index_value", + .size = undefined, + }, + }, + ), + .bz_closure => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 4, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "ctx", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "function_node", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "native", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "native_raw", + .size = undefined, + }, + }, + ), + .bz_toString, .bz_newList, .bz_newMap => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_listAppend => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 3, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "list", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_listGet, .bz_mapGet => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "list", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "index", + .size = undefined, + }, + }, + ), + .bz_listSet, .bz_mapSet => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 4, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "list", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "index", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_valueEqual => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "other", + .size = undefined, + }, + }, + ), + .bz_listConcat, + .bz_mapConcat, + .bz_objStringConcat, + .bz_instance, + => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 3, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "list", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "other_list", + .size = undefined, + }, + }, + ), + .bz_getEnumCaseFromValue, .bz_getEnumCase => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 3, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "enum", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_getEnumCaseValue => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "enum_instance", + .size = undefined, + }, + }, + ), + .bz_getObjectField, .bz_valueIs => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "subject", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "field", + .size = undefined, + }, + }, + ), + .bz_getListField, + .bz_getMapField, + .bz_getStringField, + .bz_getPatternField, + .bz_getFiberField, + .bz_getInstanceField, + => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 4, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "subject", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "field", + .size = undefined, + }, + .{ + .type = m.MIR_T_U8, + .name = "bind", + .size = undefined, + }, + }, + ), + .bz_setInstanceField, + .bz_setObjectField, + .bz_bindMethod, + => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 4, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "instance", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "field", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_getUpValue => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "native_ctx", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "slot", + .size = undefined, + }, + }, + ), + .bz_setUpValue => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 3, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "native_ctx", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "slot", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_closeUpValues => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "last", + .size = undefined, + }, + }, + ), + .bz_setTryCtx => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + // *TryContext + &[_]m.MIR_type_t{m.MIR_T_P}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + }, + ), + .bz_popTryCtx, .bz_rethrow => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + }, + ), + .bz_throw => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "payload", + .size = undefined, + }, + }, + ), + .bz_context => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_P}, + 4, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "native_ctx", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "function", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "new_native_ctx", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "arg_count", + .size = undefined, + }, + }, + ), + .setjmp => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "jmp_buf", + .size = undefined, + }, + }, + ), + .bz_clone => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_dumpStack => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "ctx", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "offset", + .size = undefined, + }, + }, + ), + .exit => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U8, + .name = "status", + .size = undefined, + }, + }, + ), + .bz_stringNext, + .bz_listNext, + .bz_mapNext, + .bz_enumNext, + => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 3, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "iterable", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "key", + .size = undefined, + }, + }, + ), + .rawfn => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_U64}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "ctx", + .size = undefined, + }, + }, + ), + .nativefn => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_I16}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "ctx", + .size = undefined, + }, + }, + ), + .dumpInt => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_valueDump => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + }, + ), + .bz_valueToCString => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_P}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_valueToUserData => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_P}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_userDataToValue => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_I64}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_valueToForeignContainerPtr => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_P}, + 1, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_stringZ => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_I64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "string", + .size = undefined, + }, + }, + ), + .bz_containerGet => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_I64}, + 4, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "field", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "len", + .size = undefined, + }, + }, + ), + .bz_containerSet => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 5, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "field", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "len", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "new_value", + .size = undefined, + }, + }, + ), + .bz_containerInstance => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_I64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + }, + ), + .bz_valueTypeOf => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_I64}, + 2, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_U64, + .name = "value", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + }, + ), + .bz_containerFromSlice => m.MIR_new_proto_arr( + ctx, + self.pname(), + 1, + &[_]m.MIR_type_t{m.MIR_T_I64}, + 4, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "vm", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "type_def", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "ptr", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "len", + .size = undefined, + }, + }, + ), + .memcpy => m.MIR_new_proto_arr( + ctx, + self.pname(), + 0, + null, + 4, + &[_]m.MIR_var_t{ + .{ + .type = m.MIR_T_P, + .name = "dest", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "dest_len", + .size = undefined, + }, + .{ + .type = m.MIR_T_P, + .name = "source", + .size = undefined, + }, + .{ + .type = m.MIR_T_U64, + .name = "source_len", + .size = undefined, + }, + }, + ), + }; + } + + pub fn ptr(self: ExternApi) *anyopaque { + return switch (self) { + .bz_toString => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_toString))), + .bz_objStringConcat => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_objStringConcat))), + .bz_objStringSubscript => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_objStringSubscript))), + .bz_stringNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_stringNext))), + .bz_newList => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_newList))), + .bz_listAppend => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listAppend))), + .bz_listGet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listGet))), + .bz_listSet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listSet))), + .bz_listConcat => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listConcat))), + .bz_listNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_listNext))), + .bz_newMap => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_newMap))), + .bz_mapGet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_mapGet))), + .bz_mapSet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_mapSet))), + .bz_mapNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_mapNext))), + .bz_mapConcat => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_mapConcat))), + .bz_valueEqual => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueEqual))), + .bz_valueIs => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueIs))), + .bz_closure => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_closure))), + .bz_context => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_context))), + .bz_instance => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_instance))), + .bz_setInstanceField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_setInstanceField))), + .bz_getInstanceField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getInstanceField))), + .bz_rethrow => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_rethrow))), + .bz_throw => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_throw))), + .bz_bindMethod => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_bindMethod))), + .bz_getUpValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_getUpValue))), + .bz_setUpValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_setUpValue))), + .bz_closeUpValues => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_closeUpValues))), + .bz_clone => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_clone))), + .bz_dumpStack => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_dumpStack))), + .bz_getEnumCaseFromValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjEnum.bz_getEnumCaseFromValue))), + .bz_getEnumCase => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjEnum.bz_getEnumCase))), + .bz_enumNext => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjEnum.bz_enumNext))), + .bz_getEnumCaseValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjEnumInstance.bz_getEnumCaseValue))), + .bz_setObjectField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_setObjectField))), + .bz_getObjectField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getObjectField))), + .bz_getListField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_getListField))), + .bz_getMapField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_getMapField))), + .bz_getStringField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_getStringField))), + .bz_getPatternField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjPattern.bz_getPatternField))), + .bz_getFiberField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjFiber.bz_getFiberField))), + .bz_setTryCtx => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_setTryCtx))), + .bz_popTryCtx => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_popTryCtx))), + .bz_valueToCString => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueToCString))), + .bz_valueToUserData => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueToUserData))), + .bz_userDataToValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjUserData.bz_userDataToValue))), + .bz_valueToForeignContainerPtr => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueToForeignContainerPtr))), + .bz_stringZ => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_stringZ))), + .bz_containerGet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjForeignContainer.bz_containerGet))), + .bz_containerSet => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjForeignContainer.bz_containerSet))), + .bz_containerInstance => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjForeignContainer.bz_containerInstance))), + .bz_valueTypeOf => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueTypeOf))), + .bz_containerFromSlice => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjForeignContainer.bz_containerFromSlice))), + .memcpy => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.bz_memcpy))), + .setjmp => @as( + *anyopaque, + @ptrFromInt( + @intFromPtr(&(if (builtin.os.tag == .macos or builtin.os.tag == .linux or builtin.os.tag == .windows) jmp._setjmp else jmp.setjmp)), + ), + ), + .exit => @as(*anyopaque, @ptrFromInt(@intFromPtr(&bz_exit))), + + .dumpInt => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.dumpInt))), + .bz_valueDump => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueDump))), + else => { + std.debug.print("{s}\n", .{self.name()}); + unreachable; + }, + }; + } + + pub fn name(self: ExternApi) [*:0]const u8 { + return switch (self) { + .nativefn => "NativeFn", + .rawfn => "RawFn", + + .bz_objStringConcat => "bz_objStringConcat", + .bz_objStringSubscript => "bz_objStringSubscript", + .bz_toString => "bz_toString", + .bz_newList => "bz_newList", + .bz_listAppend => "bz_listAppend", + .bz_listGet => "bz_listGet", + .bz_listSet => "bz_listSet", + .bz_valueEqual => "bz_valueEqual", + .bz_listConcat => "bz_listConcat", + .bz_newMap => "bz_newMap", + .bz_mapSet => "bz_mapSet", + .bz_mapGet => "bz_mapGet", + .bz_mapConcat => "bz_mapConcat", + .bz_valueIs => "bz_valueIs", + .bz_setTryCtx => "bz_setTryCtx", + .bz_popTryCtx => "bz_popTryCtx", + .bz_rethrow => "bz_rethrow", + .bz_throw => "bz_throw", + .bz_getUpValue => "bz_getUpValue", + .bz_setUpValue => "bz_setUpValue", + .bz_closeUpValues => "bz_closeUpValues", + .bz_closure => "bz_closure", + .bz_context => "bz_context", + .bz_instance => "bz_instance", + .bz_setInstanceField => "bz_setInstanceField", + .bz_getInstanceField => "bz_getInstanceField", + .bz_setObjectField => "bz_setObjectField", + .bz_getObjectField => "bz_getObjectField", + .bz_getStringField => "bz_getStringField", + .bz_getPatternField => "bz_getPatternField", + .bz_getFiberField => "bz_getFiberField", + .bz_getEnumCase => "bz_getEnumCase", + .bz_getEnumCaseValue => "bz_getEnumCaseValue", + .bz_getListField => "bz_getListField", + .bz_getMapField => "bz_getMapField", + .bz_getEnumCaseFromValue => "bz_getEnumCaseFromValue", + .bz_bindMethod => "bz_bindMethod", + .bz_stringNext => "bz_stringNext", + .bz_listNext => "bz_listNext", + .bz_mapNext => "bz_mapNext", + .bz_enumNext => "bz_enumNext", + .bz_clone => "bz_clone", + .bz_valueToCString => "bz_valueToCString", + .bz_valueToUserData => "bz_valueToUserData", + .bz_userDataToValue => "bz_userDataToValue", + .bz_valueToForeignContainerPtr => "bz_valueToForeignContainerPtr", + .bz_stringZ => "bz_stringZ", + .bz_containerGet => "bz_containerGet", + .bz_containerSet => "bz_containerSet", + .bz_containerInstance => "bz_containerInstance", + .bz_valueTypeOf => "bz_valueTypeOf", + .bz_containerFromSlice => "bz_containerFromSlice", + .memcpy => "bz_memcpy", + + .setjmp => if (builtin.os.tag == .macos or builtin.os.tag == .linux or builtin.os.tag == .windows) "_setjmp" else "setjmp", + .exit => "bz_exit", + + .bz_dumpStack => "bz_dumpStack", + + .dumpInt => "dumpInt", + .bz_valueDump => "bz_valueDump", + }; + } + + pub fn pname(self: ExternApi) [*:0]const u8 { + return switch (self) { + .nativefn => "p_NativeFn", + .rawfn => "p_RawFn", + + .bz_objStringConcat => "p_bz_objStringConcat", + .bz_objStringSubscript => "p_bz_objStringSubscript", + .bz_toString => "p_bz_toString", + .bz_newList => "p_bz_newList", + .bz_listAppend => "p_bz_listAppend", + .bz_listGet => "p_bz_listGet", + .bz_listSet => "p_bz_listSet", + .bz_valueEqual => "p_bz_valueEqual", + .bz_listConcat => "p_bz_listConcat", + .bz_newMap => "p_bz_newMap", + .bz_mapSet => "p_bz_mapSet", + .bz_mapGet => "p_bz_mapGet", + .bz_mapConcat => "p_bz_mapConcat", + .bz_valueIs => "p_bz_valueIs", + .bz_setTryCtx => "p_bz_setTryCtx", + .bz_popTryCtx => "p_bz_popTryCtx", + .bz_rethrow => "p_bz_rethrow", + .bz_throw => "p_bz_throw", + .bz_getUpValue => "p_bz_getUpValue", + .bz_setUpValue => "p_bz_setUpValue", + .bz_closeUpValues => "p_bz_closeUpValues", + .bz_closure => "p_bz_closure", + .bz_context => "p_bz_context", + .bz_instance => "p_bz_instance", + .bz_setInstanceField => "p_bz_setInstanceField", + .bz_getInstanceField => "p_bz_getInstanceField", + .bz_setObjectField => "p_bz_setObjectField", + .bz_getObjectField => "p_bz_getObjectField", + .bz_getStringField => "p_bz_getStringField", + .bz_getPatternField => "p_bz_getPatternField", + .bz_getFiberField => "p_bz_getFiberField", + .bz_getEnumCase => "p_bz_getEnumCase", + .bz_getEnumCaseValue => "p_bz_getEnumCaseValue", + .bz_getListField => "p_bz_getListField", + .bz_getMapField => "p_bz_getMapField", + .bz_getEnumCaseFromValue => "p_bz_getEnumCaseFromValue", + .bz_bindMethod => "p_bz_bindMethod", + .bz_stringNext => "p_bz_stringNext", + .bz_listNext => "p_bz_listNext", + .bz_mapNext => "p_bz_mapNext", + .bz_enumNext => "p_bz_enumNext", + .bz_clone => "p_bz_clone", + .bz_valueToCString => "p_bz_valueToCString", + .bz_valueToUserData => "p_bz_valueToUserData", + .bz_userDataToValue => "p_bz_userDataToValue", + .bz_valueToForeignContainerPtr => "p_bz_valueToForeignContainerPtr", + .bz_stringZ => "p_bz_stringZ", + .bz_containerGet => "p_bz_containerGet", + .bz_containerSet => "p_bz_containerSet", + .bz_containerInstance => "p_bz_containerInstance", + .bz_valueTypeOf => "p_bz_valueTypeOf", + .bz_containerFromSlice => "p_bz_containerFromSlice", + .memcpy => "p_bz_memcpy", + + .setjmp => if (builtin.os.tag == .macos or builtin.os.tag == .linux or builtin.os.windows) "p__setjmp" else "p_setjmp", + .exit => "p_exit", + + .bz_dumpStack => "p_bz_dumpStack", + + .dumpInt => "p_dumpInt", + .bz_valueDump => "p_bz_valueDump", + }; + } +}; diff --git a/src/lib/buzz_api.zig b/src/lib/buzz_api.zig index 55d44efe..14179c58 100644 --- a/src/lib/buzz_api.zig +++ b/src/lib/buzz_api.zig @@ -1,7 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const BuildOptions = @import("build_options"); -const jmp = @import("../jmp.zig").jmp; +const jmp = @import("jmp.zig").jmp; // FIXME: all those should operate on Value // FIXME: some should only be available to the JIT compiler @@ -36,8 +36,17 @@ pub const ZigType = opaque { pub extern fn bz_zigTypeAlignment(self: *ZigType) u16; }; +var gpa = std.heap.GeneralPurposeAllocator(.{ + .safety = true, +}){}; + pub const VM = opaque { - pub const allocator = @import("../buzz_api.zig").allocator; + pub const allocator = if (builtin.mode == .Debug) + gpa.allocator() + else if (BuildOptions.mimalloc) + @import("mimalloc.zig").mim_allocator + else + std.heap.c_allocator; pub extern fn bz_newVM(self: *VM) *VM; pub extern fn bz_deinitVM(self: *VM) void; @@ -48,7 +57,14 @@ pub const VM = opaque { file_name: ?[*]const u8, file_name_len: usize, ) ?*ObjFunction; - pub extern fn bz_interpret(self: *VM, function: *ObjFunction) bool; + pub extern fn bz_interpret(self: *VM, ast: *anyopaque, function: *ObjFunction) bool; + pub extern fn bz_run( + self: *VM, + source: ?[*]const u8, + source_len: usize, + file_name: ?[*]const u8, + file_name_len: usize, + ) bool; pub extern fn bz_call( self: *VM, closure: *ObjClosure, diff --git a/src/lib/buzz_buffer.zig b/src/lib/buzz_buffer.zig index 4618e7d9..56241ae7 100644 --- a/src/lib/buzz_buffer.zig +++ b/src/lib/buzz_buffer.zig @@ -1,11 +1,12 @@ const std = @import("std"); const api = @import("buzz_api.zig"); +const builtin = @import("builtin"); const native_endian = @import("builtin").target.cpu.arch.endian(); export fn BufferNew(ctx: *api.NativeCtx) c_int { const capacity = ctx.vm.bz_peek(0).integer(); - var buffer = api.VM.allocator.create(Buffer) catch { + const buffer = api.VM.allocator.create(Buffer) catch { @panic("Out of memory"); }; buffer.* = Buffer.init(api.VM.allocator, @intCast(capacity)) catch { @@ -22,7 +23,7 @@ export fn BufferNew(ctx: *api.NativeCtx) c_int { } export fn BufferDeinit(ctx: *api.NativeCtx) c_int { - var userdata = ctx.vm.bz_peek(0).bz_valueToUserData(); + const userdata = ctx.vm.bz_peek(0).bz_valueToUserData(); var buffer = Buffer.fromUserData(userdata); @@ -120,7 +121,7 @@ const Buffer = struct { var buffer_stream = std.io.fixedBufferStream(self.buffer.items[self.cursor..self.buffer.items.len]); var reader = buffer_stream.reader(); - const number = try reader.readIntNative(i32); + const number = try reader.readInt(i32, builtin.cpu.arch.endian()); self.cursor += @sizeOf(i32); @@ -135,7 +136,7 @@ const Buffer = struct { var writer = self.buffer.writer(); // Flag so we know it an integer - try writer.writeIntNative(i32, integer); + try writer.writeInt(i32, integer, native_endian); } pub fn readUserData(self: *Self, vm: *api.VM) !?*api.ObjUserData { @@ -146,7 +147,7 @@ const Buffer = struct { var buffer_stream = std.io.fixedBufferStream(self.buffer.items[self.cursor..self.buffer.items.len]); var reader = buffer_stream.reader(); - const number = try reader.readIntNative(u64); + const number = try reader.readInt(u64, builtin.cpu.arch.endian()); self.cursor += @sizeOf(u64); @@ -161,9 +162,10 @@ const Buffer = struct { var writer = self.buffer.writer(); // Flag so we know it an integer - try writer.writeIntNative( + try writer.writeInt( u64, userdata.bz_getUserDataPtr(), + native_endian, ); } @@ -175,7 +177,7 @@ const Buffer = struct { var buffer_stream = std.io.fixedBufferStream(self.buffer.items[self.cursor..self.buffer.items.len]); var reader = buffer_stream.reader(); - const number = try reader.readIntNative(u64); + const number = try reader.readInt(u64, builtin.cpu.arch.endian()); self.cursor += @sizeOf(f64); @@ -190,7 +192,11 @@ const Buffer = struct { var writer = self.buffer.writer(); // Flag so we know it an float - try writer.writeIntNative(u64, @as(u64, @bitCast(float))); + try writer.writeInt( + u64, + @as(u64, @bitCast(float)), + native_endian, + ); } pub fn empty(self: *Self) void { @@ -481,7 +487,7 @@ inline fn rawWriteZ( return false; } - var len = api.VM.bz_zigValueSize(zig_type.?); + const len = api.VM.bz_zigValueSize(zig_type.?); buffer.buffer.ensureTotalCapacityPrecise(buffer.buffer.items.len + len) catch @panic("Out of memory"); buffer.buffer.expandToCapacity(); diff --git a/src/lib/buzz_debug.zig b/src/lib/buzz_debug.zig index 0c24417c..9e80e877 100644 --- a/src/lib/buzz_debug.zig +++ b/src/lib/buzz_debug.zig @@ -1,14 +1,5 @@ const std = @import("std"); const api = @import("buzz_api.zig"); -const _obj = @import("../obj.zig"); -const _parser = @import("../parser.zig"); -const Parser = _parser.Parser; -const ObjString = _obj.ObjString; -const ObjTypeDef = _obj.ObjTypeDef; -const _memory = @import("../memory.zig"); -const RunFlavor = @import("../vm.zig").RunFlavor; -const GarbageCollector = _memory.GarbageCollector; -const TypeRegistry = _memory.TypeRegistry; export fn dump(ctx: *api.NativeCtx) c_int { ctx.vm.bz_peek(0).bz_valueDump(ctx.vm); @@ -18,72 +9,73 @@ export fn dump(ctx: *api.NativeCtx) c_int { return 0; } -export fn ast(ctx: *api.NativeCtx) c_int { - var source_len: usize = 0; - const source = api.Value.bz_valueToString(ctx.vm.bz_peek(1), &source_len); - - var script_len: usize = 0; - const script_name = api.Value.bz_valueToString(ctx.vm.bz_peek(0), &script_len); - - var gc = GarbageCollector.init(api.VM.allocator); - gc.type_registry = TypeRegistry{ - .gc = &gc, - .registry = std.StringHashMap(*ObjTypeDef).init(api.VM.allocator), - }; - var strings = std.StringHashMap(*ObjString).init(api.VM.allocator); - var imports = std.StringHashMap(Parser.ScriptImport).init(api.VM.allocator); - - var parser = Parser.init( - &gc, - &imports, - false, - .Ast, - ); - - defer { - parser.deinit(); - strings.deinit(); - var it = imports.iterator(); - while (it.next()) |kv| { - kv.value_ptr.*.globals.deinit(); - } - imports.deinit(); - // TODO: free type_registry and its keys which are on the heap - } - - const root = parser.parse(source.?[0..source_len], script_name.?[0..script_len]) catch |err| { - switch (err) { - error.OutOfMemory, - error.NoSpaceLeft, - => @panic("Out of memory"), - } - - return -1; - }; - - if (root != null) { - var out = std.ArrayList(u8).init(api.VM.allocator); - - root.?.toJson(root.?, &out.writer()) catch |err| { - switch (err) { - error.OutOfMemory, - error.NoSpaceLeft, - => @panic("Out of memory"), - } - - return -1; - }; - - ctx.vm.bz_pushString( - api.ObjString.bz_string(ctx.vm, if (out.items.len > 0) @as([*]const u8, @ptrCast(out.items)) else null, out.items.len) orelse { - @panic("Out of memory"); - }, - ); - - return 1; - } - - ctx.vm.pushError("errors.CompileError", null); - - return -1; -} +// TODO: reactivate +// fn ast(ctx: *api.NativeCtx) c_int { +// var source_len: usize = 0; +// const source = api.Value.bz_valueToString(ctx.vm.bz_peek(1), &source_len); + +// var script_len: usize = 0; +// const script_name = api.Value.bz_valueToString(ctx.vm.bz_peek(0), &script_len); + +// var gc = GarbageCollector.init(api.VM.allocator); +// gc.type_registry = TypeRegistry{ +// .gc = &gc, +// .registry = std.StringHashMap(*ObjTypeDef).init(api.VM.allocator), +// }; +// var strings = std.StringHashMap(*ObjString).init(api.VM.allocator); +// var imports = std.StringHashMap(Parser.ScriptImport).init(api.VM.allocator); + +// var parser = Parser.init( +// &gc, +// &imports, +// false, +// .Ast, +// ); + +// defer { +// parser.deinit(); +// strings.deinit(); +// var it = imports.iterator(); +// while (it.next()) |kv| { +// kv.value_ptr.*.globals.deinit(); +// } +// imports.deinit(); +// // TODO: free type_registry and its keys which are on the heap +// } + +// const root = parser.parse(source.?[0..source_len], script_name.?[0..script_len]) catch |err| { +// switch (err) { +// error.OutOfMemory, +// error.NoSpaceLeft, +// => @panic("Out of memory"), +// } + +// return -1; +// }; + +// if (root != null) { +// var out = std.ArrayList(u8).init(api.VM.allocator); + +// root.?.toJson(root.?, &out.writer()) catch |err| { +// switch (err) { +// error.OutOfMemory, +// error.NoSpaceLeft, +// => @panic("Out of memory"), +// } + +// return -1; +// }; + +// ctx.vm.bz_pushString( +// api.ObjString.bz_string(ctx.vm, if (out.items.len > 0) @as([*]const u8, @ptrCast(out.items)) else null, out.items.len) orelse { +// @panic("Out of memory"); +// }, +// ); + +// return 1; +// } + +// ctx.vm.pushError("errors.CompileError", null); + +// return -1; +// } diff --git a/src/lib/buzz_fs.zig b/src/lib/buzz_fs.zig index 037825e3..454919e7 100644 --- a/src/lib/buzz_fs.zig +++ b/src/lib/buzz_fs.zig @@ -275,17 +275,27 @@ export fn list(ctx: *api.NativeCtx) c_int { const filename_slice = filename.?[0..len]; const dir = if (std.fs.path.isAbsolute(filename_slice)) - std.fs.openIterableDirAbsolute(filename_slice, .{}) catch |err| { + std.fs.openDirAbsolute( + filename_slice, + .{ + .iterate = true, + }, + ) catch |err| { handleOpenDirAbsoluteError(ctx, err); return -1; } else - std.fs.cwd().openIterableDir(filename_slice, .{}) catch |err| { + std.fs.cwd().openDir( + filename_slice, + .{ + .iterate = true, + }, + ) catch |err| { handleOpenDirError(ctx, err); return -1; }; - var file_list = api.ObjList.bz_newList( + const file_list = api.ObjList.bz_newList( ctx.vm, api.ObjTypeDef.bz_stringType(ctx.vm), ); diff --git a/src/lib/buzz_io.zig b/src/lib/buzz_io.zig index 2852625f..4d056bf1 100644 --- a/src/lib/buzz_io.zig +++ b/src/lib/buzz_io.zig @@ -66,7 +66,7 @@ export fn FileOpen(ctx: *api.NativeCtx) c_int { const filename = ctx.vm.bz_peek(1).bz_valueToString(&len); const filename_slice = filename.?[0..len]; - var file: std.fs.File = if (std.fs.path.isAbsolute(filename_slice)) + const file: std.fs.File = if (std.fs.path.isAbsolute(filename_slice)) switch (mode) { 0 => std.fs.openFileAbsolute(filename_slice, .{ .mode = .read_only }) catch |err| { handleFileOpenError(ctx, err); @@ -116,6 +116,7 @@ fn handleFileReadWriteError(ctx: *api.NativeCtx, err: anytype) void { error.IsDir, error.SystemResources, error.WouldBlock, + error.SocketNotConnected, => ctx.vm.pushErrorEnum("errors.FileSystemError", @errorName(err)), error.OperationAborted, @@ -168,6 +169,7 @@ fn handleFileReadLineError(ctx: *api.NativeCtx, err: anytype) void { error.IsDir, error.SystemResources, error.WouldBlock, + error.SocketNotConnected, => ctx.vm.pushErrorEnum("errors.FileSystemError", @errorName(err)), error.BrokenPipe, @@ -179,6 +181,8 @@ fn handleFileReadLineError(ctx: *api.NativeCtx, err: anytype) void { error.StreamTooLong, => ctx.vm.pushErrorEnum("errors.ReadWriteError", @errorName(err)), + error.OutOfMemory => @panic("Out of memory"), + error.Unexpected => ctx.vm.pushError("errors.UnexpectedError", null), } } @@ -192,7 +196,7 @@ export fn FileReadLine(ctx: *api.NativeCtx) c_int { const file: std.fs.File = std.fs.File{ .handle = handle }; const reader = file.reader(); - var buffer = reader.readUntilDelimiterOrEofAlloc( + const buffer = reader.readUntilDelimiterOrEofAlloc( api.VM.allocator, '\n', if (max_size.isNull()) @@ -229,6 +233,7 @@ fn handleFileReadAllError(ctx: *api.NativeCtx, err: anytype) void { error.IsDir, error.SystemResources, error.WouldBlock, + error.SocketNotConnected, => ctx.vm.pushErrorEnum("errors.FileSystemError", @errorName(err)), error.OperationAborted, @@ -358,19 +363,14 @@ export fn runFile(ctx: *api.NativeCtx) c_int { defer vm.bz_deinitVM(); // Compile - var function = vm.bz_compile( + + // Run + if (!vm.bz_run( if (source.len > 0) @as([*]const u8, @ptrCast(source)) else null, source.len, if (filename.len > 0) @as([*]const u8, @ptrCast(filename)) else null, filename.len, - ) orelse { - ctx.vm.pushError("errors.CompileError", null); - - return -1; - }; - - // Run - if (!vm.bz_interpret(function)) { + )) { ctx.vm.pushError("errors.InterpretError", null); return -1; diff --git a/src/lib/buzz_os.zig b/src/lib/buzz_os.zig index 37a9f299..3df9c456 100644 --- a/src/lib/buzz_os.zig +++ b/src/lib/buzz_os.zig @@ -235,6 +235,7 @@ fn handleConnectError(ctx: *api.NativeCtx, err: anytype) void { error.SharingViolation, error.SymLinkLoop, error.NetworkNotFound, + error.SocketNotConnected, => ctx.vm.pushErrorEnum("errors.FileSystemError", @errorName(err)), error.BrokenPipe, @@ -413,6 +414,7 @@ fn handleReadLineError(ctx: *api.NativeCtx, err: anytype) void { error.IsDir, error.SystemResources, error.WouldBlock, + error.SocketNotConnected, => ctx.vm.pushErrorEnum("errors.FileSystemError", @errorName(err)), error.BrokenPipe, @@ -426,6 +428,8 @@ fn handleReadLineError(ctx: *api.NativeCtx, err: anytype) void { error.Unexpected => ctx.vm.pushError("errors.UnexpectedError", null), + error.OutOfMemory => @panic("Out of memory"), + error.EndOfStream => {}, } } @@ -439,7 +443,7 @@ export fn SocketReadLine(ctx: *api.NativeCtx) c_int { const stream: std.net.Stream = .{ .handle = handle }; const reader = stream.reader(); - var buffer = reader.readUntilDelimiterAlloc( + const buffer = reader.readUntilDelimiterAlloc( api.VM.allocator, '\n', if (max_size.isNull()) @@ -484,7 +488,7 @@ export fn SocketReadAll(ctx: *api.NativeCtx) c_int { const stream: std.net.Stream = .{ .handle = handle }; const reader = stream.reader(); - var buffer = reader.readAllAlloc( + const buffer = reader.readAllAlloc( api.VM.allocator, if (max_size.isNull()) std.math.maxInt(usize) @@ -636,6 +640,7 @@ export fn SocketServerStart(ctx: *api.NativeCtx) c_int { error.SystemResources, error.WouldBlock, error.NetworkNotFound, + error.SocketNotConnected, => ctx.vm.pushErrorEnum("errors.FileSystemError", @errorName(err)), error.Unexpected => ctx.vm.pushError("errors.UnexpectedError", null), error.OutOfMemory => @panic("Out of memory"), @@ -711,6 +716,7 @@ export fn SocketServerAccept(ctx: *api.NativeCtx) c_int { .reuse_address = reuse_address, .reuse_port = reuse_port, .listen_address = undefined, + .force_nonblocking = false, }; const connection = server.accept() catch |err| { @@ -727,6 +733,7 @@ export fn SocketServerAccept(ctx: *api.NativeCtx) c_int { error.NetworkSubsystemFailed, error.OperationNotSupported, => ctx.vm.pushErrorEnum("errors.SocketError", @errorName(err)), + error.WouldBlock => ctx.vm.pushErrorEnum("errors.FileSystemError", @errorName(err)), error.Unexpected => ctx.vm.pushError("errors.UnexpectedError", null), } diff --git a/src/lib/debug.buzz b/src/lib/debug.buzz index 33e997f1..a23bc72e 100644 --- a/src/lib/debug.buzz +++ b/src/lib/debug.buzz @@ -4,8 +4,9 @@ import "errors"; || Dump any value to stdout export extern fun dump(any value) > void; -|| Parse `source` and return the abstract syntax tree in JSON -|| @param source the buzz source -|| @param script name (used to fetch eventual extern functions) -|| @return AST as JSON -export extern fun ast(str source, str scriptName) > str !> CompileError; \ No newline at end of file +| Parse `source` and return the abstract syntax tree in JSON +| @param source the buzz source +| @param script name (used to fetch eventual extern functions) +| @return AST as JSON +| TODO: reactivate +| export extern fun ast(str source, str scriptName) > str !> CompileError; \ No newline at end of file diff --git a/src/lib/errors.buzz b/src/lib/errors.buzz index f04c545e..84268ec8 100644 --- a/src/lib/errors.buzz +++ b/src/lib/errors.buzz @@ -27,6 +27,7 @@ export enum FileSystemError { ReadOnlyFileSystem, RenameAcrossMountPoints, SharingViolation, + SocketNotConnected, SymLinkLoop, SystemFdQuotaExceeded, SystemResources, diff --git a/src/lib/jmp.zig b/src/lib/jmp.zig new file mode 100644 index 00000000..7e60b182 --- /dev/null +++ b/src/lib/jmp.zig @@ -0,0 +1,3 @@ +pub const jmp = @cImport({ + @cInclude("setjmp.h"); +}); diff --git a/src/lib/mimalloc.zig b/src/lib/mimalloc.zig new file mode 100644 index 00000000..b50dd539 --- /dev/null +++ b/src/lib/mimalloc.zig @@ -0,0 +1,63 @@ +const std = @import("std"); +const mem = std.mem; +const math = std.math; +const debug = std.debug; + +const Allocator = mem.Allocator; + +const mi = @cImport(@cInclude("mimalloc.h")); + +const MimAllocator = struct { + fn alloc( + _: *anyopaque, + len: usize, + log2_align: u8, + _: usize, + ) ?[*]u8 { + return @ptrCast( + mi.mi_malloc_aligned( + len, + @as(usize, 1) << @as(u6, @intCast(log2_align)), + ), + ); + } + + fn resize( + _: *anyopaque, + buf: []u8, + _: u8, + new_len: usize, + _: usize, + ) bool { + if (new_len > buf.len) { + const available = mi.mi_usable_size(buf.ptr); + if (available > new_len) { + if (mi.mi_expand(buf.ptr, new_len)) |_| { + return true; + } + } + return false; + } else { + return true; + } + } + + fn free( + _: *anyopaque, + buf: []u8, + _: u8, + _: usize, + ) void { + mi.mi_free(buf.ptr); + } +}; + +pub const mim_allocator = Allocator{ + .ptr = undefined, + .vtable = &mim_allocator_vtable, +}; +const mim_allocator_vtable = Allocator.VTable{ + .alloc = MimAllocator.alloc, + .resize = MimAllocator.resize, + .free = MimAllocator.free, +}; diff --git a/src/lsp/DocumentStore.zig b/src/lsp/DocumentStore.zig new file mode 100644 index 00000000..0ba587af --- /dev/null +++ b/src/lsp/DocumentStore.zig @@ -0,0 +1,27 @@ +const std = @import("std"); +const node = @import("../node.zig"); + +const Self = @This(); + +pub const Uri = []const u8; + +pub const Document = struct { + uri: Uri, + root: *node.ParseNode, + + // TODO: add here things like, already computed list of symbols etc. +}; + +allocator: std.mem.Allocator, +documents: std.StringArrayHashMap(*Document), + +pub fn init(allocator: std.mem.Allocator) Self { + return .{ + .allocator = allocator, + .documents = std.StringArrayHashMap(*Document).init(allocator), + }; +} + +pub fn deinit(self: *Self) void { + self.documents.deinit(); +} diff --git a/src/lsp/Server.zig b/src/lsp/Server.zig new file mode 100644 index 00000000..d75a8f08 --- /dev/null +++ b/src/lsp/Server.zig @@ -0,0 +1,244 @@ +const std = @import("std"); +const types = @import("lsp.zig"); +const DocumentStore = @import("DocumentStore.zig"); + +const Self = @This(); + +document_store: DocumentStore, + +/// From zls/src/Server.zig +/// workaround for https://github.com/ziglang/zig/issues/16392 +/// ```zig +/// union(enum) { +/// request: Request, +/// notification: Notification, +/// response: Response, +/// } +/// ```zig +pub const Message = struct { + tag: enum(u32) { + request, + notification, + response, + }, + request: ?Request = null, + notification: ?Notification = null, + response: ?Response = null, + + pub const Request = struct { + id: types.RequestId, + params: Params, + + pub const Params = union(enum) { + initialize: types.InitializeParams, + shutdown: void, + @"textDocument/willSaveWaitUntil": types.WillSaveTextDocumentParams, + @"textDocument/semanticTokens/full": types.SemanticTokensParams, + @"textDocument/semanticTokens/range": types.SemanticTokensRangeParams, + @"textDocument/inlayHint": types.InlayHintParams, + @"textDocument/completion": types.CompletionParams, + @"textDocument/signatureHelp": types.SignatureHelpParams, + @"textDocument/definition": types.DefinitionParams, + @"textDocument/typeDefinition": types.TypeDefinitionParams, + @"textDocument/implementation": types.ImplementationParams, + @"textDocument/declaration": types.DeclarationParams, + @"textDocument/hover": types.HoverParams, + @"textDocument/documentSymbol": types.DocumentSymbolParams, + @"textDocument/formatting": types.DocumentFormattingParams, + @"textDocument/rename": types.RenameParams, + @"textDocument/references": types.ReferenceParams, + @"textDocument/documentHighlight": types.DocumentHighlightParams, + @"textDocument/codeAction": types.CodeActionParams, + @"textDocument/foldingRange": types.FoldingRangeParams, + @"textDocument/selectionRange": types.SelectionRangeParams, + unknown: []const u8, + }; + }; + + pub const Notification = union(enum) { + initialized: types.InitializedParams, + exit: void, + @"$/cancelRequest": types.CancelParams, + @"$/setTrace": types.SetTraceParams, + @"textDocument/didOpen": types.DidOpenTextDocumentParams, + @"textDocument/didChange": types.DidChangeTextDocumentParams, + @"textDocument/didSave": types.DidSaveTextDocumentParams, + @"textDocument/didClose": types.DidCloseTextDocumentParams, + @"workspace/didChangeWorkspaceFolders": types.DidChangeWorkspaceFoldersParams, + @"workspace/didChangeConfiguration": types.DidChangeConfigurationParams, + unknown: []const u8, + }; + + pub const Response = struct { + id: types.RequestId, + data: Data, + + pub const Data = union(enum) { + result: types.LSPAny, + @"error": types.ResponseError, + }; + }; + + pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) std.json.ParseError(@TypeOf(source.*))!Message { + const json_value = try std.json.parseFromTokenSourceLeaky(std.json.Value, allocator, source, options); + return try jsonParseFromValue(allocator, json_value, options); + } + + pub fn jsonParseFromValue( + allocator: std.mem.Allocator, + source: std.json.Value, + options: std.json.ParseOptions, + ) !Message { + if (source != .object) return error.UnexpectedToken; + const object = source.object; + + @setEvalBranchQuota(10_000); + if (object.get("id")) |id_obj| { + const msg_id = try std.json.parseFromValueLeaky(types.RequestId, allocator, id_obj, options); + + if (object.get("method")) |method_obj| { + const msg_method = try std.json.parseFromValueLeaky([]const u8, allocator, method_obj, options); + + const msg_params = object.get("params") orelse .null; + + const fields = @typeInfo(Request.Params).Union.fields; + + inline for (fields) |field| { + if (std.mem.eql(u8, msg_method, field.name)) { + const params = if (field.type == void) + void{} + else + try std.json.parseFromValueLeaky(field.type, allocator, msg_params, options); + + return .{ + .tag = .request, + .request = .{ + .id = msg_id, + .params = @unionInit(Request.Params, field.name, params), + }, + }; + } + } + return .{ + .tag = .request, + .request = .{ + .id = msg_id, + .params = .{ .unknown = msg_method }, + }, + }; + } else { + const result = object.get("result") orelse .null; + const error_obj = object.get("error") orelse .null; + + const err = try std.json.parseFromValueLeaky(?types.ResponseError, allocator, error_obj, options); + + if (result != .null and err != null) return error.UnexpectedToken; + + if (err) |e| { + return .{ + .tag = .response, + .response = .{ + .id = msg_id, + .data = .{ .@"error" = e }, + }, + }; + } else { + return .{ + .tag = .response, + .response = .{ + .id = msg_id, + .data = .{ .result = result }, + }, + }; + } + } + } else { + const method_obj = object.get("method") orelse return error.UnexpectedToken; + const msg_method = try std.json.parseFromValueLeaky([]const u8, allocator, method_obj, options); + + const msg_params = object.get("params") orelse .null; + + const fields = @typeInfo(Notification).Union.fields; + + inline for (fields) |field| { + if (std.mem.eql(u8, msg_method, field.name)) { + const params = if (field.type == void) + void{} + else + try std.json.parseFromValueLeaky(field.type, allocator, msg_params, options); + + return .{ + .tag = .notification, + .notification = @unionInit(Notification, field.name, params), + }; + } + } + return .{ + .tag = .notification, + .notification = .{ .unknown = msg_method }, + }; + } + } + + pub fn isBlocking(self: Message) bool { + switch (self.tag) { + .request => switch (self.request.?.params) { + .initialize, + .shutdown, + => return true, + .@"textDocument/willSaveWaitUntil", + .@"textDocument/semanticTokens/full", + .@"textDocument/semanticTokens/range", + .@"textDocument/inlayHint", + .@"textDocument/completion", + .@"textDocument/signatureHelp", + .@"textDocument/definition", + .@"textDocument/typeDefinition", + .@"textDocument/implementation", + .@"textDocument/declaration", + .@"textDocument/hover", + .@"textDocument/documentSymbol", + .@"textDocument/formatting", + .@"textDocument/rename", + .@"textDocument/references", + .@"textDocument/documentHighlight", + .@"textDocument/codeAction", + .@"textDocument/foldingRange", + .@"textDocument/selectionRange", + => return false, + .unknown => return false, + }, + .notification => switch (self.notification.?) { + .@"$/cancelRequest" => return false, + .initialized, + .exit, + .@"$/setTrace", + .@"textDocument/didOpen", + .@"textDocument/didChange", + .@"textDocument/didSave", + .@"textDocument/didClose", + .@"workspace/didChangeWorkspaceFolders", + .@"workspace/didChangeConfiguration", + => return true, + .unknown => return false, + }, + .response => return true, + } + } + + pub fn format(message: Message, comptime fmt_str: []const u8, options: std.fmt.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + _ = options; + if (fmt_str.len != 0) std.fmt.invalidFmtError(fmt_str, message); + switch (message.tag) { + .request => try writer.print("request-{}-{s}", .{ message.request.?.id, switch (message.request.?.params) { + .unknown => |method| method, + else => @tagName(message.request.?.params), + } }), + .notification => try writer.print("notification-{s}", .{switch (message.notification.?) { + .unknown => |method| method, + else => @tagName(message.notification.?), + }}), + .response => try writer.print("response-{}", .{message.response.?.id}), + } + } +}; diff --git a/src/lsp/lsp.zig b/src/lsp/lsp.zig new file mode 100644 index 00000000..94691a14 --- /dev/null +++ b/src/lsp/lsp.zig @@ -0,0 +1,7987 @@ +//! generated by zig-lsp-codegen + +const std = @import("std"); + +const URI = []const u8; +/// The URI of a document +pub const DocumentUri = []const u8; +/// A JavaScript regular expression; never used +pub const RegExp = []const u8; + +pub const LSPAny = std.json.Value; +pub const LSPArray = []LSPAny; +pub const LSPObject = std.json.ObjectMap; + +pub const RequestId = union(enum) { + integer: i64, + string: []const u8, + pub usingnamespace UnionParser(@This()); +}; + +pub const ResponseError = struct { + /// A number indicating the error type that occurred. + code: i64, + /// A string providing a short description of the error. + message: []const u8, + + /// A primitive or structured value that contains additional + /// information about the error. Can be omitted. + data: std.json.Value = .null, +}; + +/// Indicates in which direction a message is sent in the protocol. +pub const MessageDirection = enum { + clientToServer, + serverToClient, + both, +}; + +pub const RegistrationMetadata = struct { + method: ?[]const u8, + Options: ?type, +}; + +pub const NotificationMetadata = struct { + method: []const u8, + documentation: ?[]const u8, + direction: MessageDirection, + Params: ?type, + registration: RegistrationMetadata, +}; + +pub const RequestMetadata = struct { + method: []const u8, + documentation: ?[]const u8, + direction: MessageDirection, + Params: ?type, + Result: type, + PartialResult: ?type, + ErrorData: ?type, + registration: RegistrationMetadata, +}; + +pub fn Map(comptime Key: type, comptime Value: type) type { + if (Key != []const u8) @compileError("TODO support non string Key's"); + return std.json.ArrayHashMap(Value); +} + +pub fn UnionParser(comptime T: type) type { + return struct { + pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) std.json.ParseError(@TypeOf(source.*))!T { + const json_value = try std.json.parseFromTokenSourceLeaky(std.json.Value, allocator, source, options); + return try jsonParseFromValue(allocator, json_value, options); + } + + pub fn jsonParseFromValue(allocator: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) std.json.ParseFromValueError!T { + inline for (std.meta.fields(T)) |field| { + if (std.json.parseFromValueLeaky(field.type, allocator, source, options)) |result| { + return @unionInit(T, field.name, result); + } else |_| {} + } + return error.UnexpectedToken; + } + + pub fn jsonStringify(self: T, stream: anytype) @TypeOf(stream.*).Error!void { + switch (self) { + inline else => |value| try stream.write(value), + } + } + }; +} + +pub fn EnumCustomStringValues(comptime T: type, comptime contains_empty_enum: bool) type { + return struct { + const kvs = build_kvs: { + const KV = struct { []const u8, T }; + const fields = @typeInfo(T).Union.fields; + var kvs_array: [fields.len - 1]KV = undefined; + for (fields[0 .. fields.len - 1], 0..) |field, i| { + kvs_array[i] = .{ field.name, @field(T, field.name) }; + } + break :build_kvs kvs_array[0..]; + }; + /// NOTE: this maps 'empty' to .empty when T contains an empty enum + /// this shouldn't happen but this doesn't do any harm + const map = std.ComptimeStringMap(T, kvs); + + pub fn eql(a: T, b: T) bool { + const tag_a = std.meta.activeTag(a); + const tag_b = std.meta.activeTag(b); + if (tag_a != tag_b) return false; + + if (tag_a == .custom_value) { + return std.mem.eql(u8, a.custom_value, b.custom_value); + } else { + return true; + } + } + + pub fn jsonParse(allocator: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) std.json.ParseError(@TypeOf(source.*))!T { + const slice = try std.json.parseFromTokenSourceLeaky([]const u8, allocator, source, options); + if (contains_empty_enum and slice.len == 0) return .empty; + return map.get(slice) orelse return .{ .custom_value = slice }; + } + + pub fn jsonParseFromValue(allocator: std.mem.Allocator, source: std.json.Value, options: std.json.ParseOptions) std.json.ParseFromValueError!T { + const slice = try std.json.parseFromValueLeaky([]const u8, allocator, source, options); + if (contains_empty_enum and slice.len == 0) return .empty; + return map.get(slice) orelse return .{ .custom_value = slice }; + } + + pub fn jsonStringify(self: T, stream: anytype) @TypeOf(stream.*).Error!void { + if (contains_empty_enum and self == .empty) { + try stream.write(""); + return; + } + switch (self) { + .custom_value => |str| try stream.write(str), + else => |val| try stream.write(@tagName(val)), + } + } + }; +} + +pub fn EnumStringifyAsInt(comptime T: type) type { + return struct { + pub fn jsonStringify(self: T, stream: anytype) @TypeOf(stream.*).Error!void { + try stream.write(@intFromEnum(self)); + } + }; +} + +comptime { + _ = @field(@This(), "notification_metadata"); + _ = @field(@This(), "request_metadata"); +} + +// Type Aliases + +/// The definition of a symbol represented as one or many {@link Location locations}. +/// For most programming languages there is only one location at which a symbol is +/// defined. +/// +/// Servers should prefer returning `DefinitionLink` over `Definition` if supported +/// by the client. +pub const Definition = union(enum) { + Location: Location, + array_of_Location: []const Location, + pub usingnamespace UnionParser(@This()); +}; + +/// Information about where a symbol is defined. +/// +/// Provides additional metadata over normal {@link Location location} definitions, including the range of +/// the defining symbol +pub const DefinitionLink = LocationLink; + +/// The declaration of a symbol representation as one or many {@link Location locations}. +pub const Declaration = union(enum) { + Location: Location, + array_of_Location: []const Location, + pub usingnamespace UnionParser(@This()); +}; + +/// Information about where a symbol is declared. +/// +/// Provides additional metadata over normal {@link Location location} declarations, including the range of +/// the declaring symbol. +/// +/// Servers should prefer returning `DeclarationLink` over `Declaration` if supported +/// by the client. +pub const DeclarationLink = LocationLink; + +/// Inline value information can be provided by different means: +/// - directly as a text value (class InlineValueText). +/// - as a name to use for a variable lookup (class InlineValueVariableLookup) +/// - as an evaluatable expression (class InlineValueEvaluatableExpression) +/// The InlineValue types combines all inline value types into one type. +/// +/// @since 3.17.0 +pub const InlineValue = union(enum) { + InlineValueText: InlineValueText, + InlineValueVariableLookup: InlineValueVariableLookup, + InlineValueEvaluatableExpression: InlineValueEvaluatableExpression, + pub usingnamespace UnionParser(@This()); +}; + +/// The result of a document diagnostic pull request. A report can +/// either be a full report containing all diagnostics for the +/// requested document or an unchanged report indicating that nothing +/// has changed in terms of diagnostics in comparison to the last +/// pull request. +/// +/// @since 3.17.0 +pub const DocumentDiagnosticReport = union(enum) { + RelatedFullDocumentDiagnosticReport: RelatedFullDocumentDiagnosticReport, + RelatedUnchangedDocumentDiagnosticReport: RelatedUnchangedDocumentDiagnosticReport, + pub usingnamespace UnionParser(@This()); +}; + +pub const PrepareRenameResult = union(enum) { + Range: Range, + PrepareRenamePlaceholder: PrepareRenamePlaceholder, + PrepareRenameDefaultBehavior: PrepareRenameDefaultBehavior, + pub usingnamespace UnionParser(@This()); +}; + +/// A document selector is the combination of one or many document filters. +/// +/// @sample `let sel:DocumentSelector = [{ language: 'typescript' }, { language: 'json', pattern: '**∕tsconfig.json' }]`; +/// +/// The use of a string as a document filter is deprecated @since 3.16.0. +pub const DocumentSelector = []const DocumentFilter; + +pub const ProgressToken = union(enum) { + integer: i32, + string: []const u8, + pub usingnamespace UnionParser(@This()); +}; + +/// An identifier to refer to a change annotation stored with a workspace edit. +pub const ChangeAnnotationIdentifier = []const u8; + +/// A workspace diagnostic document report. +/// +/// @since 3.17.0 +pub const WorkspaceDocumentDiagnosticReport = union(enum) { + WorkspaceFullDocumentDiagnosticReport: WorkspaceFullDocumentDiagnosticReport, + WorkspaceUnchangedDocumentDiagnosticReport: WorkspaceUnchangedDocumentDiagnosticReport, + pub usingnamespace UnionParser(@This()); +}; + +/// An event describing a change to a text document. If only a text is provided +/// it is considered to be the full content of the document. +pub const TextDocumentContentChangeEvent = union(enum) { + TextDocumentContentChangePartial: TextDocumentContentChangePartial, + TextDocumentContentChangeWholeDocument: TextDocumentContentChangeWholeDocument, + pub usingnamespace UnionParser(@This()); +}; + +/// MarkedString can be used to render human readable text. It is either a markdown string +/// or a code-block that provides a language and a code snippet. The language identifier +/// is semantically equal to the optional language identifier in fenced code blocks in GitHub +/// issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting +/// +/// The pair of a language and a value is an equivalent to markdown: +/// ```${language} +/// ${value} +/// ``` +/// +/// Note that markdown strings will be sanitized - that means html will be escaped. +/// @deprecated use MarkupContent instead. +pub const MarkedString = union(enum) { + string: []const u8, + MarkedStringWithLanguage: MarkedStringWithLanguage, + pub usingnamespace UnionParser(@This()); +}; + +/// A document filter describes a top level text document or +/// a notebook cell document. +/// +/// @since 3.17.0 - proposed support for NotebookCellTextDocumentFilter. +pub const DocumentFilter = union(enum) { + TextDocumentFilter: TextDocumentFilter, + NotebookCellTextDocumentFilter: NotebookCellTextDocumentFilter, + pub usingnamespace UnionParser(@This()); +}; + +/// The glob pattern. Either a string pattern or a relative pattern. +/// +/// @since 3.17.0 +pub const GlobPattern = union(enum) { + Pattern: Pattern, + RelativePattern: RelativePattern, + pub usingnamespace UnionParser(@This()); +}; + +/// A document filter denotes a document by different properties like +/// the {@link TextDocument.languageId language}, the {@link Uri.scheme scheme} of +/// its resource, or a glob-pattern that is applied to the {@link TextDocument.fileName path}. +/// +/// Glob patterns can have the following syntax: +/// - `*` to match one or more characters in a path segment +/// - `?` to match on one character in a path segment +/// - `**` to match any number of path segments, including none +/// - `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) +/// - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +/// - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) +/// +/// @sample A language filter that applies to typescript files on disk: `{ language: 'typescript', scheme: 'file' }` +/// @sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }` +/// +/// @since 3.17.0 +pub const TextDocumentFilter = union(enum) { + TextDocumentFilterLanguage: TextDocumentFilterLanguage, + TextDocumentFilterScheme: TextDocumentFilterScheme, + TextDocumentFilterPattern: TextDocumentFilterPattern, + pub usingnamespace UnionParser(@This()); +}; + +/// The glob pattern to watch relative to the base path. Glob patterns can have the following syntax: +/// - `*` to match one or more characters in a path segment +/// - `?` to match on one character in a path segment +/// - `**` to match any number of path segments, including none +/// - `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) +/// - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +/// - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) +/// +/// @since 3.17.0 +pub const Pattern = []const u8; + +/// A notebook document filter denotes a notebook document by +/// different properties. The properties will be match +/// against the notebook's URI (same as with documents) +/// +/// @since 3.17.0 +pub const NotebookDocumentFilter = union(enum) { + NotebookDocumentFilterNotebookType: NotebookDocumentFilterNotebookType, + NotebookDocumentFilterScheme: NotebookDocumentFilterScheme, + NotebookDocumentFilterPattern: NotebookDocumentFilterPattern, + pub usingnamespace UnionParser(@This()); +}; + +// Enumerations + +/// A set of predefined token types. This set is not fixed +/// an clients can specify additional token types via the +/// corresponding client capabilities. +/// +/// @since 3.16.0 +pub const SemanticTokenTypes = union(enum) { + namespace, + /// Represents a generic type. Acts as a fallback for types which can't be mapped to + /// a specific type like class or enum. + type, + class, + @"enum", + interface, + @"struct", + typeParameter, + parameter, + variable, + property, + enumMember, + event, + function, + method, + macro, + keyword, + modifier, + comment, + string, + number, + regexp, + operator, + /// @since 3.17.0 + decorator, + custom_value: []const u8, + pub usingnamespace EnumCustomStringValues(@This(), false); +}; + +/// A set of predefined token modifiers. This set is not fixed +/// an clients can specify additional token types via the +/// corresponding client capabilities. +/// +/// @since 3.16.0 +pub const SemanticTokenModifiers = union(enum) { + declaration, + definition, + readonly, + static, + deprecated, + abstract, + @"async", + modification, + documentation, + defaultLibrary, + custom_value: []const u8, + pub usingnamespace EnumCustomStringValues(@This(), false); +}; + +/// The document diagnostic report kinds. +/// +/// @since 3.17.0 +pub const DocumentDiagnosticReportKind = enum { + /// A diagnostic report with a full + /// set of problems. + full, + /// A report indicating that the last + /// returned report is still accurate. + unchanged, +}; + +/// Predefined error codes. +pub const ErrorCodes = enum(i32) { + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + /// Error code indicating that a server received a notification or + /// request before the server has received the `initialize` request. + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + _, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +pub const LSPErrorCodes = enum(i32) { + /// A request failed but it was syntactically correct, e.g the + /// method name was known and the parameters were valid. The error + /// message should contain human readable information about why + /// the request failed. + /// + /// @since 3.17.0 + RequestFailed = -32803, + /// The server cancelled the request. This error code should + /// only be used for requests that explicitly support being + /// server cancellable. + /// + /// @since 3.17.0 + ServerCancelled = -32802, + /// The server detected that the content of a document got + /// modified outside normal conditions. A server should + /// NOT send this error code if it detects a content change + /// in it unprocessed messages. The result even computed + /// on an older state might still be useful for the client. + /// + /// If a client decides that a result is not of any use anymore + /// the client should cancel the request. + ContentModified = -32801, + /// The client has canceled a request and a server as detected + /// the cancel. + RequestCancelled = -32800, + _, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// A set of predefined range kinds. +pub const FoldingRangeKind = union(enum) { + /// Folding range for a comment + comment, + /// Folding range for an import or include + imports, + /// Folding range for a region (e.g. `#region`) + region, + custom_value: []const u8, + pub usingnamespace EnumCustomStringValues(@This(), false); +}; + +/// A symbol kind. +pub const SymbolKind = enum(u32) { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// Symbol tags are extra annotations that tweak the rendering of a symbol. +/// +/// @since 3.16 +pub const SymbolTag = enum(u32) { + /// Render a symbol as obsolete, usually using a strike-out. + Deprecated = 1, + placeholder__, // fixes alignment issue + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// Moniker uniqueness level to define scope of the moniker. +/// +/// @since 3.16.0 +pub const UniquenessLevel = enum { + /// The moniker is only unique inside a document + document, + /// The moniker is unique inside a project for which a dump got created + project, + /// The moniker is unique inside the group to which a project belongs + group, + /// The moniker is unique inside the moniker scheme. + scheme, + /// The moniker is globally unique + global, +}; + +/// The moniker kind. +/// +/// @since 3.16.0 +pub const MonikerKind = enum { + /// The moniker represent a symbol that is imported into a project + import, + /// The moniker represents a symbol that is exported from a project + @"export", + /// The moniker represents a symbol that is local to a project (e.g. a local + /// variable of a function, a class not visible outside the project, ...) + local, +}; + +/// Inlay hint kinds. +/// +/// @since 3.17.0 +pub const InlayHintKind = enum(u32) { + /// An inlay hint that for a type annotation. + Type = 1, + /// An inlay hint that is for a parameter. + Parameter = 2, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// The message type +pub const MessageType = enum(u32) { + /// An error message. + Error = 1, + /// A warning message. + Warning = 2, + /// An information message. + Info = 3, + /// A log message. + Log = 4, + /// A debug message. + /// + /// @since 3.18.0 + Debug = 5, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// Defines how the host (editor) should sync +/// document changes to the language server. +pub const TextDocumentSyncKind = enum(u32) { + /// Documents should not be synced at all. + None = 0, + /// Documents are synced by always sending the full content + /// of the document. + Full = 1, + /// Documents are synced by sending the full content on open. + /// After that only incremental updates to the document are + /// send. + Incremental = 2, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// Represents reasons why a text document is saved. +pub const TextDocumentSaveReason = enum(u32) { + /// Manually triggered, e.g. by the user pressing save, by starting debugging, + /// or by an API call. + Manual = 1, + /// Automatic after a delay. + AfterDelay = 2, + /// When the editor lost focus. + FocusOut = 3, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// The kind of a completion entry. +pub const CompletionItemKind = enum(u32) { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// Completion item tags are extra annotations that tweak the rendering of a completion +/// item. +/// +/// @since 3.15.0 +pub const CompletionItemTag = enum(u32) { + /// Render a completion as obsolete, usually using a strike-out. + Deprecated = 1, + placeholder__, // fixes alignment issue + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// Defines whether the insert text in a completion item should be interpreted as +/// plain text or a snippet. +pub const InsertTextFormat = enum(u32) { + /// The primary text to be inserted is treated as a plain string. + PlainText = 1, + /// The primary text to be inserted is treated as a snippet. + /// + /// A snippet can define tab stops and placeholders with `$1`, `$2` + /// and `${3:foo}`. `$0` defines the final tab stop, it defaults to + /// the end of the snippet. Placeholders with equal identifiers are linked, + /// that is typing in one will update others too. + /// + /// See also: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#snippet_syntax + Snippet = 2, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// How whitespace and indentation is handled during completion +/// item insertion. +/// +/// @since 3.16.0 +pub const InsertTextMode = enum(u32) { + /// The insertion or replace strings is taken as it is. If the + /// value is multi line the lines below the cursor will be + /// inserted using the indentation defined in the string value. + /// The client will not apply any kind of adjustments to the + /// string. + asIs = 1, + /// The editor adjusts leading whitespace of new lines so that + /// they match the indentation up to the cursor of the line for + /// which the item is accepted. + /// + /// Consider a line like this: <2tabs><3tabs>foo. Accepting a + /// multi line completion item is indented using 2 tabs and all + /// following lines inserted will be indented using 2 tabs as well. + adjustIndentation = 2, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// A document highlight kind. +pub const DocumentHighlightKind = enum(u32) { + /// A textual occurrence. + Text = 1, + /// Read-access of a symbol, like reading a variable. + Read = 2, + /// Write-access of a symbol, like writing to a variable. + Write = 3, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// A set of predefined code action kinds +pub const CodeActionKind = union(enum) { + /// Empty kind. + empty, + /// Base kind for quickfix actions: 'quickfix' + quickfix, + /// Base kind for refactoring actions: 'refactor' + refactor, + /// Base kind for refactoring extraction actions: 'refactor.extract' + /// + /// Example extract actions: + /// + /// - Extract method + /// - Extract function + /// - Extract variable + /// - Extract interface from class + /// - ... + @"refactor.extract", + /// Base kind for refactoring inline actions: 'refactor.inline' + /// + /// Example inline actions: + /// + /// - Inline function + /// - Inline variable + /// - Inline constant + /// - ... + @"refactor.inline", + /// Base kind for refactoring rewrite actions: 'refactor.rewrite' + /// + /// Example rewrite actions: + /// + /// - Convert JavaScript function to class + /// - Add or remove parameter + /// - Encapsulate field + /// - Make method static + /// - Move method to base class + /// - ... + @"refactor.rewrite", + /// Base kind for source actions: `source` + /// + /// Source code actions apply to the entire file. + source, + /// Base kind for an organize imports source action: `source.organizeImports` + @"source.organizeImports", + /// Base kind for auto-fix source actions: `source.fixAll`. + /// + /// Fix all actions automatically fix errors that have a clear fix that do not require user input. + /// They should not suppress errors or perform unsafe fixes such as generating new types or classes. + /// + /// @since 3.15.0 + @"source.fixAll", + custom_value: []const u8, + pub usingnamespace EnumCustomStringValues(@This(), true); +}; + +pub const TraceValues = enum { + /// Turn tracing off. + off, + /// Trace messages only. + messages, + /// Verbose message tracing. + verbose, +}; + +/// Describes the content type that a client supports in various +/// result literals like `Hover`, `ParameterInfo` or `CompletionItem`. +/// +/// Please note that `MarkupKinds` must not start with a `$`. This kinds +/// are reserved for internal usage. +pub const MarkupKind = enum { + /// Plain text is supported as a content format + plaintext, + /// Markdown is supported as a content format + markdown, +}; + +/// Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. +/// +/// @since 3.18.0 +/// @proposed +pub const InlineCompletionTriggerKind = enum(u32) { + /// Completion was triggered explicitly by a user gesture. + Invoked = 0, + /// Completion was triggered automatically while editing. + Automatic = 1, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// A set of predefined position encoding kinds. +/// +/// @since 3.17.0 +pub const PositionEncodingKind = union(enum) { + /// Character offsets count UTF-8 code units (e.g. bytes). + @"utf-8", + /// Character offsets count UTF-16 code units. + /// + /// This is the default and must always be supported + /// by servers + @"utf-16", + /// Character offsets count UTF-32 code units. + /// + /// Implementation note: these are the same as Unicode codepoints, + /// so this `PositionEncodingKind` may also be used for an + /// encoding-agnostic representation of character offsets. + @"utf-32", + custom_value: []const u8, + pub usingnamespace EnumCustomStringValues(@This(), false); +}; + +/// The file event type +pub const FileChangeType = enum(u32) { + /// The file got created. + Created = 1, + /// The file got changed. + Changed = 2, + /// The file got deleted. + Deleted = 3, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +pub const WatchKind = enum(u32) { + /// Interested in create events. + Create = 1, + /// Interested in change events + Change = 2, + /// Interested in delete events + Delete = 4, + _, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// The diagnostic's severity. +pub const DiagnosticSeverity = enum(u32) { + /// Reports an error. + Error = 1, + /// Reports a warning. + Warning = 2, + /// Reports an information. + Information = 3, + /// Reports a hint. + Hint = 4, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// The diagnostic tags. +/// +/// @since 3.15.0 +pub const DiagnosticTag = enum(u32) { + /// Unused or unnecessary code. + /// + /// Clients are allowed to render diagnostics with this tag faded out instead of having + /// an error squiggle. + Unnecessary = 1, + /// Deprecated or obsolete code. + /// + /// Clients are allowed to rendered diagnostics with this tag strike through. + Deprecated = 2, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// How a completion was triggered +pub const CompletionTriggerKind = enum(u32) { + /// Completion was triggered by typing an identifier (24x7 code + /// complete), manual invocation (e.g Ctrl+Space) or via API. + Invoked = 1, + /// Completion was triggered by a trigger character specified by + /// the `triggerCharacters` properties of the `CompletionRegistrationOptions`. + TriggerCharacter = 2, + /// Completion was re-triggered as current completion list is incomplete + TriggerForIncompleteCompletions = 3, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// How a signature help was triggered. +/// +/// @since 3.15.0 +pub const SignatureHelpTriggerKind = enum(u32) { + /// Signature help was invoked manually by the user or by a command. + Invoked = 1, + /// Signature help was triggered by a trigger character. + TriggerCharacter = 2, + /// Signature help was triggered by the cursor moving or by the document content changing. + ContentChange = 3, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// The reason why code actions were requested. +/// +/// @since 3.17.0 +pub const CodeActionTriggerKind = enum(u32) { + /// Code actions were explicitly requested by the user or by an extension. + Invoked = 1, + /// Code actions were requested automatically. + /// + /// This typically happens when current selection in a file changes, but can + /// also be triggered when file content changes. + Automatic = 2, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +/// A pattern kind describing if a glob pattern matches a file a folder or +/// both. +/// +/// @since 3.16.0 +pub const FileOperationPatternKind = enum { + /// The pattern matches a file only. + file, + /// The pattern matches a folder only. + folder, +}; + +/// A notebook cell kind. +/// +/// @since 3.17.0 +pub const NotebookCellKind = enum(u32) { + /// A markup-cell is formatted source that is used for display. + Markup = 1, + /// A code-cell is source code. + Code = 2, + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +pub const ResourceOperationKind = enum { + /// Supports creating new files and folders. + create, + /// Supports renaming existing files and folders. + rename, + /// Supports deleting existing files and folders. + delete, +}; + +pub const FailureHandlingKind = enum { + /// Applying the workspace change is simply aborted if one of the changes provided + /// fails. All operations executed before the failing operation stay executed. + abort, + /// All operations are executed transactional. That means they either all + /// succeed or no changes at all are applied to the workspace. + transactional, + /// If the workspace edit contains only textual file changes they are executed transactional. + /// If resource changes (create, rename or delete file) are part of the change the failure + /// handling strategy is abort. + textOnlyTransactional, + /// The client tries to undo the operations already executed. But there is no + /// guarantee that this is succeeding. + undo, +}; + +pub const PrepareSupportDefaultBehavior = enum(u32) { + /// The client's default behavior is to select the identifier + /// according the to language's syntax rule. + Identifier = 1, + placeholder__, // fixes alignment issue + pub usingnamespace EnumStringifyAsInt(@This()); +}; + +pub const TokenFormat = enum { + relative, + placeholder__, // fixes alignment issue +}; + +// Structures + +pub const ImplementationParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Represents a location inside a resource, such as a line +/// inside a text file. +pub const Location = struct { + uri: DocumentUri, + range: Range, +}; + +pub const ImplementationRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends ImplementationOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +pub const TypeDefinitionParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +pub const TypeDefinitionRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends TypeDefinitionOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// A workspace folder inside a client. +pub const WorkspaceFolder = struct { + /// The associated URI for this workspace folder. + uri: URI, + /// The name of the workspace folder. Used to refer to this + /// workspace folder in the user interface. + name: []const u8, +}; + +/// The parameters of a `workspace/didChangeWorkspaceFolders` notification. +pub const DidChangeWorkspaceFoldersParams = struct { + /// The actual workspace folder change event. + event: WorkspaceFoldersChangeEvent, +}; + +/// The parameters of a configuration request. +pub const ConfigurationParams = struct { + items: []const ConfigurationItem, +}; + +/// Parameters for a {@link DocumentColorRequest}. +pub const DocumentColorParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Represents a color range from a document. +pub const ColorInformation = struct { + /// The range in the document where this color appears. + range: Range, + /// The actual color value for this color range. + color: Color, +}; + +pub const DocumentColorRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends DocumentColorOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// Parameters for a {@link ColorPresentationRequest}. +pub const ColorPresentationParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + /// The color to request presentations for. + color: Color, + /// The range where the color would be inserted. Serves as a context. + range: Range, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +pub const ColorPresentation = struct { + /// The label of this color presentation. It will be shown on the color + /// picker header. By default this is also the text that is inserted when selecting + /// this color presentation. + label: []const u8, + /// An {@link TextEdit edit} which is applied to a document when selecting + /// this presentation for the color. When `falsy` the {@link ColorPresentation.label label} + /// is used. + textEdit: ?TextEdit = null, + /// An optional array of additional {@link TextEdit text edits} that are applied when + /// selecting this color presentation. Edits must not overlap with the main {@link ColorPresentation.textEdit edit} nor with themselves. + additionalTextEdits: ?[]const TextEdit = null, +}; + +pub const WorkDoneProgressOptions = struct { + workDoneProgress: ?bool = null, +}; + +/// General text document registration options. +pub const TextDocumentRegistrationOptions = struct { + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, +}; + +/// Parameters for a {@link FoldingRangeRequest}. +pub const FoldingRangeParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Represents a folding range. To be valid, start and end line must be bigger than zero and smaller +/// than the number of lines in the document. Clients are free to ignore invalid ranges. +pub const FoldingRange = struct { + /// The zero-based start line of the range to fold. The folded area starts after the line's last character. + /// To be valid, the end must be zero or larger and smaller than the number of lines in the document. + startLine: u32, + /// The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. + startCharacter: ?u32 = null, + /// The zero-based end line of the range to fold. The folded area ends with the line's last character. + /// To be valid, the end must be zero or larger and smaller than the number of lines in the document. + endLine: u32, + /// The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. + endCharacter: ?u32 = null, + /// Describes the kind of the folding range such as `comment' or 'region'. The kind + /// is used to categorize folding ranges and used by commands like 'Fold all comments'. + /// See {@link FoldingRangeKind} for an enumeration of standardized kinds. + kind: ?FoldingRangeKind = null, + /// The text that the client should show when the specified range is + /// collapsed. If not defined or not supported by the client, a default + /// will be chosen by the client. + /// + /// @since 3.17.0 + collapsedText: ?[]const u8 = null, +}; + +pub const FoldingRangeRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends FoldingRangeOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +pub const DeclarationParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +pub const DeclarationRegistrationOptions = struct { + + // Extends DeclarationOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// A parameter literal used in selection range requests. +pub const SelectionRangeParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + /// The positions inside the text document. + positions: []const Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// A selection range represents a part of a selection hierarchy. A selection range +/// may have a parent selection range that contains it. +pub const SelectionRange = struct { + /// The {@link Range range} of this selection range. + range: Range, + /// The parent selection range containing this range. Therefore `parent.range` must contain `this.range`. + parent: ?SelectionRange = null, +}; + +pub const SelectionRangeRegistrationOptions = struct { + + // Extends SelectionRangeOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +pub const WorkDoneProgressCreateParams = struct { + /// The token to be used to report progress. + token: ProgressToken, +}; + +pub const WorkDoneProgressCancelParams = struct { + /// The token to be used to report progress. + token: ProgressToken, +}; + +/// The parameter of a `textDocument/prepareCallHierarchy` request. +/// +/// @since 3.16.0 +pub const CallHierarchyPrepareParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// Represents programming constructs like functions or constructors in the context +/// of call hierarchy. +/// +/// @since 3.16.0 +pub const CallHierarchyItem = struct { + /// The name of this item. + name: []const u8, + /// The kind of this item. + kind: SymbolKind, + /// Tags for this item. + tags: ?[]const SymbolTag = null, + /// More detail for this item, e.g. the signature of a function. + detail: ?[]const u8 = null, + /// The resource identifier of this item. + uri: DocumentUri, + /// The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code. + range: Range, + /// The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function. + /// Must be contained by the {@link CallHierarchyItem.range `range`}. + selectionRange: Range, + /// A data entry field that is preserved between a call hierarchy prepare and + /// incoming calls or outgoing calls requests. + data: ?LSPAny = null, +}; + +/// Call hierarchy options used during static or dynamic registration. +/// +/// @since 3.16.0 +pub const CallHierarchyRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends CallHierarchyOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// The parameter of a `callHierarchy/incomingCalls` request. +/// +/// @since 3.16.0 +pub const CallHierarchyIncomingCallsParams = struct { + item: CallHierarchyItem, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Represents an incoming call, e.g. a caller of a method or constructor. +/// +/// @since 3.16.0 +pub const CallHierarchyIncomingCall = struct { + /// The item that makes the call. + from: CallHierarchyItem, + /// The ranges at which the calls appear. This is relative to the caller + /// denoted by {@link CallHierarchyIncomingCall.from `this.from`}. + fromRanges: []const Range, +}; + +/// The parameter of a `callHierarchy/outgoingCalls` request. +/// +/// @since 3.16.0 +pub const CallHierarchyOutgoingCallsParams = struct { + item: CallHierarchyItem, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc. +/// +/// @since 3.16.0 +pub const CallHierarchyOutgoingCall = struct { + /// The item that is called. + to: CallHierarchyItem, + /// The range at which this item is called. This is the range relative to the caller, e.g the item + /// passed to {@link CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls `provideCallHierarchyOutgoingCalls`} + /// and not {@link CallHierarchyOutgoingCall.to `this.to`}. + fromRanges: []const Range, +}; + +/// @since 3.16.0 +pub const SemanticTokensParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// @since 3.16.0 +pub const SemanticTokens = struct { + /// An optional result id. If provided and clients support delta updating + /// the client will include the result id in the next semantic token request. + /// A server can then instead of computing all semantic tokens again simply + /// send a delta. + resultId: ?[]const u8 = null, + /// The actual tokens. + data: []const u32, +}; + +/// @since 3.16.0 +pub const SemanticTokensPartialResult = struct { + data: []const u32, +}; + +/// @since 3.16.0 +pub const SemanticTokensRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends SemanticTokensOptions + /// The legend used by the server + legend: SemanticTokensLegend, + /// Server supports providing semantic tokens for a specific range + /// of a document. + range: ?union(enum) { + bool: bool, + literal_1: struct {}, + pub usingnamespace UnionParser(@This()); + } = null, + /// Server supports providing semantic tokens for a full document. + full: ?union(enum) { + bool: bool, + SemanticTokensFullDelta: SemanticTokensFullDelta, + pub usingnamespace UnionParser(@This()); + } = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// @since 3.16.0 +pub const SemanticTokensDeltaParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + /// The result id of a previous response. The result Id can either point to a full response + /// or a delta response depending on what was received last. + previousResultId: []const u8, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// @since 3.16.0 +pub const SemanticTokensDelta = struct { + resultId: ?[]const u8 = null, + /// The semantic token edits to transform a previous result into a new result. + edits: []const SemanticTokensEdit, +}; + +/// @since 3.16.0 +pub const SemanticTokensDeltaPartialResult = struct { + edits: []const SemanticTokensEdit, +}; + +/// @since 3.16.0 +pub const SemanticTokensRangeParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + /// The range the semantic tokens are requested for. + range: Range, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Params to show a resource in the UI. +/// +/// @since 3.16.0 +pub const ShowDocumentParams = struct { + /// The uri to show. + uri: URI, + /// Indicates to show the resource in an external program. + /// To show, for example, `https://code.visualstudio.com/` + /// in the default WEB browser set `external` to `true`. + external: ?bool = null, + /// An optional property to indicate whether the editor + /// showing the document should take focus or not. + /// Clients might ignore this property if an external + /// program is started. + takeFocus: ?bool = null, + /// An optional selection range if the document is a text + /// document. Clients might ignore the property if an + /// external program is started or the file is not a text + /// file. + selection: ?Range = null, +}; + +/// The result of a showDocument request. +/// +/// @since 3.16.0 +pub const ShowDocumentResult = struct { + /// A boolean indicating if the show was successful. + success: bool, +}; + +pub const LinkedEditingRangeParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// The result of a linked editing range request. +/// +/// @since 3.16.0 +pub const LinkedEditingRanges = struct { + /// A list of ranges that can be edited together. The ranges must have + /// identical length and contain identical text content. The ranges cannot overlap. + ranges: []const Range, + /// An optional word pattern (regular expression) that describes valid contents for + /// the given ranges. If no pattern is provided, the client configuration's word + /// pattern will be used. + wordPattern: ?[]const u8 = null, +}; + +pub const LinkedEditingRangeRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends LinkedEditingRangeOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// The parameters sent in notifications/requests for user-initiated creation of +/// files. +/// +/// @since 3.16.0 +pub const CreateFilesParams = struct { + /// An array of all files/folders created in this operation. + files: []const FileCreate, +}; + +/// A workspace edit represents changes to many resources managed in the workspace. The edit +/// should either provide `changes` or `documentChanges`. If documentChanges are present +/// they are preferred over `changes` if the client can handle versioned document edits. +/// +/// Since version 3.13.0 a workspace edit can contain resource operations as well. If resource +/// operations are present clients need to execute the operations in the order in which they +/// are provided. So a workspace edit for example can consist of the following two changes: +/// (1) a create file a.txt and (2) a text document edit which insert text into file a.txt. +/// +/// An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will +/// cause failure of the operation. How the client recovers from the failure is described by +/// the client capability: `workspace.workspaceEdit.failureHandling` +pub const WorkspaceEdit = struct { + /// Holds changes to existing resources. + changes: ?Map(DocumentUri, []const TextEdit) = null, + /// Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes + /// are either an array of `TextDocumentEdit`s to express changes to n different text documents + /// where each text document edit addresses a specific version of a text document. Or it can contain + /// above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations. + /// + /// Whether a client supports versioned document edits is expressed via + /// `workspace.workspaceEdit.documentChanges` client capability. + /// + /// If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then + /// only plain `TextEdit`s using the `changes` property are supported. + documentChanges: ?[]const union(enum) { + TextDocumentEdit: TextDocumentEdit, + CreateFile: CreateFile, + RenameFile: RenameFile, + DeleteFile: DeleteFile, + pub usingnamespace UnionParser(@This()); + } = null, + /// A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and + /// delete file / folder operations. + /// + /// Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`. + /// + /// @since 3.16.0 + changeAnnotations: ?Map(ChangeAnnotationIdentifier, ChangeAnnotation) = null, +}; + +/// The options to register for file operations. +/// +/// @since 3.16.0 +pub const FileOperationRegistrationOptions = struct { + /// The actual filters. + filters: []const FileOperationFilter, +}; + +/// The parameters sent in notifications/requests for user-initiated renames of +/// files. +/// +/// @since 3.16.0 +pub const RenameFilesParams = struct { + /// An array of all files/folders renamed in this operation. When a folder is renamed, only + /// the folder will be included, and not its children. + files: []const FileRename, +}; + +/// The parameters sent in notifications/requests for user-initiated deletes of +/// files. +/// +/// @since 3.16.0 +pub const DeleteFilesParams = struct { + /// An array of all files/folders deleted in this operation. + files: []const FileDelete, +}; + +pub const MonikerParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Moniker definition to match LSIF 0.5 moniker definition. +/// +/// @since 3.16.0 +pub const Moniker = struct { + /// The scheme of the moniker. For example tsc or .Net + scheme: []const u8, + /// The identifier of the moniker. The value is opaque in LSIF however + /// schema owners are allowed to define the structure if they want. + identifier: []const u8, + /// The scope in which the moniker is unique + unique: UniquenessLevel, + /// The moniker kind if known. + kind: ?MonikerKind = null, +}; + +pub const MonikerRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends MonikerOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The parameter of a `textDocument/prepareTypeHierarchy` request. +/// +/// @since 3.17.0 +pub const TypeHierarchyPrepareParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// @since 3.17.0 +pub const TypeHierarchyItem = struct { + /// The name of this item. + name: []const u8, + /// The kind of this item. + kind: SymbolKind, + /// Tags for this item. + tags: ?[]const SymbolTag = null, + /// More detail for this item, e.g. the signature of a function. + detail: ?[]const u8 = null, + /// The resource identifier of this item. + uri: DocumentUri, + /// The range enclosing this symbol not including leading/trailing whitespace + /// but everything else, e.g. comments and code. + range: Range, + /// The range that should be selected and revealed when this symbol is being + /// picked, e.g. the name of a function. Must be contained by the + /// {@link TypeHierarchyItem.range `range`}. + selectionRange: Range, + /// A data entry field that is preserved between a type hierarchy prepare and + /// supertypes or subtypes requests. It could also be used to identify the + /// type hierarchy in the server, helping improve the performance on + /// resolving supertypes and subtypes. + data: ?LSPAny = null, +}; + +/// Type hierarchy options used during static or dynamic registration. +/// +/// @since 3.17.0 +pub const TypeHierarchyRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends TypeHierarchyOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// The parameter of a `typeHierarchy/supertypes` request. +/// +/// @since 3.17.0 +pub const TypeHierarchySupertypesParams = struct { + item: TypeHierarchyItem, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// The parameter of a `typeHierarchy/subtypes` request. +/// +/// @since 3.17.0 +pub const TypeHierarchySubtypesParams = struct { + item: TypeHierarchyItem, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// A parameter literal used in inline value requests. +/// +/// @since 3.17.0 +pub const InlineValueParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + /// The document range for which inline values should be computed. + range: Range, + /// Additional information about the context in which inline values were + /// requested. + context: InlineValueContext, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// Inline value options used during static or dynamic registration. +/// +/// @since 3.17.0 +pub const InlineValueRegistrationOptions = struct { + + // Extends InlineValueOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// A parameter literal used in inlay hint requests. +/// +/// @since 3.17.0 +pub const InlayHintParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + /// The document range for which inlay hints should be computed. + range: Range, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// Inlay hint information. +/// +/// @since 3.17.0 +pub const InlayHint = struct { + /// The position of this hint. + position: Position, + /// The label of this hint. A human readable string or an array of + /// InlayHintLabelPart label parts. + /// + /// *Note* that neither the string nor the label part can be empty. + label: union(enum) { + string: []const u8, + array_of_InlayHintLabelPart: []const InlayHintLabelPart, + pub usingnamespace UnionParser(@This()); + }, + /// The kind of this hint. Can be omitted in which case the client + /// should fall back to a reasonable default. + kind: ?InlayHintKind = null, + /// Optional text edits that are performed when accepting this inlay hint. + /// + /// *Note* that edits are expected to change the document so that the inlay + /// hint (or its nearest variant) is now part of the document and the inlay + /// hint itself is now obsolete. + textEdits: ?[]const TextEdit = null, + /// The tooltip text when you hover over this item. + tooltip: ?union(enum) { + string: []const u8, + MarkupContent: MarkupContent, + pub usingnamespace UnionParser(@This()); + } = null, + /// Render padding before the hint. + /// + /// Note: Padding should use the editor's background color, not the + /// background color of the hint itself. That means padding can be used + /// to visually align/separate an inlay hint. + paddingLeft: ?bool = null, + /// Render padding after the hint. + /// + /// Note: Padding should use the editor's background color, not the + /// background color of the hint itself. That means padding can be used + /// to visually align/separate an inlay hint. + paddingRight: ?bool = null, + /// A data entry field that is preserved on an inlay hint between + /// a `textDocument/inlayHint` and a `inlayHint/resolve` request. + data: ?LSPAny = null, +}; + +/// Inlay hint options used during static or dynamic registration. +/// +/// @since 3.17.0 +pub const InlayHintRegistrationOptions = struct { + + // Extends InlayHintOptions + /// The server provides support to resolve additional + /// information for an inlay hint item. + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// Parameters of the document diagnostic request. +/// +/// @since 3.17.0 +pub const DocumentDiagnosticParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + /// The additional identifier provided during registration. + identifier: ?[]const u8 = null, + /// The result id of a previous response if provided. + previousResultId: ?[]const u8 = null, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// A partial result for a document diagnostic report. +/// +/// @since 3.17.0 +pub const DocumentDiagnosticReportPartialResult = struct { + relatedDocuments: Map(DocumentUri, union(enum) { + FullDocumentDiagnosticReport: FullDocumentDiagnosticReport, + UnchangedDocumentDiagnosticReport: UnchangedDocumentDiagnosticReport, + pub usingnamespace UnionParser(@This()); + }), +}; + +/// Cancellation data returned from a diagnostic request. +/// +/// @since 3.17.0 +pub const DiagnosticServerCancellationData = struct { + retriggerRequest: bool, +}; + +/// Diagnostic registration options. +/// +/// @since 3.17.0 +pub const DiagnosticRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends DiagnosticOptions + /// An optional identifier under which the diagnostics are + /// managed by the client. + identifier: ?[]const u8 = null, + /// Whether the language has inter file dependencies meaning that + /// editing code in one file can result in a different diagnostic + /// set in another file. Inter file dependencies are common for + /// most programming languages and typically uncommon for linters. + interFileDependencies: bool, + /// The server provides support for workspace diagnostics as well. + workspaceDiagnostics: bool, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// Parameters of the workspace diagnostic request. +/// +/// @since 3.17.0 +pub const WorkspaceDiagnosticParams = struct { + /// The additional identifier provided during registration. + identifier: ?[]const u8 = null, + /// The currently known diagnostic reports with their + /// previous result ids. + previousResultIds: []const PreviousResultId, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// A workspace diagnostic report. +/// +/// @since 3.17.0 +pub const WorkspaceDiagnosticReport = struct { + items: []const WorkspaceDocumentDiagnosticReport, +}; + +/// A partial result for a workspace diagnostic report. +/// +/// @since 3.17.0 +pub const WorkspaceDiagnosticReportPartialResult = struct { + items: []const WorkspaceDocumentDiagnosticReport, +}; + +/// The params sent in an open notebook document notification. +/// +/// @since 3.17.0 +pub const DidOpenNotebookDocumentParams = struct { + /// The notebook document that got opened. + notebookDocument: NotebookDocument, + /// The text documents that represent the content + /// of a notebook cell. + cellTextDocuments: []const TextDocumentItem, +}; + +/// The params sent in a change notebook document notification. +/// +/// @since 3.17.0 +pub const DidChangeNotebookDocumentParams = struct { + /// The notebook document that did change. The version number points + /// to the version after all provided changes have been applied. If + /// only the text document content of a cell changes the notebook version + /// doesn't necessarily have to change. + notebookDocument: VersionedNotebookDocumentIdentifier, + /// The actual changes to the notebook document. + /// + /// The changes describe single state changes to the notebook document. + /// So if there are two changes c1 (at array index 0) and c2 (at array + /// index 1) for a notebook in state S then c1 moves the notebook from + /// S to S' and c2 from S' to S''. So c1 is computed on the state S and + /// c2 is computed on the state S'. + /// + /// To mirror the content of a notebook using change events use the following approach: + /// - start with the same initial content + /// - apply the 'notebookDocument/didChange' notifications in the order you receive them. + /// - apply the `NotebookChangeEvent`s in a single notification in the order + /// you receive them. + change: NotebookDocumentChangeEvent, +}; + +/// The params sent in a save notebook document notification. +/// +/// @since 3.17.0 +pub const DidSaveNotebookDocumentParams = struct { + /// The notebook document that got saved. + notebookDocument: NotebookDocumentIdentifier, +}; + +/// The params sent in a close notebook document notification. +/// +/// @since 3.17.0 +pub const DidCloseNotebookDocumentParams = struct { + /// The notebook document that got closed. + notebookDocument: NotebookDocumentIdentifier, + /// The text documents that represent the content + /// of a notebook cell that got closed. + cellTextDocuments: []const TextDocumentIdentifier, +}; + +/// A parameter literal used in inline completion requests. +/// +/// @since 3.18.0 +/// @proposed +pub const InlineCompletionParams = struct { + /// Additional information about the context in which inline completions were + /// requested. + context: InlineCompletionContext, + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// Represents a collection of {@link InlineCompletionItem inline completion items} to be presented in the editor. +/// +/// @since 3.18.0 +/// @proposed +pub const InlineCompletionList = struct { + /// The inline completion items + items: []const InlineCompletionItem, +}; + +/// An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. +/// +/// @since 3.18.0 +/// @proposed +pub const InlineCompletionItem = struct { + /// The text to replace the range with. Must be set. + insertText: union(enum) { + string: []const u8, + StringValue: StringValue, + pub usingnamespace UnionParser(@This()); + }, + /// A text that is used to decide if this inline completion should be shown. When `falsy` the {@link InlineCompletionItem.insertText} is used. + filterText: ?[]const u8 = null, + /// The range to replace. Must begin and end on the same line. + range: ?Range = null, + /// An optional {@link Command} that is executed *after* inserting this completion. + command: ?Command = null, +}; + +/// Inline completion options used during static or dynamic registration. +/// +/// @since 3.18.0 +/// @proposed +pub const InlineCompletionRegistrationOptions = struct { + + // Extends InlineCompletionOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +pub const RegistrationParams = struct { + registrations: []const Registration, +}; + +pub const UnregistrationParams = struct { + unregisterations: []const Unregistration, +}; + +pub const InitializeParams = struct { + + // Extends _InitializeParams + /// The process Id of the parent process that started + /// the server. + /// + /// Is `null` if the process has not been started by another process. + /// If the parent process is not alive then the server should exit. + processId: ?i32 = null, + /// Information about the client + /// + /// @since 3.15.0 + clientInfo: ?ClientInfo = null, + /// The locale the client is currently showing the user interface + /// in. This must not necessarily be the locale of the operating + /// system. + /// + /// Uses IETF language tags as the value's syntax + /// (See https://en.wikipedia.org/wiki/IETF_language_tag) + /// + /// @since 3.16.0 + locale: ?[]const u8 = null, + /// The rootPath of the workspace. Is null + /// if no folder is open. + /// + /// @deprecated in favour of rootUri. + rootPath: ?[]const u8 = null, + /// The rootUri of the workspace. Is null if no + /// folder is open. If both `rootPath` and `rootUri` are set + /// `rootUri` wins. + /// + /// @deprecated in favour of workspaceFolders. + rootUri: ?DocumentUri = null, + /// The capabilities provided by the client (editor or tool) + capabilities: ClientCapabilities, + /// User provided initialization options. + initializationOptions: ?LSPAny = null, + /// The initial trace setting. If omitted trace is disabled ('off'). + trace: ?TraceValues = null, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Extends WorkspaceFoldersInitializeParams + /// The workspace folders configured in the client when the server starts. + /// + /// This property is only available if the client supports workspace folders. + /// It can be `null` if the client supports workspace folders but none are + /// configured. + /// + /// @since 3.6.0 + workspaceFolders: ?[]const WorkspaceFolder = null, +}; + +/// The result returned from an initialize request. +pub const InitializeResult = struct { + /// The capabilities the language server provides. + capabilities: ServerCapabilities, + /// Information about the server. + /// + /// @since 3.15.0 + serverInfo: ?ServerInfo = null, +}; + +/// The data type of the ResponseError if the +/// initialize request fails. +pub const InitializeError = struct { + /// Indicates whether the client execute the following retry logic: + /// (1) show the message provided by the ResponseError to the user + /// (2) user selects retry or cancel + /// (3) if user selected retry the initialize method is sent again. + retry: bool, +}; + +pub const InitializedParams = struct {}; + +/// The parameters of a change configuration notification. +pub const DidChangeConfigurationParams = struct { + /// The actual changed settings + settings: LSPAny, +}; + +pub const DidChangeConfigurationRegistrationOptions = struct { + section: ?union(enum) { + string: []const u8, + array_of_string: []const []const u8, + pub usingnamespace UnionParser(@This()); + } = null, +}; + +/// The parameters of a notification message. +pub const ShowMessageParams = struct { + /// The message type. See {@link MessageType} + type: MessageType, + /// The actual message. + message: []const u8, +}; + +pub const ShowMessageRequestParams = struct { + /// The message type. See {@link MessageType} + type: MessageType, + /// The actual message. + message: []const u8, + /// The message action items to present. + actions: ?[]const MessageActionItem = null, +}; + +pub const MessageActionItem = struct { + /// A short title like 'Retry', 'Open Log' etc. + title: []const u8, +}; + +/// The log message parameters. +pub const LogMessageParams = struct { + /// The message type. See {@link MessageType} + type: MessageType, + /// The actual message. + message: []const u8, +}; + +/// The parameters sent in an open text document notification +pub const DidOpenTextDocumentParams = struct { + /// The document that was opened. + textDocument: TextDocumentItem, +}; + +/// The change text document notification's parameters. +pub const DidChangeTextDocumentParams = struct { + /// The document that did change. The version number points + /// to the version after all provided content changes have + /// been applied. + textDocument: VersionedTextDocumentIdentifier, + /// The actual content changes. The content changes describe single state changes + /// to the document. So if there are two content changes c1 (at array index 0) and + /// c2 (at array index 1) for a document in state S then c1 moves the document from + /// S to S' and c2 from S' to S''. So c1 is computed on the state S and c2 is computed + /// on the state S'. + /// + /// To mirror the content of a document using change events use the following approach: + /// - start with the same initial content + /// - apply the 'textDocument/didChange' notifications in the order you receive them. + /// - apply the `TextDocumentContentChangeEvent`s in a single notification in the order + /// you receive them. + contentChanges: []const TextDocumentContentChangeEvent, +}; + +/// Describe options to be used when registered for text document change events. +pub const TextDocumentChangeRegistrationOptions = struct { + /// How documents are synced to the server. + syncKind: TextDocumentSyncKind, + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, +}; + +/// The parameters sent in a close text document notification +pub const DidCloseTextDocumentParams = struct { + /// The document that was closed. + textDocument: TextDocumentIdentifier, +}; + +/// The parameters sent in a save text document notification +pub const DidSaveTextDocumentParams = struct { + /// The document that was saved. + textDocument: TextDocumentIdentifier, + /// Optional the content when saved. Depends on the includeText value + /// when the save notification was requested. + text: ?[]const u8 = null, +}; + +/// Save registration options. +pub const TextDocumentSaveRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends SaveOptions + /// The client is supposed to include the content on save. + includeText: ?bool = null, +}; + +/// The parameters sent in a will save text document notification. +pub const WillSaveTextDocumentParams = struct { + /// The document that will be saved. + textDocument: TextDocumentIdentifier, + /// The 'TextDocumentSaveReason'. + reason: TextDocumentSaveReason, +}; + +/// A text edit applicable to a text document. +pub const TextEdit = struct { + /// The range of the text document to be manipulated. To insert + /// text into a document create a range where start === end. + range: Range, + /// The string to be inserted. For delete operations use an + /// empty string. + newText: []const u8, +}; + +/// The watched files change notification's parameters. +pub const DidChangeWatchedFilesParams = struct { + /// The actual file events. + changes: []const FileEvent, +}; + +/// Describe options to be used when registered for text document change events. +pub const DidChangeWatchedFilesRegistrationOptions = struct { + /// The watchers to register. + watchers: []const FileSystemWatcher, +}; + +/// The publish diagnostic notification's parameters. +pub const PublishDiagnosticsParams = struct { + /// The URI for which diagnostic information is reported. + uri: DocumentUri, + /// Optional the version number of the document the diagnostics are published for. + /// + /// @since 3.15.0 + version: ?i32 = null, + /// An array of diagnostic information items. + diagnostics: []const Diagnostic, +}; + +/// Completion parameters +pub const CompletionParams = struct { + /// The completion context. This is only available it the client specifies + /// to send this using the client capability `textDocument.completion.contextSupport === true` + context: ?CompletionContext = null, + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// A completion item represents a text snippet that is +/// proposed to complete text that is being typed. +pub const CompletionItem = struct { + /// The label of this completion item. + /// + /// The label property is also by default the text that + /// is inserted when selecting this completion. + /// + /// If label details are provided the label itself should + /// be an unqualified name of the completion item. + label: []const u8, + /// Additional details for the label + /// + /// @since 3.17.0 + labelDetails: ?CompletionItemLabelDetails = null, + /// The kind of this completion item. Based of the kind + /// an icon is chosen by the editor. + kind: ?CompletionItemKind = null, + /// Tags for this completion item. + /// + /// @since 3.15.0 + tags: ?[]const CompletionItemTag = null, + /// A human-readable string with additional information + /// about this item, like type or symbol information. + detail: ?[]const u8 = null, + /// A human-readable string that represents a doc-comment. + documentation: ?union(enum) { + string: []const u8, + MarkupContent: MarkupContent, + pub usingnamespace UnionParser(@This()); + } = null, + /// Indicates if this item is deprecated. + /// @deprecated Use `tags` instead. + deprecated: ?bool = null, + /// Select this item when showing. + /// + /// *Note* that only one completion item can be selected and that the + /// tool / client decides which item that is. The rule is that the *first* + /// item of those that match best is selected. + preselect: ?bool = null, + /// A string that should be used when comparing this item + /// with other items. When `falsy` the {@link CompletionItem.label label} + /// is used. + sortText: ?[]const u8 = null, + /// A string that should be used when filtering a set of + /// completion items. When `falsy` the {@link CompletionItem.label label} + /// is used. + filterText: ?[]const u8 = null, + /// A string that should be inserted into a document when selecting + /// this completion. When `falsy` the {@link CompletionItem.label label} + /// is used. + /// + /// The `insertText` is subject to interpretation by the client side. + /// Some tools might not take the string literally. For example + /// VS Code when code complete is requested in this example + /// `con` and a completion item with an `insertText` of + /// `console` is provided it will only insert `sole`. Therefore it is + /// recommended to use `textEdit` instead since it avoids additional client + /// side interpretation. + insertText: ?[]const u8 = null, + /// The format of the insert text. The format applies to both the + /// `insertText` property and the `newText` property of a provided + /// `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`. + /// + /// Please note that the insertTextFormat doesn't apply to + /// `additionalTextEdits`. + insertTextFormat: ?InsertTextFormat = null, + /// How whitespace and indentation is handled during completion + /// item insertion. If not provided the clients default value depends on + /// the `textDocument.completion.insertTextMode` client capability. + /// + /// @since 3.16.0 + insertTextMode: ?InsertTextMode = null, + /// An {@link TextEdit edit} which is applied to a document when selecting + /// this completion. When an edit is provided the value of + /// {@link CompletionItem.insertText insertText} is ignored. + /// + /// Most editors support two different operations when accepting a completion + /// item. One is to insert a completion text and the other is to replace an + /// existing text with a completion text. Since this can usually not be + /// predetermined by a server it can report both ranges. Clients need to + /// signal support for `InsertReplaceEdits` via the + /// `textDocument.completion.insertReplaceSupport` client capability + /// property. + /// + /// *Note 1:* The text edit's range as well as both ranges from an insert + /// replace edit must be a [single line] and they must contain the position + /// at which completion has been requested. + /// *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range + /// must be a prefix of the edit's replace range, that means it must be + /// contained and starting at the same position. + /// + /// @since 3.16.0 additional type `InsertReplaceEdit` + textEdit: ?union(enum) { + TextEdit: TextEdit, + InsertReplaceEdit: InsertReplaceEdit, + pub usingnamespace UnionParser(@This()); + } = null, + /// The edit text used if the completion item is part of a CompletionList and + /// CompletionList defines an item default for the text edit range. + /// + /// Clients will only honor this property if they opt into completion list + /// item defaults using the capability `completionList.itemDefaults`. + /// + /// If not provided and a list's default range is provided the label + /// property is used as a text. + /// + /// @since 3.17.0 + textEditText: ?[]const u8 = null, + /// An optional array of additional {@link TextEdit text edits} that are applied when + /// selecting this completion. Edits must not overlap (including the same insert position) + /// with the main {@link CompletionItem.textEdit edit} nor with themselves. + /// + /// Additional text edits should be used to change text unrelated to the current cursor position + /// (for example adding an import statement at the top of the file if the completion item will + /// insert an unqualified type). + additionalTextEdits: ?[]const TextEdit = null, + /// An optional set of characters that when pressed while this completion is active will accept it first and + /// then type that character. *Note* that all commit characters should have `length=1` and that superfluous + /// characters will be ignored. + commitCharacters: ?[]const []const u8 = null, + /// An optional {@link Command command} that is executed *after* inserting this completion. *Note* that + /// additional modifications to the current document should be described with the + /// {@link CompletionItem.additionalTextEdits additionalTextEdits}-property. + command: ?Command = null, + /// A data entry field that is preserved on a completion item between a + /// {@link CompletionRequest} and a {@link CompletionResolveRequest}. + data: ?LSPAny = null, +}; + +/// Represents a collection of {@link CompletionItem completion items} to be presented +/// in the editor. +pub const CompletionList = struct { + /// This list it not complete. Further typing results in recomputing this list. + /// + /// Recomputed lists have all their items replaced (not appended) in the + /// incomplete completion sessions. + isIncomplete: bool, + /// In many cases the items of an actual completion result share the same + /// value for properties like `commitCharacters` or the range of a text + /// edit. A completion list can therefore define item defaults which will + /// be used if a completion item itself doesn't specify the value. + /// + /// If a completion list specifies a default value and a completion item + /// also specifies a corresponding value the one from the item is used. + /// + /// Servers are only allowed to return default values if the client + /// signals support for this via the `completionList.itemDefaults` + /// capability. + /// + /// @since 3.17.0 + itemDefaults: ?CompletionItemDefaults = null, + /// The completion items. + items: []const CompletionItem, +}; + +/// Registration options for a {@link CompletionRequest}. +pub const CompletionRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends CompletionOptions + /// Most tools trigger completion request automatically without explicitly requesting + /// it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user + /// starts to type an identifier. For example if the user types `c` in a JavaScript file + /// code complete will automatically pop up present `console` besides others as a + /// completion item. Characters that make up identifiers don't need to be listed here. + /// + /// If code complete should automatically be trigger on characters not being valid inside + /// an identifier (for example `.` in JavaScript) list them in `triggerCharacters`. + triggerCharacters: ?[]const []const u8 = null, + /// The list of all possible characters that commit a completion. This field can be used + /// if clients don't support individual commit characters per completion item. See + /// `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport` + /// + /// If a server provides both `allCommitCharacters` and commit characters on an individual + /// completion item the ones on the completion item win. + /// + /// @since 3.2.0 + allCommitCharacters: ?[]const []const u8 = null, + /// The server provides support to resolve additional + /// information for a completion item. + resolveProvider: ?bool = null, + /// The server supports the following `CompletionItem` specific + /// capabilities. + /// + /// @since 3.17.0 + completionItem: ?ServerCompletionItemOptions = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Parameters for a {@link HoverRequest}. +pub const HoverParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// The result of a hover request. +pub const Hover = struct { + /// The hover's content + contents: union(enum) { + MarkupContent: MarkupContent, + MarkedString: MarkedString, + array_of_MarkedString: []const MarkedString, + pub usingnamespace UnionParser(@This()); + }, + /// An optional range inside the text document that is used to + /// visualize the hover, e.g. by changing the background color. + range: ?Range = null, +}; + +/// Registration options for a {@link HoverRequest}. +pub const HoverRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends HoverOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Parameters for a {@link SignatureHelpRequest}. +pub const SignatureHelpParams = struct { + /// The signature help context. This is only available if the client specifies + /// to send this using the client capability `textDocument.signatureHelp.contextSupport === true` + /// + /// @since 3.15.0 + context: ?SignatureHelpContext = null, + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// Signature help represents the signature of something +/// callable. There can be multiple signature but only one +/// active and only one active parameter. +pub const SignatureHelp = struct { + /// One or more signatures. + signatures: []const SignatureInformation, + /// The active signature. If omitted or the value lies outside the + /// range of `signatures` the value defaults to zero or is ignored if + /// the `SignatureHelp` has no signatures. + /// + /// Whenever possible implementors should make an active decision about + /// the active signature and shouldn't rely on a default value. + /// + /// In future version of the protocol this property might become + /// mandatory to better express this. + activeSignature: ?u32 = null, + /// The active parameter of the active signature. If omitted or the value + /// lies outside the range of `signatures[activeSignature].parameters` + /// defaults to 0 if the active signature has parameters. If + /// the active signature has no parameters it is ignored. + /// In future version of the protocol this property might become + /// mandatory to better express the active parameter if the + /// active signature does have any. + activeParameter: ?u32 = null, +}; + +/// Registration options for a {@link SignatureHelpRequest}. +pub const SignatureHelpRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends SignatureHelpOptions + /// List of characters that trigger signature help automatically. + triggerCharacters: ?[]const []const u8 = null, + /// List of characters that re-trigger signature help. + /// + /// These trigger characters are only active when signature help is already showing. All trigger characters + /// are also counted as re-trigger characters. + /// + /// @since 3.15.0 + retriggerCharacters: ?[]const []const u8 = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Parameters for a {@link DefinitionRequest}. +pub const DefinitionParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Registration options for a {@link DefinitionRequest}. +pub const DefinitionRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends DefinitionOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Parameters for a {@link ReferencesRequest}. +pub const ReferenceParams = struct { + context: ReferenceContext, + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Registration options for a {@link ReferencesRequest}. +pub const ReferenceRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends ReferenceOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Parameters for a {@link DocumentHighlightRequest}. +pub const DocumentHighlightParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// A document highlight is a range inside a text document which deserves +/// special attention. Usually a document highlight is visualized by changing +/// the background color of its range. +pub const DocumentHighlight = struct { + /// The range this highlight applies to. + range: Range, + /// The highlight kind, default is {@link DocumentHighlightKind.Text text}. + kind: ?DocumentHighlightKind = null, +}; + +/// Registration options for a {@link DocumentHighlightRequest}. +pub const DocumentHighlightRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends DocumentHighlightOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Parameters for a {@link DocumentSymbolRequest}. +pub const DocumentSymbolParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Represents information about programming constructs like variables, classes, +/// interfaces etc. +pub const SymbolInformation = struct { + /// Indicates if this symbol is deprecated. + /// + /// @deprecated Use tags instead + deprecated: ?bool = null, + /// The location of this symbol. The location's range is used by a tool + /// to reveal the location in the editor. If the symbol is selected in the + /// tool the range's start information is used to position the cursor. So + /// the range usually spans more than the actual symbol's name and does + /// normally include things like visibility modifiers. + /// + /// The range doesn't have to denote a node range in the sense of an abstract + /// syntax tree. It can therefore not be used to re-construct a hierarchy of + /// the symbols. + location: Location, + + // Extends BaseSymbolInformation + /// The name of this symbol. + name: []const u8, + /// The kind of this symbol. + kind: SymbolKind, + /// Tags for this symbol. + /// + /// @since 3.16.0 + tags: ?[]const SymbolTag = null, + /// The name of the symbol containing this symbol. This information is for + /// user interface purposes (e.g. to render a qualifier in the user interface + /// if necessary). It can't be used to re-infer a hierarchy for the document + /// symbols. + containerName: ?[]const u8 = null, +}; + +/// Represents programming constructs like variables, classes, interfaces etc. +/// that appear in a document. Document symbols can be hierarchical and they +/// have two ranges: one that encloses its definition and one that points to +/// its most interesting range, e.g. the range of an identifier. +pub const DocumentSymbol = struct { + /// The name of this symbol. Will be displayed in the user interface and therefore must not be + /// an empty string or a string only consisting of white spaces. + name: []const u8, + /// More detail for this symbol, e.g the signature of a function. + detail: ?[]const u8 = null, + /// The kind of this symbol. + kind: SymbolKind, + /// Tags for this document symbol. + /// + /// @since 3.16.0 + tags: ?[]const SymbolTag = null, + /// Indicates if this symbol is deprecated. + /// + /// @deprecated Use tags instead + deprecated: ?bool = null, + /// The range enclosing this symbol not including leading/trailing whitespace but everything else + /// like comments. This information is typically used to determine if the clients cursor is + /// inside the symbol to reveal in the symbol in the UI. + range: Range, + /// The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. + /// Must be contained by the `range`. + selectionRange: Range, + /// Children of this symbol, e.g. properties of a class. + children: ?[]const DocumentSymbol = null, +}; + +/// Registration options for a {@link DocumentSymbolRequest}. +pub const DocumentSymbolRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends DocumentSymbolOptions + /// A human-readable string that is shown when multiple outlines trees + /// are shown for the same document. + /// + /// @since 3.16.0 + label: ?[]const u8 = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The parameters of a {@link CodeActionRequest}. +pub const CodeActionParams = struct { + /// The document in which the command was invoked. + textDocument: TextDocumentIdentifier, + /// The range for which the command was invoked. + range: Range, + /// Context carrying additional information. + context: CodeActionContext, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Represents a reference to a command. Provides a title which +/// will be used to represent a command in the UI and, optionally, +/// an array of arguments which will be passed to the command handler +/// function when invoked. +pub const Command = struct { + /// Title of the command, like `save`. + title: []const u8, + /// The identifier of the actual command handler. + command: []const u8, + /// Arguments that the command handler should be + /// invoked with. + arguments: ?[]const LSPAny = null, +}; + +/// A code action represents a change that can be performed in code, e.g. to fix a problem or +/// to refactor code. +/// +/// A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed. +pub const CodeAction = struct { + /// A short, human-readable, title for this code action. + title: []const u8, + /// The kind of the code action. + /// + /// Used to filter code actions. + kind: ?CodeActionKind = null, + /// The diagnostics that this code action resolves. + diagnostics: ?[]const Diagnostic = null, + /// Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted + /// by keybindings. + /// + /// A quick fix should be marked preferred if it properly addresses the underlying error. + /// A refactoring should be marked preferred if it is the most reasonable choice of actions to take. + /// + /// @since 3.15.0 + isPreferred: ?bool = null, + /// Marks that the code action cannot currently be applied. + /// + /// Clients should follow the following guidelines regarding disabled code actions: + /// + /// - Disabled code actions are not shown in automatic [lightbulbs](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) + /// code action menus. + /// + /// - Disabled actions are shown as faded out in the code action menu when the user requests a more specific type + /// of code action, such as refactorings. + /// + /// - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) + /// that auto applies a code action and only disabled code actions are returned, the client should show the user an + /// error message with `reason` in the editor. + /// + /// @since 3.16.0 + disabled: ?CodeActionDisabled = null, + /// The workspace edit this code action performs. + edit: ?WorkspaceEdit = null, + /// A command this code action executes. If a code action + /// provides an edit and a command, first the edit is + /// executed and then the command. + command: ?Command = null, + /// A data entry field that is preserved on a code action between + /// a `textDocument/codeAction` and a `codeAction/resolve` request. + /// + /// @since 3.16.0 + data: ?LSPAny = null, +}; + +/// Registration options for a {@link CodeActionRequest}. +pub const CodeActionRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends CodeActionOptions + /// CodeActionKinds that this server may return. + /// + /// The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server + /// may list out every specific kind they provide. + codeActionKinds: ?[]const CodeActionKind = null, + /// The server provides support to resolve additional + /// information for a code action. + /// + /// @since 3.16.0 + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The parameters of a {@link WorkspaceSymbolRequest}. +pub const WorkspaceSymbolParams = struct { + /// A query string to filter symbols by. Clients may send an empty + /// string here to request all symbols. + query: []const u8, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// A special workspace symbol that supports locations without a range. +/// +/// See also SymbolInformation. +/// +/// @since 3.17.0 +pub const WorkspaceSymbol = struct { + /// The location of the symbol. Whether a server is allowed to + /// return a location without a range depends on the client + /// capability `workspace.symbol.resolveSupport`. + /// + /// See SymbolInformation#location for more details. + location: union(enum) { + Location: Location, + LocationUriOnly: LocationUriOnly, + pub usingnamespace UnionParser(@This()); + }, + /// A data entry field that is preserved on a workspace symbol between a + /// workspace symbol request and a workspace symbol resolve request. + data: ?LSPAny = null, + + // Extends BaseSymbolInformation + /// The name of this symbol. + name: []const u8, + /// The kind of this symbol. + kind: SymbolKind, + /// Tags for this symbol. + /// + /// @since 3.16.0 + tags: ?[]const SymbolTag = null, + /// The name of the symbol containing this symbol. This information is for + /// user interface purposes (e.g. to render a qualifier in the user interface + /// if necessary). It can't be used to re-infer a hierarchy for the document + /// symbols. + containerName: ?[]const u8 = null, +}; + +/// Registration options for a {@link WorkspaceSymbolRequest}. +pub const WorkspaceSymbolRegistrationOptions = struct { + + // Extends WorkspaceSymbolOptions + /// The server provides support to resolve additional + /// information for a workspace symbol. + /// + /// @since 3.17.0 + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The parameters of a {@link CodeLensRequest}. +pub const CodeLensParams = struct { + /// The document to request code lens for. + textDocument: TextDocumentIdentifier, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// A code lens represents a {@link Command command} that should be shown along with +/// source text, like the number of references, a way to run tests, etc. +/// +/// A code lens is _unresolved_ when no command is associated to it. For performance +/// reasons the creation of a code lens and resolving should be done in two stages. +pub const CodeLens = struct { + /// The range in which this code lens is valid. Should only span a single line. + range: Range, + /// The command this code lens represents. + command: ?Command = null, + /// A data entry field that is preserved on a code lens item between + /// a {@link CodeLensRequest} and a {@link CodeLensResolveRequest} + data: ?LSPAny = null, +}; + +/// Registration options for a {@link CodeLensRequest}. +pub const CodeLensRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends CodeLensOptions + /// Code lens has a resolve provider as well. + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The parameters of a {@link DocumentLinkRequest}. +pub const DocumentLinkParams = struct { + /// The document to provide document links for. + textDocument: TextDocumentIdentifier, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, + + // Uses mixin PartialResultParams + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// A document link is a range in a text document that links to an internal or external resource, like another +/// text document or a web site. +pub const DocumentLink = struct { + /// The range this link applies to. + range: Range, + /// The uri this link points to. If missing a resolve request is sent later. + target: ?URI = null, + /// The tooltip text when you hover over this link. + /// + /// If a tooltip is provided, is will be displayed in a string that includes instructions on how to + /// trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS, + /// user settings, and localization. + /// + /// @since 3.15.0 + tooltip: ?[]const u8 = null, + /// A data entry field that is preserved on a document link between a + /// DocumentLinkRequest and a DocumentLinkResolveRequest. + data: ?LSPAny = null, +}; + +/// Registration options for a {@link DocumentLinkRequest}. +pub const DocumentLinkRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends DocumentLinkOptions + /// Document links have a resolve provider as well. + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The parameters of a {@link DocumentFormattingRequest}. +pub const DocumentFormattingParams = struct { + /// The document to format. + textDocument: TextDocumentIdentifier, + /// The format options. + options: FormattingOptions, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// Registration options for a {@link DocumentFormattingRequest}. +pub const DocumentFormattingRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends DocumentFormattingOptions + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The parameters of a {@link DocumentRangeFormattingRequest}. +pub const DocumentRangeFormattingParams = struct { + /// The document to format. + textDocument: TextDocumentIdentifier, + /// The range to format + range: Range, + /// The format options + options: FormattingOptions, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// Registration options for a {@link DocumentRangeFormattingRequest}. +pub const DocumentRangeFormattingRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends DocumentRangeFormattingOptions + /// Whether the server supports formatting multiple ranges at once. + /// + /// @since 3.18.0 + /// @proposed + rangesSupport: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The parameters of a {@link DocumentRangesFormattingRequest}. +/// +/// @since 3.18.0 +/// @proposed +pub const DocumentRangesFormattingParams = struct { + /// The document to format. + textDocument: TextDocumentIdentifier, + /// The ranges to format + ranges: []const Range, + /// The format options + options: FormattingOptions, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// The parameters of a {@link DocumentOnTypeFormattingRequest}. +pub const DocumentOnTypeFormattingParams = struct { + /// The document to format. + textDocument: TextDocumentIdentifier, + /// The position around which the on type formatting should happen. + /// This is not necessarily the exact position where the character denoted + /// by the property `ch` got typed. + position: Position, + /// The character that has been typed that triggered the formatting + /// on type request. That is not necessarily the last character that + /// got inserted into the document since the client could auto insert + /// characters as well (e.g. like automatic brace completion). + ch: []const u8, + /// The formatting options. + options: FormattingOptions, +}; + +/// Registration options for a {@link DocumentOnTypeFormattingRequest}. +pub const DocumentOnTypeFormattingRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends DocumentOnTypeFormattingOptions + /// A character on which formatting should be triggered, like `{`. + firstTriggerCharacter: []const u8, + /// More trigger characters. + moreTriggerCharacter: ?[]const []const u8 = null, +}; + +/// The parameters of a {@link RenameRequest}. +pub const RenameParams = struct { + /// The document to rename. + textDocument: TextDocumentIdentifier, + /// The position at which this request was sent. + position: Position, + /// The new name of the symbol. If the given name is not valid the + /// request must return a {@link ResponseError} with an + /// appropriate message set. + newName: []const u8, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// Registration options for a {@link RenameRequest}. +pub const RenameRegistrationOptions = struct { + + // Extends TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + + // Extends RenameOptions + /// Renames should be checked and tested before being executed. + /// + /// @since version 3.12.0 + prepareProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +pub const PrepareRenameParams = struct { + + // Extends TextDocumentPositionParams + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// The parameters of a {@link ExecuteCommandRequest}. +pub const ExecuteCommandParams = struct { + /// The identifier of the actual command handler. + command: []const u8, + /// Arguments that the command should be invoked with. + arguments: ?[]const LSPAny = null, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +/// Registration options for a {@link ExecuteCommandRequest}. +pub const ExecuteCommandRegistrationOptions = struct { + + // Extends ExecuteCommandOptions + /// The commands to be executed on the server + commands: []const []const u8, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The parameters passed via an apply workspace edit request. +pub const ApplyWorkspaceEditParams = struct { + /// An optional label of the workspace edit. This label is + /// presented in the user interface for example on an undo + /// stack to undo the workspace edit. + label: ?[]const u8 = null, + /// The edits to apply. + edit: WorkspaceEdit, +}; + +/// The result returned from the apply workspace edit request. +/// +/// @since 3.17 renamed from ApplyWorkspaceEditResponse +pub const ApplyWorkspaceEditResult = struct { + /// Indicates whether the edit was applied or not. + applied: bool, + /// An optional textual description for why the edit was not applied. + /// This may be used by the server for diagnostic logging or to provide + /// a suitable error for a request that triggered the edit. + failureReason: ?[]const u8 = null, + /// Depending on the client's failure handling strategy `failedChange` might + /// contain the index of the change that failed. This property is only available + /// if the client signals a `failureHandlingStrategy` in its client capabilities. + failedChange: ?u32 = null, +}; + +pub const WorkDoneProgressBegin = struct { + kind: []const u8 = "begin", + /// Mandatory title of the progress operation. Used to briefly inform about + /// the kind of operation being performed. + /// + /// Examples: "Indexing" or "Linking dependencies". + title: []const u8, + /// Controls if a cancel button should show to allow the user to cancel the + /// long running operation. Clients that don't support cancellation are allowed + /// to ignore the setting. + cancellable: ?bool = null, + /// Optional, more detailed associated progress message. Contains + /// complementary information to the `title`. + /// + /// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". + /// If unset, the previous progress message (if any) is still valid. + message: ?[]const u8 = null, + /// Optional progress percentage to display (value 100 is considered 100%). + /// If not provided infinite progress is assumed and clients are allowed + /// to ignore the `percentage` value in subsequent in report notifications. + /// + /// The value should be steadily rising. Clients are free to ignore values + /// that are not following this rule. The value range is [0, 100]. + percentage: ?u32 = null, +}; + +pub const WorkDoneProgressReport = struct { + kind: []const u8 = "report", + /// Controls enablement state of a cancel button. + /// + /// Clients that don't support cancellation or don't support controlling the button's + /// enablement state are allowed to ignore the property. + cancellable: ?bool = null, + /// Optional, more detailed associated progress message. Contains + /// complementary information to the `title`. + /// + /// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". + /// If unset, the previous progress message (if any) is still valid. + message: ?[]const u8 = null, + /// Optional progress percentage to display (value 100 is considered 100%). + /// If not provided infinite progress is assumed and clients are allowed + /// to ignore the `percentage` value in subsequent in report notifications. + /// + /// The value should be steadily rising. Clients are free to ignore values + /// that are not following this rule. The value range is [0, 100] + percentage: ?u32 = null, +}; + +pub const WorkDoneProgressEnd = struct { + kind: []const u8 = "end", + /// Optional, a final message indicating to for example indicate the outcome + /// of the operation. + message: ?[]const u8 = null, +}; + +pub const SetTraceParams = struct { + value: TraceValues, +}; + +pub const LogTraceParams = struct { + message: []const u8, + verbose: ?[]const u8 = null, +}; + +pub const CancelParams = struct { + /// The request id to cancel. + id: union(enum) { + integer: i32, + string: []const u8, + pub usingnamespace UnionParser(@This()); + }, +}; + +pub const ProgressParams = struct { + /// The progress token provided by the client or server. + token: ProgressToken, + /// The progress data. + value: LSPAny, +}; + +/// A parameter literal used in requests to pass a text document and a position inside that +/// document. +pub const TextDocumentPositionParams = struct { + /// The text document. + textDocument: TextDocumentIdentifier, + /// The position inside the text document. + position: Position, +}; + +pub const WorkDoneProgressParams = struct { + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +pub const PartialResultParams = struct { + /// An optional token that a server can use to report partial results (e.g. streaming) to + /// the client. + partialResultToken: ?ProgressToken = null, +}; + +/// Represents the connection of two locations. Provides additional metadata over normal {@link Location locations}, +/// including an origin range. +pub const LocationLink = struct { + /// Span of the origin of this link. + /// + /// Used as the underlined span for mouse interaction. Defaults to the word range at + /// the definition position. + originSelectionRange: ?Range = null, + /// The target resource identifier of this link. + targetUri: DocumentUri, + /// The full target range of this link. If the target for example is a symbol then target range is the + /// range enclosing this symbol not including leading/trailing whitespace but everything else + /// like comments. This information is typically used to highlight the range in the editor. + targetRange: Range, + /// The range that should be selected and revealed when this link is being followed, e.g the name of a function. + /// Must be contained by the `targetRange`. See also `DocumentSymbol#range` + targetSelectionRange: Range, +}; + +/// A range in a text document expressed as (zero-based) start and end positions. +/// +/// If you want to specify a range that contains a line including the line ending +/// character(s) then use an end position denoting the start of the next line. +/// For example: +/// ```ts +/// { +/// start: { line: 5, character: 23 } +/// end : { line 6, character : 0 } +/// } +/// ``` +pub const Range = struct { + /// The range's start position. + start: Position, + /// The range's end position. + end: Position, +}; + +pub const ImplementationOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Static registration options to be returned in the initialize +/// request. +pub const StaticRegistrationOptions = struct { + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +pub const TypeDefinitionOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// The workspace folder change event. +pub const WorkspaceFoldersChangeEvent = struct { + /// The array of added workspace folders + added: []const WorkspaceFolder, + /// The array of the removed workspace folders + removed: []const WorkspaceFolder, +}; + +pub const ConfigurationItem = struct { + /// The scope to get the configuration section for. + scopeUri: ?URI = null, + /// The configuration section asked for. + section: ?[]const u8 = null, +}; + +/// A literal to identify a text document in the client. +pub const TextDocumentIdentifier = struct { + /// The text document's uri. + uri: DocumentUri, +}; + +/// Represents a color in RGBA space. +pub const Color = struct { + /// The red component of this color in the range [0-1]. + red: f32, + /// The green component of this color in the range [0-1]. + green: f32, + /// The blue component of this color in the range [0-1]. + blue: f32, + /// The alpha component of this color in the range [0-1]. + alpha: f32, +}; + +pub const DocumentColorOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +pub const FoldingRangeOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +pub const DeclarationOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Position in a text document expressed as zero-based line and character +/// offset. Prior to 3.17 the offsets were always based on a UTF-16 string +/// representation. So a string of the form `a𐐀b` the character offset of the +/// character `a` is 0, the character offset of `𐐀` is 1 and the character +/// offset of b is 3 since `𐐀` is represented using two code units in UTF-16. +/// Since 3.17 clients and servers can agree on a different string encoding +/// representation (e.g. UTF-8). The client announces it's supported encoding +/// via the client capability [`general.positionEncodings`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#clientCapabilities). +/// The value is an array of position encodings the client supports, with +/// decreasing preference (e.g. the encoding at index `0` is the most preferred +/// one). To stay backwards compatible the only mandatory encoding is UTF-16 +/// represented via the string `utf-16`. The server can pick one of the +/// encodings offered by the client and signals that encoding back to the +/// client via the initialize result's property +/// [`capabilities.positionEncoding`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#serverCapabilities). If the string value +/// `utf-16` is missing from the client's capability `general.positionEncodings` +/// servers can safely assume that the client supports UTF-16. If the server +/// omits the position encoding in its initialize result the encoding defaults +/// to the string value `utf-16`. Implementation considerations: since the +/// conversion from one encoding into another requires the content of the +/// file / line the conversion is best done where the file is read which is +/// usually on the server side. +/// +/// Positions are line end character agnostic. So you can not specify a position +/// that denotes `\r|\n` or `\n|` where `|` represents the character offset. +/// +/// @since 3.17.0 - support for negotiated position encoding. +pub const Position = struct { + /// Line position in a document (zero-based). + /// + /// If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document. + /// If a line number is negative, it defaults to 0. + line: u32, + /// Character offset on a line in a document (zero-based). + /// + /// The meaning of this offset is determined by the negotiated + /// `PositionEncodingKind`. + /// + /// If the character value is greater than the line length it defaults back to the + /// line length. + character: u32, +}; + +pub const SelectionRangeOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Call hierarchy options used during static registration. +/// +/// @since 3.16.0 +pub const CallHierarchyOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// @since 3.16.0 +pub const SemanticTokensOptions = struct { + /// The legend used by the server + legend: SemanticTokensLegend, + /// Server supports providing semantic tokens for a specific range + /// of a document. + range: ?union(enum) { + bool: bool, + literal_1: struct {}, + pub usingnamespace UnionParser(@This()); + } = null, + /// Server supports providing semantic tokens for a full document. + full: ?union(enum) { + bool: bool, + SemanticTokensFullDelta: SemanticTokensFullDelta, + pub usingnamespace UnionParser(@This()); + } = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// @since 3.16.0 +pub const SemanticTokensEdit = struct { + /// The start offset of the edit. + start: u32, + /// The count of elements to remove. + deleteCount: u32, + /// The elements to insert. + data: ?[]const u32 = null, +}; + +pub const LinkedEditingRangeOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Represents information on a file/folder create. +/// +/// @since 3.16.0 +pub const FileCreate = struct { + /// A file:// URI for the location of the file/folder being created. + uri: []const u8, +}; + +/// Describes textual changes on a text document. A TextDocumentEdit describes all changes +/// on a document version Si and after they are applied move the document to version Si+1. +/// So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any +/// kind of ordering. However the edits must be non overlapping. +pub const TextDocumentEdit = struct { + /// The text document to change. + textDocument: OptionalVersionedTextDocumentIdentifier, + /// The edits to be applied. + /// + /// @since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a + /// client capability. + edits: []const union(enum) { + TextEdit: TextEdit, + AnnotatedTextEdit: AnnotatedTextEdit, + pub usingnamespace UnionParser(@This()); + }, +}; + +/// Create file operation. +pub const CreateFile = struct { + /// A create + kind: []const u8 = "create", + /// The resource to create. + uri: DocumentUri, + /// Additional options + options: ?CreateFileOptions = null, + + // Extends ResourceOperation + /// An optional annotation identifier describing the operation. + /// + /// @since 3.16.0 + annotationId: ?ChangeAnnotationIdentifier = null, +}; + +/// Rename file operation +pub const RenameFile = struct { + /// A rename + kind: []const u8 = "rename", + /// The old (existing) location. + oldUri: DocumentUri, + /// The new location. + newUri: DocumentUri, + /// Rename options. + options: ?RenameFileOptions = null, + + // Extends ResourceOperation + /// An optional annotation identifier describing the operation. + /// + /// @since 3.16.0 + annotationId: ?ChangeAnnotationIdentifier = null, +}; + +/// Delete file operation +pub const DeleteFile = struct { + /// A delete + kind: []const u8 = "delete", + /// The file to delete. + uri: DocumentUri, + /// Delete options. + options: ?DeleteFileOptions = null, + + // Extends ResourceOperation + /// An optional annotation identifier describing the operation. + /// + /// @since 3.16.0 + annotationId: ?ChangeAnnotationIdentifier = null, +}; + +/// Additional information that describes document changes. +/// +/// @since 3.16.0 +pub const ChangeAnnotation = struct { + /// A human-readable string describing the actual change. The string + /// is rendered prominent in the user interface. + label: []const u8, + /// A flag which indicates that user confirmation is needed + /// before applying the change. + needsConfirmation: ?bool = null, + /// A human-readable string which is rendered less prominent in + /// the user interface. + description: ?[]const u8 = null, +}; + +/// A filter to describe in which file operation requests or notifications +/// the server is interested in receiving. +/// +/// @since 3.16.0 +pub const FileOperationFilter = struct { + /// A Uri scheme like `file` or `untitled`. + scheme: ?[]const u8 = null, + /// The actual file operation pattern. + pattern: FileOperationPattern, +}; + +/// Represents information on a file/folder rename. +/// +/// @since 3.16.0 +pub const FileRename = struct { + /// A file:// URI for the original location of the file/folder being renamed. + oldUri: []const u8, + /// A file:// URI for the new location of the file/folder being renamed. + newUri: []const u8, +}; + +/// Represents information on a file/folder delete. +/// +/// @since 3.16.0 +pub const FileDelete = struct { + /// A file:// URI for the location of the file/folder being deleted. + uri: []const u8, +}; + +pub const MonikerOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Type hierarchy options used during static registration. +/// +/// @since 3.17.0 +pub const TypeHierarchyOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// @since 3.17.0 +pub const InlineValueContext = struct { + /// The stack frame (as a DAP Id) where the execution has stopped. + frameId: i32, + /// The document range where execution has stopped. + /// Typically the end position of the range denotes the line where the inline values are shown. + stoppedLocation: Range, +}; + +/// Provide inline value as text. +/// +/// @since 3.17.0 +pub const InlineValueText = struct { + /// The document range for which the inline value applies. + range: Range, + /// The text of the inline value. + text: []const u8, +}; + +/// Provide inline value through a variable lookup. +/// If only a range is specified, the variable name will be extracted from the underlying document. +/// An optional variable name can be used to override the extracted name. +/// +/// @since 3.17.0 +pub const InlineValueVariableLookup = struct { + /// The document range for which the inline value applies. + /// The range is used to extract the variable name from the underlying document. + range: Range, + /// If specified the name of the variable to look up. + variableName: ?[]const u8 = null, + /// How to perform the lookup. + caseSensitiveLookup: bool, +}; + +/// Provide an inline value through an expression evaluation. +/// If only a range is specified, the expression will be extracted from the underlying document. +/// An optional expression can be used to override the extracted expression. +/// +/// @since 3.17.0 +pub const InlineValueEvaluatableExpression = struct { + /// The document range for which the inline value applies. + /// The range is used to extract the evaluatable expression from the underlying document. + range: Range, + /// If specified the expression overrides the extracted expression. + expression: ?[]const u8 = null, +}; + +/// Inline value options used during static registration. +/// +/// @since 3.17.0 +pub const InlineValueOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// An inlay hint label part allows for interactive and composite labels +/// of inlay hints. +/// +/// @since 3.17.0 +pub const InlayHintLabelPart = struct { + /// The value of this label part. + value: []const u8, + /// The tooltip text when you hover over this label part. Depending on + /// the client capability `inlayHint.resolveSupport` clients might resolve + /// this property late using the resolve request. + tooltip: ?union(enum) { + string: []const u8, + MarkupContent: MarkupContent, + pub usingnamespace UnionParser(@This()); + } = null, + /// An optional source code location that represents this + /// label part. + /// + /// The editor will use this location for the hover and for code navigation + /// features: This part will become a clickable link that resolves to the + /// definition of the symbol at the given location (not necessarily the + /// location itself), it shows the hover that shows at the given location, + /// and it shows a context menu with further code navigation commands. + /// + /// Depending on the client capability `inlayHint.resolveSupport` clients + /// might resolve this property late using the resolve request. + location: ?Location = null, + /// An optional command for this label part. + /// + /// Depending on the client capability `inlayHint.resolveSupport` clients + /// might resolve this property late using the resolve request. + command: ?Command = null, +}; + +/// A `MarkupContent` literal represents a string value which content is interpreted base on its +/// kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. +/// +/// If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. +/// See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting +/// +/// Here is an example how such a string can be constructed using JavaScript / TypeScript: +/// ```ts +/// let markdown: MarkdownContent = { +/// kind: MarkupKind.Markdown, +/// value: [ +/// '# Header', +/// 'Some text', +/// '```typescript', +/// 'someCode();', +/// '```' +/// ].join('\n') +/// }; +/// ``` +/// +/// *Please Note* that clients might sanitize the return markdown. A client could decide to +/// remove HTML from the markdown to avoid script execution. +pub const MarkupContent = struct { + /// The type of the Markup + kind: MarkupKind, + /// The content itself + value: []const u8, +}; + +/// Inlay hint options used during static registration. +/// +/// @since 3.17.0 +pub const InlayHintOptions = struct { + /// The server provides support to resolve additional + /// information for an inlay hint item. + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// A full diagnostic report with a set of related documents. +/// +/// @since 3.17.0 +pub const RelatedFullDocumentDiagnosticReport = struct { + /// Diagnostics of related documents. This information is useful + /// in programming languages where code in a file A can generate + /// diagnostics in a file B which A depends on. An example of + /// such a language is C/C++ where marco definitions in a file + /// a.cpp and result in errors in a header file b.hpp. + /// + /// @since 3.17.0 + relatedDocuments: ?Map(DocumentUri, union(enum) { + FullDocumentDiagnosticReport: FullDocumentDiagnosticReport, + UnchangedDocumentDiagnosticReport: UnchangedDocumentDiagnosticReport, + pub usingnamespace UnionParser(@This()); + }) = null, + + // Extends FullDocumentDiagnosticReport + /// A full document diagnostic report. + kind: []const u8 = "full", + /// An optional result id. If provided it will + /// be sent on the next diagnostic request for the + /// same document. + resultId: ?[]const u8 = null, + /// The actual items. + items: []const Diagnostic, +}; + +/// An unchanged diagnostic report with a set of related documents. +/// +/// @since 3.17.0 +pub const RelatedUnchangedDocumentDiagnosticReport = struct { + /// Diagnostics of related documents. This information is useful + /// in programming languages where code in a file A can generate + /// diagnostics in a file B which A depends on. An example of + /// such a language is C/C++ where marco definitions in a file + /// a.cpp and result in errors in a header file b.hpp. + /// + /// @since 3.17.0 + relatedDocuments: ?Map(DocumentUri, union(enum) { + FullDocumentDiagnosticReport: FullDocumentDiagnosticReport, + UnchangedDocumentDiagnosticReport: UnchangedDocumentDiagnosticReport, + pub usingnamespace UnionParser(@This()); + }) = null, + + // Extends UnchangedDocumentDiagnosticReport + /// A document diagnostic report indicating + /// no changes to the last result. A server can + /// only return `unchanged` if result ids are + /// provided. + kind: []const u8 = "unchanged", + /// A result id which will be sent on the next + /// diagnostic request for the same document. + resultId: []const u8, +}; + +/// A diagnostic report with a full set of problems. +/// +/// @since 3.17.0 +pub const FullDocumentDiagnosticReport = struct { + /// A full document diagnostic report. + kind: []const u8 = "full", + /// An optional result id. If provided it will + /// be sent on the next diagnostic request for the + /// same document. + resultId: ?[]const u8 = null, + /// The actual items. + items: []const Diagnostic, +}; + +/// A diagnostic report indicating that the last returned +/// report is still accurate. +/// +/// @since 3.17.0 +pub const UnchangedDocumentDiagnosticReport = struct { + /// A document diagnostic report indicating + /// no changes to the last result. A server can + /// only return `unchanged` if result ids are + /// provided. + kind: []const u8 = "unchanged", + /// A result id which will be sent on the next + /// diagnostic request for the same document. + resultId: []const u8, +}; + +/// Diagnostic options. +/// +/// @since 3.17.0 +pub const DiagnosticOptions = struct { + /// An optional identifier under which the diagnostics are + /// managed by the client. + identifier: ?[]const u8 = null, + /// Whether the language has inter file dependencies meaning that + /// editing code in one file can result in a different diagnostic + /// set in another file. Inter file dependencies are common for + /// most programming languages and typically uncommon for linters. + interFileDependencies: bool, + /// The server provides support for workspace diagnostics as well. + workspaceDiagnostics: bool, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// A previous result id in a workspace pull request. +/// +/// @since 3.17.0 +pub const PreviousResultId = struct { + /// The URI for which the client knowns a + /// result id. + uri: DocumentUri, + /// The value of the previous result id. + value: []const u8, +}; + +/// A notebook document. +/// +/// @since 3.17.0 +pub const NotebookDocument = struct { + /// The notebook document's uri. + uri: URI, + /// The type of the notebook. + notebookType: []const u8, + /// The version number of this document (it will increase after each + /// change, including undo/redo). + version: i32, + /// Additional metadata stored with the notebook + /// document. + /// + /// Note: should always be an object literal (e.g. LSPObject) + metadata: ?LSPObject = null, + /// The cells of a notebook. + cells: []const NotebookCell, +}; + +/// An item to transfer a text document from the client to the +/// server. +pub const TextDocumentItem = struct { + /// The text document's uri. + uri: DocumentUri, + /// The text document's language identifier. + languageId: []const u8, + /// The version number of this document (it will increase after each + /// change, including undo/redo). + version: i32, + /// The content of the opened text document. + text: []const u8, +}; + +/// A versioned notebook document identifier. +/// +/// @since 3.17.0 +pub const VersionedNotebookDocumentIdentifier = struct { + /// The version number of this notebook document. + version: i32, + /// The notebook document's uri. + uri: URI, +}; + +/// A change event for a notebook document. +/// +/// @since 3.17.0 +pub const NotebookDocumentChangeEvent = struct { + /// The changed meta data if any. + /// + /// Note: should always be an object literal (e.g. LSPObject) + metadata: ?LSPObject = null, + /// Changes to cells + cells: ?NotebookDocumentCellChanges = null, +}; + +/// A literal to identify a notebook document in the client. +/// +/// @since 3.17.0 +pub const NotebookDocumentIdentifier = struct { + /// The notebook document's uri. + uri: URI, +}; + +/// Provides information about the context in which an inline completion was requested. +/// +/// @since 3.18.0 +/// @proposed +pub const InlineCompletionContext = struct { + /// Describes how the inline completion was triggered. + triggerKind: InlineCompletionTriggerKind, + /// Provides information about the currently selected item in the autocomplete widget if it is visible. + selectedCompletionInfo: ?SelectedCompletionInfo = null, +}; + +/// A string value used as a snippet is a template which allows to insert text +/// and to control the editor cursor when insertion happens. +/// +/// A snippet can define tab stops and placeholders with `$1`, `$2` +/// and `${3:foo}`. `$0` defines the final tab stop, it defaults to +/// the end of the snippet. Variables are defined with `$name` and +/// `${name:default value}`. +/// +/// @since 3.18.0 +/// @proposed +pub const StringValue = struct { + /// The kind of string value. + kind: []const u8 = "snippet", + /// The snippet string. + value: []const u8, +}; + +/// Inline completion options used during static registration. +/// +/// @since 3.18.0 +/// @proposed +pub const InlineCompletionOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// General parameters to register for a notification or to register a provider. +pub const Registration = struct { + /// The id used to register the request. The id can be used to deregister + /// the request again. + id: []const u8, + /// The method / capability to register for. + method: []const u8, + /// Options necessary for the registration. + registerOptions: ?LSPAny = null, +}; + +/// General parameters to unregister a request or notification. +pub const Unregistration = struct { + /// The id used to unregister the request or notification. Usually an id + /// provided during the register request. + id: []const u8, + /// The method to unregister for. + method: []const u8, +}; + +/// The initialize parameters +pub const _InitializeParams = struct { + /// The process Id of the parent process that started + /// the server. + /// + /// Is `null` if the process has not been started by another process. + /// If the parent process is not alive then the server should exit. + processId: ?i32 = null, + /// Information about the client + /// + /// @since 3.15.0 + clientInfo: ?ClientInfo = null, + /// The locale the client is currently showing the user interface + /// in. This must not necessarily be the locale of the operating + /// system. + /// + /// Uses IETF language tags as the value's syntax + /// (See https://en.wikipedia.org/wiki/IETF_language_tag) + /// + /// @since 3.16.0 + locale: ?[]const u8 = null, + /// The rootPath of the workspace. Is null + /// if no folder is open. + /// + /// @deprecated in favour of rootUri. + rootPath: ?[]const u8 = null, + /// The rootUri of the workspace. Is null if no + /// folder is open. If both `rootPath` and `rootUri` are set + /// `rootUri` wins. + /// + /// @deprecated in favour of workspaceFolders. + rootUri: ?DocumentUri = null, + /// The capabilities provided by the client (editor or tool) + capabilities: ClientCapabilities, + /// User provided initialization options. + initializationOptions: ?LSPAny = null, + /// The initial trace setting. If omitted trace is disabled ('off'). + trace: ?TraceValues = null, + + // Uses mixin WorkDoneProgressParams + /// An optional token that a server can use to report work done progress. + workDoneToken: ?ProgressToken = null, +}; + +pub const WorkspaceFoldersInitializeParams = struct { + /// The workspace folders configured in the client when the server starts. + /// + /// This property is only available if the client supports workspace folders. + /// It can be `null` if the client supports workspace folders but none are + /// configured. + /// + /// @since 3.6.0 + workspaceFolders: ?[]const WorkspaceFolder = null, +}; + +/// Defines the capabilities provided by a language +/// server. +pub const ServerCapabilities = struct { + /// The position encoding the server picked from the encodings offered + /// by the client via the client capability `general.positionEncodings`. + /// + /// If the client didn't provide any position encodings the only valid + /// value that a server can return is 'utf-16'. + /// + /// If omitted it defaults to 'utf-16'. + /// + /// @since 3.17.0 + positionEncoding: ?PositionEncodingKind = null, + /// Defines how text documents are synced. Is either a detailed structure + /// defining each notification or for backwards compatibility the + /// TextDocumentSyncKind number. + textDocumentSync: ?union(enum) { + TextDocumentSyncOptions: TextDocumentSyncOptions, + TextDocumentSyncKind: TextDocumentSyncKind, + pub usingnamespace UnionParser(@This()); + } = null, + /// Defines how notebook documents are synced. + /// + /// @since 3.17.0 + notebookDocumentSync: ?union(enum) { + NotebookDocumentSyncOptions: NotebookDocumentSyncOptions, + NotebookDocumentSyncRegistrationOptions: NotebookDocumentSyncRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides completion support. + completionProvider: ?CompletionOptions = null, + /// The server provides hover support. + hoverProvider: ?union(enum) { + bool: bool, + HoverOptions: HoverOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides signature help support. + signatureHelpProvider: ?SignatureHelpOptions = null, + /// The server provides Goto Declaration support. + declarationProvider: ?union(enum) { + bool: bool, + DeclarationOptions: DeclarationOptions, + DeclarationRegistrationOptions: DeclarationRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides goto definition support. + definitionProvider: ?union(enum) { + bool: bool, + DefinitionOptions: DefinitionOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides Goto Type Definition support. + typeDefinitionProvider: ?union(enum) { + bool: bool, + TypeDefinitionOptions: TypeDefinitionOptions, + TypeDefinitionRegistrationOptions: TypeDefinitionRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides Goto Implementation support. + implementationProvider: ?union(enum) { + bool: bool, + ImplementationOptions: ImplementationOptions, + ImplementationRegistrationOptions: ImplementationRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides find references support. + referencesProvider: ?union(enum) { + bool: bool, + ReferenceOptions: ReferenceOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides document highlight support. + documentHighlightProvider: ?union(enum) { + bool: bool, + DocumentHighlightOptions: DocumentHighlightOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides document symbol support. + documentSymbolProvider: ?union(enum) { + bool: bool, + DocumentSymbolOptions: DocumentSymbolOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides code actions. CodeActionOptions may only be + /// specified if the client states that it supports + /// `codeActionLiteralSupport` in its initial `initialize` request. + codeActionProvider: ?union(enum) { + bool: bool, + CodeActionOptions: CodeActionOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides code lens. + codeLensProvider: ?CodeLensOptions = null, + /// The server provides document link support. + documentLinkProvider: ?DocumentLinkOptions = null, + /// The server provides color provider support. + colorProvider: ?union(enum) { + bool: bool, + DocumentColorOptions: DocumentColorOptions, + DocumentColorRegistrationOptions: DocumentColorRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides workspace symbol support. + workspaceSymbolProvider: ?union(enum) { + bool: bool, + WorkspaceSymbolOptions: WorkspaceSymbolOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides document formatting. + documentFormattingProvider: ?union(enum) { + bool: bool, + DocumentFormattingOptions: DocumentFormattingOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides document range formatting. + documentRangeFormattingProvider: ?union(enum) { + bool: bool, + DocumentRangeFormattingOptions: DocumentRangeFormattingOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides document formatting on typing. + documentOnTypeFormattingProvider: ?DocumentOnTypeFormattingOptions = null, + /// The server provides rename support. RenameOptions may only be + /// specified if the client states that it supports + /// `prepareSupport` in its initial `initialize` request. + renameProvider: ?union(enum) { + bool: bool, + RenameOptions: RenameOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides folding provider support. + foldingRangeProvider: ?union(enum) { + bool: bool, + FoldingRangeOptions: FoldingRangeOptions, + FoldingRangeRegistrationOptions: FoldingRangeRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides selection range support. + selectionRangeProvider: ?union(enum) { + bool: bool, + SelectionRangeOptions: SelectionRangeOptions, + SelectionRangeRegistrationOptions: SelectionRangeRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides execute command support. + executeCommandProvider: ?ExecuteCommandOptions = null, + /// The server provides call hierarchy support. + /// + /// @since 3.16.0 + callHierarchyProvider: ?union(enum) { + bool: bool, + CallHierarchyOptions: CallHierarchyOptions, + CallHierarchyRegistrationOptions: CallHierarchyRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides linked editing range support. + /// + /// @since 3.16.0 + linkedEditingRangeProvider: ?union(enum) { + bool: bool, + LinkedEditingRangeOptions: LinkedEditingRangeOptions, + LinkedEditingRangeRegistrationOptions: LinkedEditingRangeRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides semantic tokens support. + /// + /// @since 3.16.0 + semanticTokensProvider: ?union(enum) { + SemanticTokensOptions: SemanticTokensOptions, + SemanticTokensRegistrationOptions: SemanticTokensRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides moniker support. + /// + /// @since 3.16.0 + monikerProvider: ?union(enum) { + bool: bool, + MonikerOptions: MonikerOptions, + MonikerRegistrationOptions: MonikerRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides type hierarchy support. + /// + /// @since 3.17.0 + typeHierarchyProvider: ?union(enum) { + bool: bool, + TypeHierarchyOptions: TypeHierarchyOptions, + TypeHierarchyRegistrationOptions: TypeHierarchyRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides inline values. + /// + /// @since 3.17.0 + inlineValueProvider: ?union(enum) { + bool: bool, + InlineValueOptions: InlineValueOptions, + InlineValueRegistrationOptions: InlineValueRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server provides inlay hints. + /// + /// @since 3.17.0 + inlayHintProvider: ?union(enum) { + bool: bool, + InlayHintOptions: InlayHintOptions, + InlayHintRegistrationOptions: InlayHintRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// The server has support for pull model diagnostics. + /// + /// @since 3.17.0 + diagnosticProvider: ?union(enum) { + DiagnosticOptions: DiagnosticOptions, + DiagnosticRegistrationOptions: DiagnosticRegistrationOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// Inline completion options used during static registration. + /// + /// @since 3.18.0 + /// @proposed + inlineCompletionProvider: ?union(enum) { + bool: bool, + InlineCompletionOptions: InlineCompletionOptions, + pub usingnamespace UnionParser(@This()); + } = null, + /// Workspace specific server capabilities. + workspace: ?WorkspaceOptions = null, + /// Experimental server capabilities. + experimental: ?LSPAny = null, +}; + +/// Information about the server +/// +/// @since 3.15.0 +/// @since 3.18.0 ServerInfo type name added. +/// @proposed +pub const ServerInfo = struct { + /// The name of the server as defined by the server. + name: []const u8, + /// The server's version as defined by the server. + version: ?[]const u8 = null, +}; + +/// A text document identifier to denote a specific version of a text document. +pub const VersionedTextDocumentIdentifier = struct { + /// The version number of this document. + version: i32, + + // Extends TextDocumentIdentifier + /// The text document's uri. + uri: DocumentUri, +}; + +/// Save options. +pub const SaveOptions = struct { + /// The client is supposed to include the content on save. + includeText: ?bool = null, +}; + +/// An event describing a file change. +pub const FileEvent = struct { + /// The file's uri. + uri: DocumentUri, + /// The change type. + type: FileChangeType, +}; + +pub const FileSystemWatcher = struct { + /// The glob pattern to watch. See {@link GlobPattern glob pattern} for more detail. + /// + /// @since 3.17.0 support for relative patterns. + globPattern: GlobPattern, + /// The kind of events of interest. If omitted it defaults + /// to WatchKind.Create | WatchKind.Change | WatchKind.Delete + /// which is 7. + kind: ?WatchKind = null, +}; + +/// Represents a diagnostic, such as a compiler error or warning. Diagnostic objects +/// are only valid in the scope of a resource. +pub const Diagnostic = struct { + /// The range at which the message applies + range: Range, + /// The diagnostic's severity. Can be omitted. If omitted it is up to the + /// client to interpret diagnostics as error, warning, info or hint. + severity: ?DiagnosticSeverity = null, + /// The diagnostic's code, which usually appear in the user interface. + code: ?union(enum) { + integer: i32, + string: []const u8, + pub usingnamespace UnionParser(@This()); + } = null, + /// An optional property to describe the error code. + /// Requires the code field (above) to be present/not null. + /// + /// @since 3.16.0 + codeDescription: ?CodeDescription = null, + /// A human-readable string describing the source of this + /// diagnostic, e.g. 'typescript' or 'super lint'. It usually + /// appears in the user interface. + source: ?[]const u8 = null, + /// The diagnostic's message. It usually appears in the user interface + message: []const u8, + /// Additional metadata about the diagnostic. + /// + /// @since 3.15.0 + tags: ?[]const DiagnosticTag = null, + /// An array of related diagnostic information, e.g. when symbol-names within + /// a scope collide all definitions can be marked via this property. + relatedInformation: ?[]const DiagnosticRelatedInformation = null, + /// A data entry field that is preserved between a `textDocument/publishDiagnostics` + /// notification and `textDocument/codeAction` request. + /// + /// @since 3.16.0 + data: ?LSPAny = null, +}; + +/// Contains additional information about the context in which a completion request is triggered. +pub const CompletionContext = struct { + /// How the completion was triggered. + triggerKind: CompletionTriggerKind, + /// The trigger character (a single character) that has trigger code complete. + /// Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` + triggerCharacter: ?[]const u8 = null, +}; + +/// Additional details for a completion item label. +/// +/// @since 3.17.0 +pub const CompletionItemLabelDetails = struct { + /// An optional string which is rendered less prominently directly after {@link CompletionItem.label label}, + /// without any spacing. Should be used for function signatures and type annotations. + detail: ?[]const u8 = null, + /// An optional string which is rendered less prominently after {@link CompletionItem.detail}. Should be used + /// for fully qualified names and file paths. + description: ?[]const u8 = null, +}; + +/// A special text edit to provide an insert and a replace operation. +/// +/// @since 3.16.0 +pub const InsertReplaceEdit = struct { + /// The string to be inserted. + newText: []const u8, + /// The range if the insert is requested + insert: Range, + /// The range if the replace is requested. + replace: Range, +}; + +/// In many cases the items of an actual completion result share the same +/// value for properties like `commitCharacters` or the range of a text +/// edit. A completion list can therefore define item defaults which will +/// be used if a completion item itself doesn't specify the value. +/// +/// If a completion list specifies a default value and a completion item +/// also specifies a corresponding value the one from the item is used. +/// +/// Servers are only allowed to return default values if the client +/// signals support for this via the `completionList.itemDefaults` +/// capability. +/// +/// @since 3.17.0 +pub const CompletionItemDefaults = struct { + /// A default commit character set. + /// + /// @since 3.17.0 + commitCharacters: ?[]const []const u8 = null, + /// A default edit range. + /// + /// @since 3.17.0 + editRange: ?union(enum) { + Range: Range, + EditRangeWithInsertReplace: EditRangeWithInsertReplace, + pub usingnamespace UnionParser(@This()); + } = null, + /// A default insert text format. + /// + /// @since 3.17.0 + insertTextFormat: ?InsertTextFormat = null, + /// A default insert text mode. + /// + /// @since 3.17.0 + insertTextMode: ?InsertTextMode = null, + /// A default data value. + /// + /// @since 3.17.0 + data: ?LSPAny = null, +}; + +/// Completion options. +pub const CompletionOptions = struct { + /// Most tools trigger completion request automatically without explicitly requesting + /// it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user + /// starts to type an identifier. For example if the user types `c` in a JavaScript file + /// code complete will automatically pop up present `console` besides others as a + /// completion item. Characters that make up identifiers don't need to be listed here. + /// + /// If code complete should automatically be trigger on characters not being valid inside + /// an identifier (for example `.` in JavaScript) list them in `triggerCharacters`. + triggerCharacters: ?[]const []const u8 = null, + /// The list of all possible characters that commit a completion. This field can be used + /// if clients don't support individual commit characters per completion item. See + /// `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport` + /// + /// If a server provides both `allCommitCharacters` and commit characters on an individual + /// completion item the ones on the completion item win. + /// + /// @since 3.2.0 + allCommitCharacters: ?[]const []const u8 = null, + /// The server provides support to resolve additional + /// information for a completion item. + resolveProvider: ?bool = null, + /// The server supports the following `CompletionItem` specific + /// capabilities. + /// + /// @since 3.17.0 + completionItem: ?ServerCompletionItemOptions = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Hover options. +pub const HoverOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Additional information about the context in which a signature help request was triggered. +/// +/// @since 3.15.0 +pub const SignatureHelpContext = struct { + /// Action that caused signature help to be triggered. + triggerKind: SignatureHelpTriggerKind, + /// Character that caused signature help to be triggered. + /// + /// This is undefined when `triggerKind !== SignatureHelpTriggerKind.TriggerCharacter` + triggerCharacter: ?[]const u8 = null, + /// `true` if signature help was already showing when it was triggered. + /// + /// Retriggers occurs when the signature help is already active and can be caused by actions such as + /// typing a trigger character, a cursor move, or document content changes. + isRetrigger: bool, + /// The currently active `SignatureHelp`. + /// + /// The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field updated based on + /// the user navigating through available signatures. + activeSignatureHelp: ?SignatureHelp = null, +}; + +/// Represents the signature of something callable. A signature +/// can have a label, like a function-name, a doc-comment, and +/// a set of parameters. +pub const SignatureInformation = struct { + /// The label of this signature. Will be shown in + /// the UI. + label: []const u8, + /// The human-readable doc-comment of this signature. Will be shown + /// in the UI but can be omitted. + documentation: ?union(enum) { + string: []const u8, + MarkupContent: MarkupContent, + pub usingnamespace UnionParser(@This()); + } = null, + /// The parameters of this signature. + parameters: ?[]const ParameterInformation = null, + /// The index of the active parameter. + /// + /// If provided, this is used in place of `SignatureHelp.activeParameter`. + /// + /// @since 3.16.0 + activeParameter: ?u32 = null, +}; + +/// Server Capabilities for a {@link SignatureHelpRequest}. +pub const SignatureHelpOptions = struct { + /// List of characters that trigger signature help automatically. + triggerCharacters: ?[]const []const u8 = null, + /// List of characters that re-trigger signature help. + /// + /// These trigger characters are only active when signature help is already showing. All trigger characters + /// are also counted as re-trigger characters. + /// + /// @since 3.15.0 + retriggerCharacters: ?[]const []const u8 = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Server Capabilities for a {@link DefinitionRequest}. +pub const DefinitionOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Value-object that contains additional information when +/// requesting references. +pub const ReferenceContext = struct { + /// Include the declaration of the current symbol. + includeDeclaration: bool, +}; + +/// Reference options. +pub const ReferenceOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Provider options for a {@link DocumentHighlightRequest}. +pub const DocumentHighlightOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// A base for all symbol information. +pub const BaseSymbolInformation = struct { + /// The name of this symbol. + name: []const u8, + /// The kind of this symbol. + kind: SymbolKind, + /// Tags for this symbol. + /// + /// @since 3.16.0 + tags: ?[]const SymbolTag = null, + /// The name of the symbol containing this symbol. This information is for + /// user interface purposes (e.g. to render a qualifier in the user interface + /// if necessary). It can't be used to re-infer a hierarchy for the document + /// symbols. + containerName: ?[]const u8 = null, +}; + +/// Provider options for a {@link DocumentSymbolRequest}. +pub const DocumentSymbolOptions = struct { + /// A human-readable string that is shown when multiple outlines trees + /// are shown for the same document. + /// + /// @since 3.16.0 + label: ?[]const u8 = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Contains additional diagnostic information about the context in which +/// a {@link CodeActionProvider.provideCodeActions code action} is run. +pub const CodeActionContext = struct { + /// An array of diagnostics known on the client side overlapping the range provided to the + /// `textDocument/codeAction` request. They are provided so that the server knows which + /// errors are currently presented to the user for the given range. There is no guarantee + /// that these accurately reflect the error state of the resource. The primary parameter + /// to compute code actions is the provided range. + diagnostics: []const Diagnostic, + /// Requested kind of actions to return. + /// + /// Actions not of this kind are filtered out by the client before being shown. So servers + /// can omit computing them. + only: ?[]const CodeActionKind = null, + /// The reason why code actions were requested. + /// + /// @since 3.17.0 + triggerKind: ?CodeActionTriggerKind = null, +}; + +/// Captures why the code action is currently disabled. +/// +/// @since 3.18.0 +/// @proposed +pub const CodeActionDisabled = struct { + /// Human readable description of why the code action is currently disabled. + /// + /// This is displayed in the code actions UI. + reason: []const u8, +}; + +/// Provider options for a {@link CodeActionRequest}. +pub const CodeActionOptions = struct { + /// CodeActionKinds that this server may return. + /// + /// The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server + /// may list out every specific kind they provide. + codeActionKinds: ?[]const CodeActionKind = null, + /// The server provides support to resolve additional + /// information for a code action. + /// + /// @since 3.16.0 + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Location with only uri and does not include range. +/// +/// @since 3.18.0 +/// @proposed +pub const LocationUriOnly = struct { + uri: DocumentUri, +}; + +/// Server capabilities for a {@link WorkspaceSymbolRequest}. +pub const WorkspaceSymbolOptions = struct { + /// The server provides support to resolve additional + /// information for a workspace symbol. + /// + /// @since 3.17.0 + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Code Lens provider options of a {@link CodeLensRequest}. +pub const CodeLensOptions = struct { + /// Code lens has a resolve provider as well. + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Provider options for a {@link DocumentLinkRequest}. +pub const DocumentLinkOptions = struct { + /// Document links have a resolve provider as well. + resolveProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Value-object describing what options formatting should use. +pub const FormattingOptions = struct { + /// Size of a tab in spaces. + tabSize: u32, + /// Prefer spaces over tabs. + insertSpaces: bool, + /// Trim trailing whitespace on a line. + /// + /// @since 3.15.0 + trimTrailingWhitespace: ?bool = null, + /// Insert a newline character at the end of the file if one does not exist. + /// + /// @since 3.15.0 + insertFinalNewline: ?bool = null, + /// Trim all newlines after the final newline at the end of the file. + /// + /// @since 3.15.0 + trimFinalNewlines: ?bool = null, +}; + +/// Provider options for a {@link DocumentFormattingRequest}. +pub const DocumentFormattingOptions = struct { + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Provider options for a {@link DocumentRangeFormattingRequest}. +pub const DocumentRangeFormattingOptions = struct { + /// Whether the server supports formatting multiple ranges at once. + /// + /// @since 3.18.0 + /// @proposed + rangesSupport: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// Provider options for a {@link DocumentOnTypeFormattingRequest}. +pub const DocumentOnTypeFormattingOptions = struct { + /// A character on which formatting should be triggered, like `{`. + firstTriggerCharacter: []const u8, + /// More trigger characters. + moreTriggerCharacter: ?[]const []const u8 = null, +}; + +/// Provider options for a {@link RenameRequest}. +pub const RenameOptions = struct { + /// Renames should be checked and tested before being executed. + /// + /// @since version 3.12.0 + prepareProvider: ?bool = null, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const PrepareRenamePlaceholder = struct { + range: Range, + placeholder: []const u8, +}; + +/// @since 3.18.0 +/// @proposed +pub const PrepareRenameDefaultBehavior = struct { + defaultBehavior: bool, +}; + +/// The server capabilities of a {@link ExecuteCommandRequest}. +pub const ExecuteCommandOptions = struct { + /// The commands to be executed on the server + commands: []const []const u8, + + // Uses mixin WorkDoneProgressOptions + workDoneProgress: ?bool = null, +}; + +/// @since 3.16.0 +pub const SemanticTokensLegend = struct { + /// The token types a server uses. + tokenTypes: []const []const u8, + /// The token modifiers a server uses. + tokenModifiers: []const []const u8, +}; + +/// Semantic tokens options to support deltas for full documents +/// +/// @since 3.18.0 +/// @proposed +pub const SemanticTokensFullDelta = struct { + /// The server supports deltas for full documents. + delta: ?bool = null, +}; + +/// A text document identifier to optionally denote a specific version of a text document. +pub const OptionalVersionedTextDocumentIdentifier = struct { + /// The version number of this document. If a versioned text document identifier + /// is sent from the server to the client and the file is not open in the editor + /// (the server has not received an open notification before) the server can send + /// `null` to indicate that the version is unknown and the content on disk is the + /// truth (as specified with document content ownership). + version: ?i32 = null, + + // Extends TextDocumentIdentifier + /// The text document's uri. + uri: DocumentUri, +}; + +/// A special text edit with an additional change annotation. +/// +/// @since 3.16.0. +pub const AnnotatedTextEdit = struct { + /// The actual identifier of the change annotation + annotationId: ChangeAnnotationIdentifier, + + // Extends TextEdit + /// The range of the text document to be manipulated. To insert + /// text into a document create a range where start === end. + range: Range, + /// The string to be inserted. For delete operations use an + /// empty string. + newText: []const u8, +}; + +/// A generic resource operation. +pub const ResourceOperation = struct { + /// The resource operation kind. + kind: []const u8, + /// An optional annotation identifier describing the operation. + /// + /// @since 3.16.0 + annotationId: ?ChangeAnnotationIdentifier = null, +}; + +/// Options to create a file. +pub const CreateFileOptions = struct { + /// Overwrite existing file. Overwrite wins over `ignoreIfExists` + overwrite: ?bool = null, + /// Ignore if exists. + ignoreIfExists: ?bool = null, +}; + +/// Rename file options +pub const RenameFileOptions = struct { + /// Overwrite target if existing. Overwrite wins over `ignoreIfExists` + overwrite: ?bool = null, + /// Ignores if target exists. + ignoreIfExists: ?bool = null, +}; + +/// Delete file options +pub const DeleteFileOptions = struct { + /// Delete the content recursively if a folder is denoted. + recursive: ?bool = null, + /// Ignore the operation if the file doesn't exist. + ignoreIfNotExists: ?bool = null, +}; + +/// A pattern to describe in which file operation requests or notifications +/// the server is interested in receiving. +/// +/// @since 3.16.0 +pub const FileOperationPattern = struct { + /// The glob pattern to match. Glob patterns can have the following syntax: + /// - `*` to match one or more characters in a path segment + /// - `?` to match on one character in a path segment + /// - `**` to match any number of path segments, including none + /// - `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) + /// - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) + /// - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) + glob: []const u8, + /// Whether to match files or folders with this pattern. + /// + /// Matches both if undefined. + matches: ?FileOperationPatternKind = null, + /// Additional options used during matching. + options: ?FileOperationPatternOptions = null, +}; + +/// A full document diagnostic report for a workspace diagnostic result. +/// +/// @since 3.17.0 +pub const WorkspaceFullDocumentDiagnosticReport = struct { + /// The URI for which diagnostic information is reported. + uri: DocumentUri, + /// The version number for which the diagnostics are reported. + /// If the document is not marked as open `null` can be provided. + version: ?i32 = null, + + // Extends FullDocumentDiagnosticReport + /// A full document diagnostic report. + kind: []const u8 = "full", + /// An optional result id. If provided it will + /// be sent on the next diagnostic request for the + /// same document. + resultId: ?[]const u8 = null, + /// The actual items. + items: []const Diagnostic, +}; + +/// An unchanged document diagnostic report for a workspace diagnostic result. +/// +/// @since 3.17.0 +pub const WorkspaceUnchangedDocumentDiagnosticReport = struct { + /// The URI for which diagnostic information is reported. + uri: DocumentUri, + /// The version number for which the diagnostics are reported. + /// If the document is not marked as open `null` can be provided. + version: ?i32 = null, + + // Extends UnchangedDocumentDiagnosticReport + /// A document diagnostic report indicating + /// no changes to the last result. A server can + /// only return `unchanged` if result ids are + /// provided. + kind: []const u8 = "unchanged", + /// A result id which will be sent on the next + /// diagnostic request for the same document. + resultId: []const u8, +}; + +/// A notebook cell. +/// +/// A cell's document URI must be unique across ALL notebook +/// cells and can therefore be used to uniquely identify a +/// notebook cell or the cell's text document. +/// +/// @since 3.17.0 +pub const NotebookCell = struct { + /// The cell's kind + kind: NotebookCellKind, + /// The URI of the cell's text document + /// content. + document: DocumentUri, + /// Additional metadata stored with the cell. + /// + /// Note: should always be an object literal (e.g. LSPObject) + metadata: ?LSPObject = null, + /// Additional execution summary information + /// if supported by the client. + executionSummary: ?ExecutionSummary = null, +}; + +/// Cell changes to a notebook document. +/// +/// @since 3.18.0 +/// @proposed +pub const NotebookDocumentCellChanges = struct { + /// Changes to the cell structure to add or + /// remove cells. + structure: ?NotebookDocumentCellChangeStructure = null, + /// Changes to notebook cells properties like its + /// kind, execution summary or metadata. + data: ?[]const NotebookCell = null, + /// Changes to the text content of notebook cells. + textContent: ?[]const NotebookDocumentCellContentChanges = null, +}; + +/// Describes the currently selected completion item. +/// +/// @since 3.18.0 +/// @proposed +pub const SelectedCompletionInfo = struct { + /// The range that will be replaced if this completion item is accepted. + range: Range, + /// The text the range will be replaced with if this completion is accepted. + text: []const u8, +}; + +/// Information about the client +/// +/// @since 3.15.0 +/// @since 3.18.0 ClientInfo type name added. +/// @proposed +pub const ClientInfo = struct { + /// The name of the client as defined by the client. + name: []const u8, + /// The client's version as defined by the client. + version: ?[]const u8 = null, +}; + +/// Defines the capabilities provided by the client. +pub const ClientCapabilities = struct { + /// Workspace specific client capabilities. + workspace: ?WorkspaceClientCapabilities = null, + /// Text document specific client capabilities. + textDocument: ?TextDocumentClientCapabilities = null, + /// Capabilities specific to the notebook document support. + /// + /// @since 3.17.0 + notebookDocument: ?NotebookDocumentClientCapabilities = null, + /// Window specific client capabilities. + window: ?WindowClientCapabilities = null, + /// General client capabilities. + /// + /// @since 3.16.0 + general: ?GeneralClientCapabilities = null, + /// Experimental client capabilities. + experimental: ?LSPAny = null, +}; + +pub const TextDocumentSyncOptions = struct { + /// Open and close notifications are sent to the server. If omitted open close notification should not + /// be sent. + openClose: ?bool = null, + /// Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full + /// and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None. + change: ?TextDocumentSyncKind = null, + /// If present will save notifications are sent to the server. If omitted the notification should not be + /// sent. + willSave: ?bool = null, + /// If present will save wait until requests are sent to the server. If omitted the request should not be + /// sent. + willSaveWaitUntil: ?bool = null, + /// If present save notifications are sent to the server. If omitted the notification should not be + /// sent. + save: ?union(enum) { + bool: bool, + SaveOptions: SaveOptions, + pub usingnamespace UnionParser(@This()); + } = null, +}; + +/// Options specific to a notebook plus its cells +/// to be synced to the server. +/// +/// If a selector provides a notebook document +/// filter but no cell selector all cells of a +/// matching notebook document will be synced. +/// +/// If a selector provides no notebook document +/// filter but only a cell selector all notebook +/// document that contain at least one matching +/// cell will be synced. +/// +/// @since 3.17.0 +pub const NotebookDocumentSyncOptions = struct { + /// The notebooks to be synced + notebookSelector: []const union(enum) { + NotebookDocumentFilterWithNotebook: NotebookDocumentFilterWithNotebook, + NotebookDocumentFilterWithCells: NotebookDocumentFilterWithCells, + pub usingnamespace UnionParser(@This()); + }, + /// Whether save notification should be forwarded to + /// the server. Will only be honored if mode === `notebook`. + save: ?bool = null, +}; + +/// Registration options specific to a notebook. +/// +/// @since 3.17.0 +pub const NotebookDocumentSyncRegistrationOptions = struct { + + // Extends NotebookDocumentSyncOptions + /// The notebooks to be synced + notebookSelector: []const union(enum) { + NotebookDocumentFilterWithNotebook: NotebookDocumentFilterWithNotebook, + NotebookDocumentFilterWithCells: NotebookDocumentFilterWithCells, + pub usingnamespace UnionParser(@This()); + }, + /// Whether save notification should be forwarded to + /// the server. Will only be honored if mode === `notebook`. + save: ?bool = null, + + // Uses mixin StaticRegistrationOptions + /// The id used to register the request. The id can be used to deregister + /// the request again. See also Registration#id. + id: ?[]const u8 = null, +}; + +/// Defines workspace specific capabilities of the server. +/// +/// @since 3.18.0 +/// @proposed +pub const WorkspaceOptions = struct { + /// The server supports workspace folder. + /// + /// @since 3.6.0 + workspaceFolders: ?WorkspaceFoldersServerCapabilities = null, + /// The server is interested in notifications/requests for operations on files. + /// + /// @since 3.16.0 + fileOperations: ?FileOperationOptions = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const TextDocumentContentChangePartial = struct { + /// The range of the document that changed. + range: Range, + /// The optional length of the range that got replaced. + /// + /// @deprecated use range instead. + rangeLength: ?u32 = null, + /// The new text for the provided range. + text: []const u8, +}; + +/// @since 3.18.0 +/// @proposed +pub const TextDocumentContentChangeWholeDocument = struct { + /// The new text of the whole document. + text: []const u8, +}; + +/// Structure to capture a description for an error code. +/// +/// @since 3.16.0 +pub const CodeDescription = struct { + /// An URI to open with more information about the diagnostic error. + href: URI, +}; + +/// Represents a related message and source code location for a diagnostic. This should be +/// used to point to code locations that cause or related to a diagnostics, e.g when duplicating +/// a symbol in a scope. +pub const DiagnosticRelatedInformation = struct { + /// The location of this related diagnostic information. + location: Location, + /// The message of this related diagnostic information. + message: []const u8, +}; + +/// Edit range variant that includes ranges for insert and replace operations. +/// +/// @since 3.18.0 +/// @proposed +pub const EditRangeWithInsertReplace = struct { + insert: Range, + replace: Range, +}; + +/// @since 3.18.0 +/// @proposed +pub const ServerCompletionItemOptions = struct { + /// The server has support for completion item label + /// details (see also `CompletionItemLabelDetails`) when + /// receiving a completion item in a resolve call. + /// + /// @since 3.17.0 + labelDetailsSupport: ?bool = null, +}; + +/// @since 3.18.0 +/// @proposed +/// @deprecated use MarkupContent instead. +pub const MarkedStringWithLanguage = struct { + language: []const u8, + value: []const u8, +}; + +/// Represents a parameter of a callable-signature. A parameter can +/// have a label and a doc-comment. +pub const ParameterInformation = struct { + /// The label of this parameter information. + /// + /// Either a string or an inclusive start and exclusive end offsets within its containing + /// signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 + /// string representation as `Position` and `Range` does. + /// + /// *Note*: a label of type string should be a substring of its containing signature label. + /// Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. + label: union(enum) { + string: []const u8, + tuple_1: struct { u32, u32 }, + pub usingnamespace UnionParser(@This()); + }, + /// The human-readable doc-comment of this parameter. Will be shown + /// in the UI but can be omitted. + documentation: ?union(enum) { + string: []const u8, + MarkupContent: MarkupContent, + pub usingnamespace UnionParser(@This()); + } = null, +}; + +/// A notebook cell text document filter denotes a cell text +/// document by different properties. +/// +/// @since 3.17.0 +pub const NotebookCellTextDocumentFilter = struct { + /// A filter that matches against the notebook + /// containing the notebook cell. If a string + /// value is provided it matches against the + /// notebook type. '*' matches every notebook. + notebook: union(enum) { + string: []const u8, + NotebookDocumentFilter: NotebookDocumentFilter, + pub usingnamespace UnionParser(@This()); + }, + /// A language id like `python`. + /// + /// Will be matched against the language id of the + /// notebook cell document. '*' matches every language. + language: ?[]const u8 = null, +}; + +/// Matching options for the file operation pattern. +/// +/// @since 3.16.0 +pub const FileOperationPatternOptions = struct { + /// The pattern should be matched ignoring casing. + ignoreCase: ?bool = null, +}; + +pub const ExecutionSummary = struct { + /// A strict monotonically increasing value + /// indicating the execution order of a cell + /// inside a notebook. + executionOrder: u32, + /// Whether the execution was successful or + /// not if known by the client. + success: ?bool = null, +}; + +/// Structural changes to cells in a notebook document. +/// +/// @since 3.18.0 +/// @proposed +pub const NotebookDocumentCellChangeStructure = struct { + /// The change to the cell array. + array: NotebookCellArrayChange, + /// Additional opened cell text documents. + didOpen: ?[]const TextDocumentItem = null, + /// Additional closed cell text documents. + didClose: ?[]const TextDocumentIdentifier = null, +}; + +/// Content changes to a cell in a notebook document. +/// +/// @since 3.18.0 +/// @proposed +pub const NotebookDocumentCellContentChanges = struct { + document: VersionedTextDocumentIdentifier, + changes: []const TextDocumentContentChangeEvent, +}; + +/// Workspace specific client capabilities. +pub const WorkspaceClientCapabilities = struct { + /// The client supports applying batch edits + /// to the workspace by supporting the request + /// 'workspace/applyEdit' + applyEdit: ?bool = null, + /// Capabilities specific to `WorkspaceEdit`s. + workspaceEdit: ?WorkspaceEditClientCapabilities = null, + /// Capabilities specific to the `workspace/didChangeConfiguration` notification. + didChangeConfiguration: ?DidChangeConfigurationClientCapabilities = null, + /// Capabilities specific to the `workspace/didChangeWatchedFiles` notification. + didChangeWatchedFiles: ?DidChangeWatchedFilesClientCapabilities = null, + /// Capabilities specific to the `workspace/symbol` request. + symbol: ?WorkspaceSymbolClientCapabilities = null, + /// Capabilities specific to the `workspace/executeCommand` request. + executeCommand: ?ExecuteCommandClientCapabilities = null, + /// The client has support for workspace folders. + /// + /// @since 3.6.0 + workspaceFolders: ?bool = null, + /// The client supports `workspace/configuration` requests. + /// + /// @since 3.6.0 + configuration: ?bool = null, + /// Capabilities specific to the semantic token requests scoped to the + /// workspace. + /// + /// @since 3.16.0. + semanticTokens: ?SemanticTokensWorkspaceClientCapabilities = null, + /// Capabilities specific to the code lens requests scoped to the + /// workspace. + /// + /// @since 3.16.0. + codeLens: ?CodeLensWorkspaceClientCapabilities = null, + /// The client has support for file notifications/requests for user operations on files. + /// + /// Since 3.16.0 + fileOperations: ?FileOperationClientCapabilities = null, + /// Capabilities specific to the inline values requests scoped to the + /// workspace. + /// + /// @since 3.17.0. + inlineValue: ?InlineValueWorkspaceClientCapabilities = null, + /// Capabilities specific to the inlay hint requests scoped to the + /// workspace. + /// + /// @since 3.17.0. + inlayHint: ?InlayHintWorkspaceClientCapabilities = null, + /// Capabilities specific to the diagnostic requests scoped to the + /// workspace. + /// + /// @since 3.17.0. + diagnostics: ?DiagnosticWorkspaceClientCapabilities = null, + /// Capabilities specific to the folding range requests scoped to the workspace. + /// + /// @since 3.18.0 + /// @proposed + foldingRange: ?FoldingRangeWorkspaceClientCapabilities = null, +}; + +/// Text document specific client capabilities. +pub const TextDocumentClientCapabilities = struct { + /// Defines which synchronization capabilities the client supports. + synchronization: ?TextDocumentSyncClientCapabilities = null, + /// Capabilities specific to the `textDocument/completion` request. + completion: ?CompletionClientCapabilities = null, + /// Capabilities specific to the `textDocument/hover` request. + hover: ?HoverClientCapabilities = null, + /// Capabilities specific to the `textDocument/signatureHelp` request. + signatureHelp: ?SignatureHelpClientCapabilities = null, + /// Capabilities specific to the `textDocument/declaration` request. + /// + /// @since 3.14.0 + declaration: ?DeclarationClientCapabilities = null, + /// Capabilities specific to the `textDocument/definition` request. + definition: ?DefinitionClientCapabilities = null, + /// Capabilities specific to the `textDocument/typeDefinition` request. + /// + /// @since 3.6.0 + typeDefinition: ?TypeDefinitionClientCapabilities = null, + /// Capabilities specific to the `textDocument/implementation` request. + /// + /// @since 3.6.0 + implementation: ?ImplementationClientCapabilities = null, + /// Capabilities specific to the `textDocument/references` request. + references: ?ReferenceClientCapabilities = null, + /// Capabilities specific to the `textDocument/documentHighlight` request. + documentHighlight: ?DocumentHighlightClientCapabilities = null, + /// Capabilities specific to the `textDocument/documentSymbol` request. + documentSymbol: ?DocumentSymbolClientCapabilities = null, + /// Capabilities specific to the `textDocument/codeAction` request. + codeAction: ?CodeActionClientCapabilities = null, + /// Capabilities specific to the `textDocument/codeLens` request. + codeLens: ?CodeLensClientCapabilities = null, + /// Capabilities specific to the `textDocument/documentLink` request. + documentLink: ?DocumentLinkClientCapabilities = null, + /// Capabilities specific to the `textDocument/documentColor` and the + /// `textDocument/colorPresentation` request. + /// + /// @since 3.6.0 + colorProvider: ?DocumentColorClientCapabilities = null, + /// Capabilities specific to the `textDocument/formatting` request. + formatting: ?DocumentFormattingClientCapabilities = null, + /// Capabilities specific to the `textDocument/rangeFormatting` request. + rangeFormatting: ?DocumentRangeFormattingClientCapabilities = null, + /// Capabilities specific to the `textDocument/onTypeFormatting` request. + onTypeFormatting: ?DocumentOnTypeFormattingClientCapabilities = null, + /// Capabilities specific to the `textDocument/rename` request. + rename: ?RenameClientCapabilities = null, + /// Capabilities specific to the `textDocument/foldingRange` request. + /// + /// @since 3.10.0 + foldingRange: ?FoldingRangeClientCapabilities = null, + /// Capabilities specific to the `textDocument/selectionRange` request. + /// + /// @since 3.15.0 + selectionRange: ?SelectionRangeClientCapabilities = null, + /// Capabilities specific to the `textDocument/publishDiagnostics` notification. + publishDiagnostics: ?PublishDiagnosticsClientCapabilities = null, + /// Capabilities specific to the various call hierarchy requests. + /// + /// @since 3.16.0 + callHierarchy: ?CallHierarchyClientCapabilities = null, + /// Capabilities specific to the various semantic token request. + /// + /// @since 3.16.0 + semanticTokens: ?SemanticTokensClientCapabilities = null, + /// Capabilities specific to the `textDocument/linkedEditingRange` request. + /// + /// @since 3.16.0 + linkedEditingRange: ?LinkedEditingRangeClientCapabilities = null, + /// Client capabilities specific to the `textDocument/moniker` request. + /// + /// @since 3.16.0 + moniker: ?MonikerClientCapabilities = null, + /// Capabilities specific to the various type hierarchy requests. + /// + /// @since 3.17.0 + typeHierarchy: ?TypeHierarchyClientCapabilities = null, + /// Capabilities specific to the `textDocument/inlineValue` request. + /// + /// @since 3.17.0 + inlineValue: ?InlineValueClientCapabilities = null, + /// Capabilities specific to the `textDocument/inlayHint` request. + /// + /// @since 3.17.0 + inlayHint: ?InlayHintClientCapabilities = null, + /// Capabilities specific to the diagnostic pull model. + /// + /// @since 3.17.0 + diagnostic: ?DiagnosticClientCapabilities = null, + /// Client capabilities specific to inline completions. + /// + /// @since 3.18.0 + /// @proposed + inlineCompletion: ?InlineCompletionClientCapabilities = null, +}; + +/// Capabilities specific to the notebook document support. +/// +/// @since 3.17.0 +pub const NotebookDocumentClientCapabilities = struct { + /// Capabilities specific to notebook document synchronization + /// + /// @since 3.17.0 + synchronization: NotebookDocumentSyncClientCapabilities, +}; + +pub const WindowClientCapabilities = struct { + /// It indicates whether the client supports server initiated + /// progress using the `window/workDoneProgress/create` request. + /// + /// The capability also controls Whether client supports handling + /// of progress notifications. If set servers are allowed to report a + /// `workDoneProgress` property in the request specific server + /// capabilities. + /// + /// @since 3.15.0 + workDoneProgress: ?bool = null, + /// Capabilities specific to the showMessage request. + /// + /// @since 3.16.0 + showMessage: ?ShowMessageRequestClientCapabilities = null, + /// Capabilities specific to the showDocument request. + /// + /// @since 3.16.0 + showDocument: ?ShowDocumentClientCapabilities = null, +}; + +/// General client capabilities. +/// +/// @since 3.16.0 +pub const GeneralClientCapabilities = struct { + /// Client capability that signals how the client + /// handles stale requests (e.g. a request + /// for which the client will not process the response + /// anymore since the information is outdated). + /// + /// @since 3.17.0 + staleRequestSupport: ?StaleRequestSupportOptions = null, + /// Client capabilities specific to regular expressions. + /// + /// @since 3.16.0 + regularExpressions: ?RegularExpressionsClientCapabilities = null, + /// Client capabilities specific to the client's markdown parser. + /// + /// @since 3.16.0 + markdown: ?MarkdownClientCapabilities = null, + /// The position encodings supported by the client. Client and server + /// have to agree on the same position encoding to ensure that offsets + /// (e.g. character position in a line) are interpreted the same on both + /// sides. + /// + /// To keep the protocol backwards compatible the following applies: if + /// the value 'utf-16' is missing from the array of position encodings + /// servers can assume that the client supports UTF-16. UTF-16 is + /// therefore a mandatory encoding. + /// + /// If omitted it defaults to ['utf-16']. + /// + /// Implementation considerations: since the conversion from one encoding + /// into another requires the content of the file / line the conversion + /// is best done where the file is read which is usually on the server + /// side. + /// + /// @since 3.17.0 + positionEncodings: ?[]const PositionEncodingKind = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const NotebookDocumentFilterWithNotebook = struct { + /// The notebook to be synced If a string + /// value is provided it matches against the + /// notebook type. '*' matches every notebook. + notebook: union(enum) { + string: []const u8, + NotebookDocumentFilter: NotebookDocumentFilter, + pub usingnamespace UnionParser(@This()); + }, + /// The cells of the matching notebook to be synced. + cells: ?[]const NotebookCellLanguage = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const NotebookDocumentFilterWithCells = struct { + /// The notebook to be synced If a string + /// value is provided it matches against the + /// notebook type. '*' matches every notebook. + notebook: ?union(enum) { + string: []const u8, + NotebookDocumentFilter: NotebookDocumentFilter, + pub usingnamespace UnionParser(@This()); + } = null, + /// The cells of the matching notebook to be synced. + cells: []const NotebookCellLanguage, +}; + +pub const WorkspaceFoldersServerCapabilities = struct { + /// The server has support for workspace folders + supported: ?bool = null, + /// Whether the server wants to receive workspace folder + /// change notifications. + /// + /// If a string is provided the string is treated as an ID + /// under which the notification is registered on the client + /// side. The ID can be used to unregister for these events + /// using the `client/unregisterCapability` request. + changeNotifications: ?union(enum) { + string: []const u8, + bool: bool, + pub usingnamespace UnionParser(@This()); + } = null, +}; + +/// Options for notifications/requests for user operations on files. +/// +/// @since 3.16.0 +pub const FileOperationOptions = struct { + /// The server is interested in receiving didCreateFiles notifications. + didCreate: ?FileOperationRegistrationOptions = null, + /// The server is interested in receiving willCreateFiles requests. + willCreate: ?FileOperationRegistrationOptions = null, + /// The server is interested in receiving didRenameFiles notifications. + didRename: ?FileOperationRegistrationOptions = null, + /// The server is interested in receiving willRenameFiles requests. + willRename: ?FileOperationRegistrationOptions = null, + /// The server is interested in receiving didDeleteFiles file notifications. + didDelete: ?FileOperationRegistrationOptions = null, + /// The server is interested in receiving willDeleteFiles file requests. + willDelete: ?FileOperationRegistrationOptions = null, +}; + +/// A relative pattern is a helper to construct glob patterns that are matched +/// relatively to a base URI. The common value for a `baseUri` is a workspace +/// folder root, but it can be another absolute URI as well. +/// +/// @since 3.17.0 +pub const RelativePattern = struct { + /// A workspace folder or a base URI to which this pattern will be matched + /// against relatively. + baseUri: union(enum) { + WorkspaceFolder: WorkspaceFolder, + uri: URI, + pub usingnamespace UnionParser(@This()); + }, + /// The actual glob pattern; + pattern: Pattern, +}; + +/// A document filter where `language` is required field. +/// +/// @since 3.18.0 +/// @proposed +pub const TextDocumentFilterLanguage = struct { + /// A language id, like `typescript`. + language: []const u8, + /// A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + scheme: ?[]const u8 = null, + /// A glob pattern, like **​/*.{ts,js}. See TextDocumentFilter for examples. + pattern: ?[]const u8 = null, +}; + +/// A document filter where `scheme` is required field. +/// +/// @since 3.18.0 +/// @proposed +pub const TextDocumentFilterScheme = struct { + /// A language id, like `typescript`. + language: ?[]const u8 = null, + /// A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + scheme: []const u8, + /// A glob pattern, like **​/*.{ts,js}. See TextDocumentFilter for examples. + pattern: ?[]const u8 = null, +}; + +/// A document filter where `pattern` is required field. +/// +/// @since 3.18.0 +/// @proposed +pub const TextDocumentFilterPattern = struct { + /// A language id, like `typescript`. + language: ?[]const u8 = null, + /// A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + scheme: ?[]const u8 = null, + /// A glob pattern, like **​/*.{ts,js}. See TextDocumentFilter for examples. + pattern: []const u8, +}; + +/// A change describing how to move a `NotebookCell` +/// array from state S to S'. +/// +/// @since 3.17.0 +pub const NotebookCellArrayChange = struct { + /// The start oftest of the cell that changed. + start: u32, + /// The deleted cells + deleteCount: u32, + /// The new cells, if any + cells: ?[]const NotebookCell = null, +}; + +pub const WorkspaceEditClientCapabilities = struct { + /// The client supports versioned document changes in `WorkspaceEdit`s + documentChanges: ?bool = null, + /// The resource operations the client supports. Clients should at least + /// support 'create', 'rename' and 'delete' files and folders. + /// + /// @since 3.13.0 + resourceOperations: ?[]const ResourceOperationKind = null, + /// The failure handling strategy of a client if applying the workspace edit + /// fails. + /// + /// @since 3.13.0 + failureHandling: ?FailureHandlingKind = null, + /// Whether the client normalizes line endings to the client specific + /// setting. + /// If set to `true` the client will normalize line ending characters + /// in a workspace edit to the client-specified new line + /// character. + /// + /// @since 3.16.0 + normalizesLineEndings: ?bool = null, + /// Whether the client in general supports change annotations on text edits, + /// create file, rename file and delete file changes. + /// + /// @since 3.16.0 + changeAnnotationSupport: ?ChangeAnnotationsSupportOptions = null, +}; + +pub const DidChangeConfigurationClientCapabilities = struct { + /// Did change configuration notification supports dynamic registration. + dynamicRegistration: ?bool = null, +}; + +pub const DidChangeWatchedFilesClientCapabilities = struct { + /// Did change watched files notification supports dynamic registration. Please note + /// that the current protocol doesn't support static configuration for file changes + /// from the server side. + dynamicRegistration: ?bool = null, + /// Whether the client has support for {@link RelativePattern relative pattern} + /// or not. + /// + /// @since 3.17.0 + relativePatternSupport: ?bool = null, +}; + +/// Client capabilities for a {@link WorkspaceSymbolRequest}. +pub const WorkspaceSymbolClientCapabilities = struct { + /// Symbol request supports dynamic registration. + dynamicRegistration: ?bool = null, + /// Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. + symbolKind: ?ClientSymbolKindOptions = null, + /// The client supports tags on `SymbolInformation`. + /// Clients supporting tags have to handle unknown tags gracefully. + /// + /// @since 3.16.0 + tagSupport: ?ClientSymbolTagOptions = null, + /// The client support partial workspace symbols. The client will send the + /// request `workspaceSymbol/resolve` to the server to resolve additional + /// properties. + /// + /// @since 3.17.0 + resolveSupport: ?ClientSymbolResolveOptions = null, +}; + +/// The client capabilities of a {@link ExecuteCommandRequest}. +pub const ExecuteCommandClientCapabilities = struct { + /// Execute command supports dynamic registration. + dynamicRegistration: ?bool = null, +}; + +/// @since 3.16.0 +pub const SemanticTokensWorkspaceClientCapabilities = struct { + /// Whether the client implementation supports a refresh request sent from + /// the server to the client. + /// + /// Note that this event is global and will force the client to refresh all + /// semantic tokens currently shown. It should be used with absolute care + /// and is useful for situation where a server for example detects a project + /// wide change that requires such a calculation. + refreshSupport: ?bool = null, +}; + +/// @since 3.16.0 +pub const CodeLensWorkspaceClientCapabilities = struct { + /// Whether the client implementation supports a refresh request sent from the + /// server to the client. + /// + /// Note that this event is global and will force the client to refresh all + /// code lenses currently shown. It should be used with absolute care and is + /// useful for situation where a server for example detect a project wide + /// change that requires such a calculation. + refreshSupport: ?bool = null, +}; + +/// Capabilities relating to events from file operations by the user in the client. +/// +/// These events do not come from the file system, they come from user operations +/// like renaming a file in the UI. +/// +/// @since 3.16.0 +pub const FileOperationClientCapabilities = struct { + /// Whether the client supports dynamic registration for file requests/notifications. + dynamicRegistration: ?bool = null, + /// The client has support for sending didCreateFiles notifications. + didCreate: ?bool = null, + /// The client has support for sending willCreateFiles requests. + willCreate: ?bool = null, + /// The client has support for sending didRenameFiles notifications. + didRename: ?bool = null, + /// The client has support for sending willRenameFiles requests. + willRename: ?bool = null, + /// The client has support for sending didDeleteFiles notifications. + didDelete: ?bool = null, + /// The client has support for sending willDeleteFiles requests. + willDelete: ?bool = null, +}; + +/// Client workspace capabilities specific to inline values. +/// +/// @since 3.17.0 +pub const InlineValueWorkspaceClientCapabilities = struct { + /// Whether the client implementation supports a refresh request sent from the + /// server to the client. + /// + /// Note that this event is global and will force the client to refresh all + /// inline values currently shown. It should be used with absolute care and is + /// useful for situation where a server for example detects a project wide + /// change that requires such a calculation. + refreshSupport: ?bool = null, +}; + +/// Client workspace capabilities specific to inlay hints. +/// +/// @since 3.17.0 +pub const InlayHintWorkspaceClientCapabilities = struct { + /// Whether the client implementation supports a refresh request sent from + /// the server to the client. + /// + /// Note that this event is global and will force the client to refresh all + /// inlay hints currently shown. It should be used with absolute care and + /// is useful for situation where a server for example detects a project wide + /// change that requires such a calculation. + refreshSupport: ?bool = null, +}; + +/// Workspace client capabilities specific to diagnostic pull requests. +/// +/// @since 3.17.0 +pub const DiagnosticWorkspaceClientCapabilities = struct { + /// Whether the client implementation supports a refresh request sent from + /// the server to the client. + /// + /// Note that this event is global and will force the client to refresh all + /// pulled diagnostics currently shown. It should be used with absolute care and + /// is useful for situation where a server for example detects a project wide + /// change that requires such a calculation. + refreshSupport: ?bool = null, +}; + +/// Client workspace capabilities specific to folding ranges +/// +/// @since 3.18.0 +/// @proposed +pub const FoldingRangeWorkspaceClientCapabilities = struct { + /// Whether the client implementation supports a refresh request sent from the + /// server to the client. + /// + /// Note that this event is global and will force the client to refresh all + /// folding ranges currently shown. It should be used with absolute care and is + /// useful for situation where a server for example detects a project wide + /// change that requires such a calculation. + /// + /// @since 3.18.0 + /// @proposed + refreshSupport: ?bool = null, +}; + +pub const TextDocumentSyncClientCapabilities = struct { + /// Whether text document synchronization supports dynamic registration. + dynamicRegistration: ?bool = null, + /// The client supports sending will save notifications. + willSave: ?bool = null, + /// The client supports sending a will save request and + /// waits for a response providing text edits which will + /// be applied to the document before it is saved. + willSaveWaitUntil: ?bool = null, + /// The client supports did save notifications. + didSave: ?bool = null, +}; + +/// Completion client capabilities +pub const CompletionClientCapabilities = struct { + /// Whether completion supports dynamic registration. + dynamicRegistration: ?bool = null, + /// The client supports the following `CompletionItem` specific + /// capabilities. + completionItem: ?ClientCompletionItemOptions = null, + completionItemKind: ?ClientCompletionItemOptionsKind = null, + /// Defines how the client handles whitespace and indentation + /// when accepting a completion item that uses multi line + /// text in either `insertText` or `textEdit`. + /// + /// @since 3.17.0 + insertTextMode: ?InsertTextMode = null, + /// The client supports to send additional context information for a + /// `textDocument/completion` request. + contextSupport: ?bool = null, + /// The client supports the following `CompletionList` specific + /// capabilities. + /// + /// @since 3.17.0 + completionList: ?CompletionListCapabilities = null, +}; + +pub const HoverClientCapabilities = struct { + /// Whether hover supports dynamic registration. + dynamicRegistration: ?bool = null, + /// Client supports the following content formats for the content + /// property. The order describes the preferred format of the client. + contentFormat: ?[]const MarkupKind = null, +}; + +/// Client Capabilities for a {@link SignatureHelpRequest}. +pub const SignatureHelpClientCapabilities = struct { + /// Whether signature help supports dynamic registration. + dynamicRegistration: ?bool = null, + /// The client supports the following `SignatureInformation` + /// specific properties. + signatureInformation: ?ClientSignatureInformationOptions = null, + /// The client supports to send additional context information for a + /// `textDocument/signatureHelp` request. A client that opts into + /// contextSupport will also support the `retriggerCharacters` on + /// `SignatureHelpOptions`. + /// + /// @since 3.15.0 + contextSupport: ?bool = null, +}; + +/// @since 3.14.0 +pub const DeclarationClientCapabilities = struct { + /// Whether declaration supports dynamic registration. If this is set to `true` + /// the client supports the new `DeclarationRegistrationOptions` return value + /// for the corresponding server capability as well. + dynamicRegistration: ?bool = null, + /// The client supports additional metadata in the form of declaration links. + linkSupport: ?bool = null, +}; + +/// Client Capabilities for a {@link DefinitionRequest}. +pub const DefinitionClientCapabilities = struct { + /// Whether definition supports dynamic registration. + dynamicRegistration: ?bool = null, + /// The client supports additional metadata in the form of definition links. + /// + /// @since 3.14.0 + linkSupport: ?bool = null, +}; + +/// Since 3.6.0 +pub const TypeDefinitionClientCapabilities = struct { + /// Whether implementation supports dynamic registration. If this is set to `true` + /// the client supports the new `TypeDefinitionRegistrationOptions` return value + /// for the corresponding server capability as well. + dynamicRegistration: ?bool = null, + /// The client supports additional metadata in the form of definition links. + /// + /// Since 3.14.0 + linkSupport: ?bool = null, +}; + +/// @since 3.6.0 +pub const ImplementationClientCapabilities = struct { + /// Whether implementation supports dynamic registration. If this is set to `true` + /// the client supports the new `ImplementationRegistrationOptions` return value + /// for the corresponding server capability as well. + dynamicRegistration: ?bool = null, + /// The client supports additional metadata in the form of definition links. + /// + /// @since 3.14.0 + linkSupport: ?bool = null, +}; + +/// Client Capabilities for a {@link ReferencesRequest}. +pub const ReferenceClientCapabilities = struct { + /// Whether references supports dynamic registration. + dynamicRegistration: ?bool = null, +}; + +/// Client Capabilities for a {@link DocumentHighlightRequest}. +pub const DocumentHighlightClientCapabilities = struct { + /// Whether document highlight supports dynamic registration. + dynamicRegistration: ?bool = null, +}; + +/// Client Capabilities for a {@link DocumentSymbolRequest}. +pub const DocumentSymbolClientCapabilities = struct { + /// Whether document symbol supports dynamic registration. + dynamicRegistration: ?bool = null, + /// Specific capabilities for the `SymbolKind` in the + /// `textDocument/documentSymbol` request. + symbolKind: ?ClientSymbolKindOptions = null, + /// The client supports hierarchical document symbols. + hierarchicalDocumentSymbolSupport: ?bool = null, + /// The client supports tags on `SymbolInformation`. Tags are supported on + /// `DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true. + /// Clients supporting tags have to handle unknown tags gracefully. + /// + /// @since 3.16.0 + tagSupport: ?ClientSymbolTagOptions = null, + /// The client supports an additional label presented in the UI when + /// registering a document symbol provider. + /// + /// @since 3.16.0 + labelSupport: ?bool = null, +}; + +/// The Client Capabilities of a {@link CodeActionRequest}. +pub const CodeActionClientCapabilities = struct { + /// Whether code action supports dynamic registration. + dynamicRegistration: ?bool = null, + /// The client support code action literals of type `CodeAction` as a valid + /// response of the `textDocument/codeAction` request. If the property is not + /// set the request can only return `Command` literals. + /// + /// @since 3.8.0 + codeActionLiteralSupport: ?ClientCodeActionLiteralOptions = null, + /// Whether code action supports the `isPreferred` property. + /// + /// @since 3.15.0 + isPreferredSupport: ?bool = null, + /// Whether code action supports the `disabled` property. + /// + /// @since 3.16.0 + disabledSupport: ?bool = null, + /// Whether code action supports the `data` property which is + /// preserved between a `textDocument/codeAction` and a + /// `codeAction/resolve` request. + /// + /// @since 3.16.0 + dataSupport: ?bool = null, + /// Whether the client supports resolving additional code action + /// properties via a separate `codeAction/resolve` request. + /// + /// @since 3.16.0 + resolveSupport: ?ClientCodeActionResolveOptions = null, + /// Whether the client honors the change annotations in + /// text edits and resource operations returned via the + /// `CodeAction#edit` property by for example presenting + /// the workspace edit in the user interface and asking + /// for confirmation. + /// + /// @since 3.16.0 + honorsChangeAnnotations: ?bool = null, +}; + +/// The client capabilities of a {@link CodeLensRequest}. +pub const CodeLensClientCapabilities = struct { + /// Whether code lens supports dynamic registration. + dynamicRegistration: ?bool = null, +}; + +/// The client capabilities of a {@link DocumentLinkRequest}. +pub const DocumentLinkClientCapabilities = struct { + /// Whether document link supports dynamic registration. + dynamicRegistration: ?bool = null, + /// Whether the client supports the `tooltip` property on `DocumentLink`. + /// + /// @since 3.15.0 + tooltipSupport: ?bool = null, +}; + +pub const DocumentColorClientCapabilities = struct { + /// Whether implementation supports dynamic registration. If this is set to `true` + /// the client supports the new `DocumentColorRegistrationOptions` return value + /// for the corresponding server capability as well. + dynamicRegistration: ?bool = null, +}; + +/// Client capabilities of a {@link DocumentFormattingRequest}. +pub const DocumentFormattingClientCapabilities = struct { + /// Whether formatting supports dynamic registration. + dynamicRegistration: ?bool = null, +}; + +/// Client capabilities of a {@link DocumentRangeFormattingRequest}. +pub const DocumentRangeFormattingClientCapabilities = struct { + /// Whether range formatting supports dynamic registration. + dynamicRegistration: ?bool = null, + /// Whether the client supports formatting multiple ranges at once. + /// + /// @since 3.18.0 + /// @proposed + rangesSupport: ?bool = null, +}; + +/// Client capabilities of a {@link DocumentOnTypeFormattingRequest}. +pub const DocumentOnTypeFormattingClientCapabilities = struct { + /// Whether on type formatting supports dynamic registration. + dynamicRegistration: ?bool = null, +}; + +pub const RenameClientCapabilities = struct { + /// Whether rename supports dynamic registration. + dynamicRegistration: ?bool = null, + /// Client supports testing for validity of rename operations + /// before execution. + /// + /// @since 3.12.0 + prepareSupport: ?bool = null, + /// Client supports the default behavior result. + /// + /// The value indicates the default behavior used by the + /// client. + /// + /// @since 3.16.0 + prepareSupportDefaultBehavior: ?PrepareSupportDefaultBehavior = null, + /// Whether the client honors the change annotations in + /// text edits and resource operations returned via the + /// rename request's workspace edit by for example presenting + /// the workspace edit in the user interface and asking + /// for confirmation. + /// + /// @since 3.16.0 + honorsChangeAnnotations: ?bool = null, +}; + +pub const FoldingRangeClientCapabilities = struct { + /// Whether implementation supports dynamic registration for folding range + /// providers. If this is set to `true` the client supports the new + /// `FoldingRangeRegistrationOptions` return value for the corresponding + /// server capability as well. + dynamicRegistration: ?bool = null, + /// The maximum number of folding ranges that the client prefers to receive + /// per document. The value serves as a hint, servers are free to follow the + /// limit. + rangeLimit: ?u32 = null, + /// If set, the client signals that it only supports folding complete lines. + /// If set, client will ignore specified `startCharacter` and `endCharacter` + /// properties in a FoldingRange. + lineFoldingOnly: ?bool = null, + /// Specific options for the folding range kind. + /// + /// @since 3.17.0 + foldingRangeKind: ?ClientFoldingRangeKindOptions = null, + /// Specific options for the folding range. + /// + /// @since 3.17.0 + foldingRange: ?ClientFoldingRangeOptions = null, +}; + +pub const SelectionRangeClientCapabilities = struct { + /// Whether implementation supports dynamic registration for selection range providers. If this is set to `true` + /// the client supports the new `SelectionRangeRegistrationOptions` return value for the corresponding server + /// capability as well. + dynamicRegistration: ?bool = null, +}; + +/// The publish diagnostic client capabilities. +pub const PublishDiagnosticsClientCapabilities = struct { + /// Whether the clients accepts diagnostics with related information. + relatedInformation: ?bool = null, + /// Client supports the tag property to provide meta data about a diagnostic. + /// Clients supporting tags have to handle unknown tags gracefully. + /// + /// @since 3.15.0 + tagSupport: ?ClientDiagnosticsTagOptions = null, + /// Whether the client interprets the version property of the + /// `textDocument/publishDiagnostics` notification's parameter. + /// + /// @since 3.15.0 + versionSupport: ?bool = null, + /// Client supports a codeDescription property + /// + /// @since 3.16.0 + codeDescriptionSupport: ?bool = null, + /// Whether code action supports the `data` property which is + /// preserved between a `textDocument/publishDiagnostics` and + /// `textDocument/codeAction` request. + /// + /// @since 3.16.0 + dataSupport: ?bool = null, +}; + +/// @since 3.16.0 +pub const CallHierarchyClientCapabilities = struct { + /// Whether implementation supports dynamic registration. If this is set to `true` + /// the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + /// return value for the corresponding server capability as well. + dynamicRegistration: ?bool = null, +}; + +/// @since 3.16.0 +pub const SemanticTokensClientCapabilities = struct { + /// Whether implementation supports dynamic registration. If this is set to `true` + /// the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + /// return value for the corresponding server capability as well. + dynamicRegistration: ?bool = null, + /// Which requests the client supports and might send to the server + /// depending on the server's capability. Please note that clients might not + /// show semantic tokens or degrade some of the user experience if a range + /// or full request is advertised by the client but not provided by the + /// server. If for example the client capability `requests.full` and + /// `request.range` are both set to true but the server only provides a + /// range provider the client might not render a minimap correctly or might + /// even decide to not show any semantic tokens at all. + requests: ClientSemanticTokensRequestOptions, + /// The token types that the client supports. + tokenTypes: []const []const u8, + /// The token modifiers that the client supports. + tokenModifiers: []const []const u8, + /// The token formats the clients supports. + formats: []const TokenFormat, + /// Whether the client supports tokens that can overlap each other. + overlappingTokenSupport: ?bool = null, + /// Whether the client supports tokens that can span multiple lines. + multilineTokenSupport: ?bool = null, + /// Whether the client allows the server to actively cancel a + /// semantic token request, e.g. supports returning + /// LSPErrorCodes.ServerCancelled. If a server does the client + /// needs to retrigger the request. + /// + /// @since 3.17.0 + serverCancelSupport: ?bool = null, + /// Whether the client uses semantic tokens to augment existing + /// syntax tokens. If set to `true` client side created syntax + /// tokens and semantic tokens are both used for colorization. If + /// set to `false` the client only uses the returned semantic tokens + /// for colorization. + /// + /// If the value is `undefined` then the client behavior is not + /// specified. + /// + /// @since 3.17.0 + augmentsSyntaxTokens: ?bool = null, +}; + +/// Client capabilities for the linked editing range request. +/// +/// @since 3.16.0 +pub const LinkedEditingRangeClientCapabilities = struct { + /// Whether implementation supports dynamic registration. If this is set to `true` + /// the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + /// return value for the corresponding server capability as well. + dynamicRegistration: ?bool = null, +}; + +/// Client capabilities specific to the moniker request. +/// +/// @since 3.16.0 +pub const MonikerClientCapabilities = struct { + /// Whether moniker supports dynamic registration. If this is set to `true` + /// the client supports the new `MonikerRegistrationOptions` return value + /// for the corresponding server capability as well. + dynamicRegistration: ?bool = null, +}; + +/// @since 3.17.0 +pub const TypeHierarchyClientCapabilities = struct { + /// Whether implementation supports dynamic registration. If this is set to `true` + /// the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + /// return value for the corresponding server capability as well. + dynamicRegistration: ?bool = null, +}; + +/// Client capabilities specific to inline values. +/// +/// @since 3.17.0 +pub const InlineValueClientCapabilities = struct { + /// Whether implementation supports dynamic registration for inline value providers. + dynamicRegistration: ?bool = null, +}; + +/// Inlay hint client capabilities. +/// +/// @since 3.17.0 +pub const InlayHintClientCapabilities = struct { + /// Whether inlay hints support dynamic registration. + dynamicRegistration: ?bool = null, + /// Indicates which properties a client can resolve lazily on an inlay + /// hint. + resolveSupport: ?ClientInlayHintResolveOptions = null, +}; + +/// Client capabilities specific to diagnostic pull requests. +/// +/// @since 3.17.0 +pub const DiagnosticClientCapabilities = struct { + /// Whether implementation supports dynamic registration. If this is set to `true` + /// the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + /// return value for the corresponding server capability as well. + dynamicRegistration: ?bool = null, + /// Whether the clients supports related documents for document diagnostic pulls. + relatedDocumentSupport: ?bool = null, +}; + +/// Client capabilities specific to inline completions. +/// +/// @since 3.18.0 +/// @proposed +pub const InlineCompletionClientCapabilities = struct { + /// Whether implementation supports dynamic registration for inline completion providers. + dynamicRegistration: ?bool = null, +}; + +/// Notebook specific client capabilities. +/// +/// @since 3.17.0 +pub const NotebookDocumentSyncClientCapabilities = struct { + /// Whether implementation supports dynamic registration. If this is + /// set to `true` the client supports the new + /// `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + /// return value for the corresponding server capability as well. + dynamicRegistration: ?bool = null, + /// The client supports sending execution summary data per cell. + executionSummarySupport: ?bool = null, +}; + +/// Show message request client capabilities +pub const ShowMessageRequestClientCapabilities = struct { + /// Capabilities specific to the `MessageActionItem` type. + messageActionItem: ?ClientShowMessageActionItemOptions = null, +}; + +/// Client capabilities for the showDocument request. +/// +/// @since 3.16.0 +pub const ShowDocumentClientCapabilities = struct { + /// The client has support for the showDocument + /// request. + support: bool, +}; + +/// @since 3.18.0 +/// @proposed +pub const StaleRequestSupportOptions = struct { + /// The client will actively cancel the request. + cancel: bool, + /// The list of requests for which the client + /// will retry the request if it receives a + /// response with error code `ContentModified` + retryOnContentModified: []const []const u8, +}; + +/// Client capabilities specific to regular expressions. +/// +/// @since 3.16.0 +pub const RegularExpressionsClientCapabilities = struct { + /// The engine's name. + engine: []const u8, + /// The engine's version. + version: ?[]const u8 = null, +}; + +/// Client capabilities specific to the used markdown parser. +/// +/// @since 3.16.0 +pub const MarkdownClientCapabilities = struct { + /// The name of the parser. + parser: []const u8, + /// The version of the parser. + version: ?[]const u8 = null, + /// A list of HTML tags that the client allows / supports in + /// Markdown. + /// + /// @since 3.17.0 + allowedTags: ?[]const []const u8 = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const NotebookCellLanguage = struct { + language: []const u8, +}; + +/// A notebook document filter where `notebookType` is required field. +/// +/// @since 3.18.0 +/// @proposed +pub const NotebookDocumentFilterNotebookType = struct { + /// The type of the enclosing notebook. + notebookType: []const u8, + /// A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + scheme: ?[]const u8 = null, + /// A glob pattern. + pattern: ?[]const u8 = null, +}; + +/// A notebook document filter where `scheme` is required field. +/// +/// @since 3.18.0 +/// @proposed +pub const NotebookDocumentFilterScheme = struct { + /// The type of the enclosing notebook. + notebookType: ?[]const u8 = null, + /// A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + scheme: []const u8, + /// A glob pattern. + pattern: ?[]const u8 = null, +}; + +/// A notebook document filter where `pattern` is required field. +/// +/// @since 3.18.0 +/// @proposed +pub const NotebookDocumentFilterPattern = struct { + /// The type of the enclosing notebook. + notebookType: ?[]const u8 = null, + /// A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + scheme: ?[]const u8 = null, + /// A glob pattern. + pattern: []const u8, +}; + +/// @since 3.18.0 +/// @proposed +pub const ChangeAnnotationsSupportOptions = struct { + /// Whether the client groups edits with equal labels into tree nodes, + /// for instance all edits labelled with "Changes in Strings" would + /// be a tree node. + groupsOnLabel: ?bool = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientSymbolKindOptions = struct { + /// The symbol kind values the client supports. When this + /// property exists the client also guarantees that it will + /// handle values outside its set gracefully and falls back + /// to a default value when unknown. + /// + /// If this property is not present the client only supports + /// the symbol kinds from `File` to `Array` as defined in + /// the initial version of the protocol. + valueSet: ?[]const SymbolKind = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientSymbolTagOptions = struct { + /// The tags supported by the client. + valueSet: []const SymbolTag, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientSymbolResolveOptions = struct { + /// The properties that a client can resolve lazily. Usually + /// `location.range` + properties: []const []const u8, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientCompletionItemOptions = struct { + /// Client supports snippets as insert text. + /// + /// A snippet can define tab stops and placeholders with `$1`, `$2` + /// and `${3:foo}`. `$0` defines the final tab stop, it defaults to + /// the end of the snippet. Placeholders with equal identifiers are linked, + /// that is typing in one will update others too. + snippetSupport: ?bool = null, + /// Client supports commit characters on a completion item. + commitCharactersSupport: ?bool = null, + /// Client supports the following content formats for the documentation + /// property. The order describes the preferred format of the client. + documentationFormat: ?[]const MarkupKind = null, + /// Client supports the deprecated property on a completion item. + deprecatedSupport: ?bool = null, + /// Client supports the preselect property on a completion item. + preselectSupport: ?bool = null, + /// Client supports the tag property on a completion item. Clients supporting + /// tags have to handle unknown tags gracefully. Clients especially need to + /// preserve unknown tags when sending a completion item back to the server in + /// a resolve call. + /// + /// @since 3.15.0 + tagSupport: ?CompletionItemTagOptions = null, + /// Client support insert replace edit to control different behavior if a + /// completion item is inserted in the text or should replace text. + /// + /// @since 3.16.0 + insertReplaceSupport: ?bool = null, + /// Indicates which properties a client can resolve lazily on a completion + /// item. Before version 3.16.0 only the predefined properties `documentation` + /// and `details` could be resolved lazily. + /// + /// @since 3.16.0 + resolveSupport: ?ClientCompletionItemResolveOptions = null, + /// The client supports the `insertTextMode` property on + /// a completion item to override the whitespace handling mode + /// as defined by the client (see `insertTextMode`). + /// + /// @since 3.16.0 + insertTextModeSupport: ?ClientCompletionItemInsertTextModeOptions = null, + /// The client has support for completion item label + /// details (see also `CompletionItemLabelDetails`). + /// + /// @since 3.17.0 + labelDetailsSupport: ?bool = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientCompletionItemOptionsKind = struct { + /// The completion item kind values the client supports. When this + /// property exists the client also guarantees that it will + /// handle values outside its set gracefully and falls back + /// to a default value when unknown. + /// + /// If this property is not present the client only supports + /// the completion items kinds from `Text` to `Reference` as defined in + /// the initial version of the protocol. + valueSet: ?[]const CompletionItemKind = null, +}; + +/// The client supports the following `CompletionList` specific +/// capabilities. +/// +/// @since 3.17.0 +pub const CompletionListCapabilities = struct { + /// The client supports the following itemDefaults on + /// a completion list. + /// + /// The value lists the supported property names of the + /// `CompletionList.itemDefaults` object. If omitted + /// no properties are supported. + /// + /// @since 3.17.0 + itemDefaults: ?[]const []const u8 = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientSignatureInformationOptions = struct { + /// Client supports the following content formats for the documentation + /// property. The order describes the preferred format of the client. + documentationFormat: ?[]const MarkupKind = null, + /// Client capabilities specific to parameter information. + parameterInformation: ?ClientSignatureParameterInformationOptions = null, + /// The client supports the `activeParameter` property on `SignatureInformation` + /// literal. + /// + /// @since 3.16.0 + activeParameterSupport: ?bool = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientCodeActionLiteralOptions = struct { + /// The code action kind is support with the following value + /// set. + codeActionKind: ClientCodeActionKindOptions, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientCodeActionResolveOptions = struct { + /// The properties that a client can resolve lazily. + properties: []const []const u8, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientFoldingRangeKindOptions = struct { + /// The folding range kind values the client supports. When this + /// property exists the client also guarantees that it will + /// handle values outside its set gracefully and falls back + /// to a default value when unknown. + valueSet: ?[]const FoldingRangeKind = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientFoldingRangeOptions = struct { + /// If set, the client signals that it supports setting collapsedText on + /// folding ranges to display custom labels instead of the default text. + /// + /// @since 3.17.0 + collapsedText: ?bool = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientDiagnosticsTagOptions = struct { + /// The tags supported by the client. + valueSet: []const DiagnosticTag, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientSemanticTokensRequestOptions = struct { + /// The client will send the `textDocument/semanticTokens/range` request if + /// the server provides a corresponding handler. + range: ?union(enum) { + bool: bool, + literal_1: struct {}, + pub usingnamespace UnionParser(@This()); + } = null, + /// The client will send the `textDocument/semanticTokens/full` request if + /// the server provides a corresponding handler. + full: ?union(enum) { + bool: bool, + ClientSemanticTokensRequestFullDelta: ClientSemanticTokensRequestFullDelta, + pub usingnamespace UnionParser(@This()); + } = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientInlayHintResolveOptions = struct { + /// The properties that a client can resolve lazily. + properties: []const []const u8, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientShowMessageActionItemOptions = struct { + /// Whether the client supports additional attributes which + /// are preserved and send back to the server in the + /// request's response. + additionalPropertiesSupport: ?bool = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const CompletionItemTagOptions = struct { + /// The tags supported by the client. + valueSet: []const CompletionItemTag, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientCompletionItemResolveOptions = struct { + /// The properties that a client can resolve lazily. + properties: []const []const u8, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientCompletionItemInsertTextModeOptions = struct { + valueSet: []const InsertTextMode, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientSignatureParameterInformationOptions = struct { + /// The client supports processing label offsets instead of a + /// simple label string. + /// + /// @since 3.14.0 + labelOffsetSupport: ?bool = null, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientCodeActionKindOptions = struct { + /// The code action kind values the client supports. When this + /// property exists the client also guarantees that it will + /// handle values outside its set gracefully and falls back + /// to a default value when unknown. + valueSet: []const CodeActionKind, +}; + +/// @since 3.18.0 +/// @proposed +pub const ClientSemanticTokensRequestFullDelta = struct { + /// The client will send the `textDocument/semanticTokens/full/delta` request if + /// the server provides a corresponding handler. + delta: ?bool = null, +}; + +pub const notification_metadata = [_]NotificationMetadata{ + // The `workspace/didChangeWorkspaceFolders` notification is sent from the client to the server when the workspace + // folder configuration changes. + .{ + .method = "workspace/didChangeWorkspaceFolders", + .documentation = "The `workspace/didChangeWorkspaceFolders` notification is sent from the client to the server when the workspace\nfolder configuration changes.", + .direction = .clientToServer, + .Params = DidChangeWorkspaceFoldersParams, + .registration = .{ .method = null, .Options = null }, + }, + // The `window/workDoneProgress/cancel` notification is sent from the client to the server to cancel a progress + // initiated on the server side. + .{ + .method = "window/workDoneProgress/cancel", + .documentation = "The `window/workDoneProgress/cancel` notification is sent from the client to the server to cancel a progress\ninitiated on the server side.", + .direction = .clientToServer, + .Params = WorkDoneProgressCancelParams, + .registration = .{ .method = null, .Options = null }, + }, + // The did create files notification is sent from the client to the server when + // files were created from within the client. + // + // @since 3.16.0 + .{ + .method = "workspace/didCreateFiles", + .documentation = "The did create files notification is sent from the client to the server when\nfiles were created from within the client.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = CreateFilesParams, + .registration = .{ .method = null, .Options = FileOperationRegistrationOptions }, + }, + // The did rename files notification is sent from the client to the server when + // files were renamed from within the client. + // + // @since 3.16.0 + .{ + .method = "workspace/didRenameFiles", + .documentation = "The did rename files notification is sent from the client to the server when\nfiles were renamed from within the client.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = RenameFilesParams, + .registration = .{ .method = null, .Options = FileOperationRegistrationOptions }, + }, + // The will delete files request is sent from the client to the server before files are actually + // deleted as long as the deletion is triggered from within the client. + // + // @since 3.16.0 + .{ + .method = "workspace/didDeleteFiles", + .documentation = "The will delete files request is sent from the client to the server before files are actually\ndeleted as long as the deletion is triggered from within the client.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = DeleteFilesParams, + .registration = .{ .method = null, .Options = FileOperationRegistrationOptions }, + }, + // A notification sent when a notebook opens. + // + // @since 3.17.0 + .{ + .method = "notebookDocument/didOpen", + .documentation = "A notification sent when a notebook opens.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = DidOpenNotebookDocumentParams, + .registration = .{ .method = "notebookDocument/sync", .Options = null }, + }, + .{ + .method = "notebookDocument/didChange", + .documentation = null, + .direction = .clientToServer, + .Params = DidChangeNotebookDocumentParams, + .registration = .{ .method = "notebookDocument/sync", .Options = null }, + }, + // A notification sent when a notebook document is saved. + // + // @since 3.17.0 + .{ + .method = "notebookDocument/didSave", + .documentation = "A notification sent when a notebook document is saved.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = DidSaveNotebookDocumentParams, + .registration = .{ .method = "notebookDocument/sync", .Options = null }, + }, + // A notification sent when a notebook closes. + // + // @since 3.17.0 + .{ + .method = "notebookDocument/didClose", + .documentation = "A notification sent when a notebook closes.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = DidCloseNotebookDocumentParams, + .registration = .{ .method = "notebookDocument/sync", .Options = null }, + }, + // The initialized notification is sent from the client to the + // server after the client is fully initialized and the server + // is allowed to send requests from the server to the client. + .{ + .method = "initialized", + .documentation = "The initialized notification is sent from the client to the\nserver after the client is fully initialized and the server\nis allowed to send requests from the server to the client.", + .direction = .clientToServer, + .Params = InitializedParams, + .registration = .{ .method = null, .Options = null }, + }, + // The exit event is sent from the client to the server to + // ask the server to exit its process. + .{ + .method = "exit", + .documentation = "The exit event is sent from the client to the server to\nask the server to exit its process.", + .direction = .clientToServer, + .Params = null, + .registration = .{ .method = null, .Options = null }, + }, + // The configuration change notification is sent from the client to the server + // when the client's configuration has changed. The notification contains + // the changed configuration as defined by the language client. + .{ + .method = "workspace/didChangeConfiguration", + .documentation = "The configuration change notification is sent from the client to the server\nwhen the client's configuration has changed. The notification contains\nthe changed configuration as defined by the language client.", + .direction = .clientToServer, + .Params = DidChangeConfigurationParams, + .registration = .{ .method = null, .Options = DidChangeConfigurationRegistrationOptions }, + }, + // The show message notification is sent from a server to a client to ask + // the client to display a particular message in the user interface. + .{ + .method = "window/showMessage", + .documentation = "The show message notification is sent from a server to a client to ask\nthe client to display a particular message in the user interface.", + .direction = .serverToClient, + .Params = ShowMessageParams, + .registration = .{ .method = null, .Options = null }, + }, + // The log message notification is sent from the server to the client to ask + // the client to log a particular message. + .{ + .method = "window/logMessage", + .documentation = "The log message notification is sent from the server to the client to ask\nthe client to log a particular message.", + .direction = .serverToClient, + .Params = LogMessageParams, + .registration = .{ .method = null, .Options = null }, + }, + // The telemetry event notification is sent from the server to the client to ask + // the client to log telemetry data. + .{ + .method = "telemetry/event", + .documentation = "The telemetry event notification is sent from the server to the client to ask\nthe client to log telemetry data.", + .direction = .serverToClient, + .Params = LSPAny, + .registration = .{ .method = null, .Options = null }, + }, + // The document open notification is sent from the client to the server to signal + // newly opened text documents. The document's truth is now managed by the client + // and the server must not try to read the document's truth using the document's + // uri. Open in this sense means it is managed by the client. It doesn't necessarily + // mean that its content is presented in an editor. An open notification must not + // be sent more than once without a corresponding close notification send before. + // This means open and close notification must be balanced and the max open count + // is one. + .{ + .method = "textDocument/didOpen", + .documentation = "The document open notification is sent from the client to the server to signal\nnewly opened text documents. The document's truth is now managed by the client\nand the server must not try to read the document's truth using the document's\nuri. Open in this sense means it is managed by the client. It doesn't necessarily\nmean that its content is presented in an editor. An open notification must not\nbe sent more than once without a corresponding close notification send before.\nThis means open and close notification must be balanced and the max open count\nis one.", + .direction = .clientToServer, + .Params = DidOpenTextDocumentParams, + .registration = .{ .method = null, .Options = TextDocumentRegistrationOptions }, + }, + // The document change notification is sent from the client to the server to signal + // changes to a text document. + .{ + .method = "textDocument/didChange", + .documentation = "The document change notification is sent from the client to the server to signal\nchanges to a text document.", + .direction = .clientToServer, + .Params = DidChangeTextDocumentParams, + .registration = .{ .method = null, .Options = TextDocumentChangeRegistrationOptions }, + }, + // The document close notification is sent from the client to the server when + // the document got closed in the client. The document's truth now exists where + // the document's uri points to (e.g. if the document's uri is a file uri the + // truth now exists on disk). As with the open notification the close notification + // is about managing the document's content. Receiving a close notification + // doesn't mean that the document was open in an editor before. A close + // notification requires a previous open notification to be sent. + .{ + .method = "textDocument/didClose", + .documentation = "The document close notification is sent from the client to the server when\nthe document got closed in the client. The document's truth now exists where\nthe document's uri points to (e.g. if the document's uri is a file uri the\ntruth now exists on disk). As with the open notification the close notification\nis about managing the document's content. Receiving a close notification\ndoesn't mean that the document was open in an editor before. A close\nnotification requires a previous open notification to be sent.", + .direction = .clientToServer, + .Params = DidCloseTextDocumentParams, + .registration = .{ .method = null, .Options = TextDocumentRegistrationOptions }, + }, + // The document save notification is sent from the client to the server when + // the document got saved in the client. + .{ + .method = "textDocument/didSave", + .documentation = "The document save notification is sent from the client to the server when\nthe document got saved in the client.", + .direction = .clientToServer, + .Params = DidSaveTextDocumentParams, + .registration = .{ .method = null, .Options = TextDocumentSaveRegistrationOptions }, + }, + // A document will save notification is sent from the client to the server before + // the document is actually saved. + .{ + .method = "textDocument/willSave", + .documentation = "A document will save notification is sent from the client to the server before\nthe document is actually saved.", + .direction = .clientToServer, + .Params = WillSaveTextDocumentParams, + .registration = .{ .method = null, .Options = TextDocumentRegistrationOptions }, + }, + // The watched files notification is sent from the client to the server when + // the client detects changes to file watched by the language client. + .{ + .method = "workspace/didChangeWatchedFiles", + .documentation = "The watched files notification is sent from the client to the server when\nthe client detects changes to file watched by the language client.", + .direction = .clientToServer, + .Params = DidChangeWatchedFilesParams, + .registration = .{ .method = null, .Options = DidChangeWatchedFilesRegistrationOptions }, + }, + // Diagnostics notification are sent from the server to the client to signal + // results of validation runs. + .{ + .method = "textDocument/publishDiagnostics", + .documentation = "Diagnostics notification are sent from the server to the client to signal\nresults of validation runs.", + .direction = .serverToClient, + .Params = PublishDiagnosticsParams, + .registration = .{ .method = null, .Options = null }, + }, + .{ + .method = "$/setTrace", + .documentation = null, + .direction = .clientToServer, + .Params = SetTraceParams, + .registration = .{ .method = null, .Options = null }, + }, + .{ + .method = "$/logTrace", + .documentation = null, + .direction = .serverToClient, + .Params = LogTraceParams, + .registration = .{ .method = null, .Options = null }, + }, + .{ + .method = "$/cancelRequest", + .documentation = null, + .direction = .both, + .Params = CancelParams, + .registration = .{ .method = null, .Options = null }, + }, + .{ + .method = "$/progress", + .documentation = null, + .direction = .both, + .Params = ProgressParams, + .registration = .{ .method = null, .Options = null }, + }, +}; +pub const request_metadata = [_]RequestMetadata{ + // A request to resolve the implementation locations of a symbol at a given text + // document position. The request's parameter is of type {@link TextDocumentPositionParams} + // the response is of type {@link Definition} or a Thenable that resolves to such. + .{ + .method = "textDocument/implementation", + .documentation = "A request to resolve the implementation locations of a symbol at a given text\ndocument position. The request's parameter is of type {@link TextDocumentPositionParams}\nthe response is of type {@link Definition} or a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = ImplementationParams, + .Result = ?union(enum) { + Definition: Definition, + array_of_DefinitionLink: []const DefinitionLink, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = union(enum) { + array_of_Location: []const Location, + array_of_DefinitionLink: []const DefinitionLink, + pub usingnamespace UnionParser(@This()); + }, + .ErrorData = null, + .registration = .{ .method = null, .Options = ImplementationRegistrationOptions }, + }, + // A request to resolve the type definition locations of a symbol at a given text + // document position. The request's parameter is of type {@link TextDocumentPositionParams} + // the response is of type {@link Definition} or a Thenable that resolves to such. + .{ + .method = "textDocument/typeDefinition", + .documentation = "A request to resolve the type definition locations of a symbol at a given text\ndocument position. The request's parameter is of type {@link TextDocumentPositionParams}\nthe response is of type {@link Definition} or a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = TypeDefinitionParams, + .Result = ?union(enum) { + Definition: Definition, + array_of_DefinitionLink: []const DefinitionLink, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = union(enum) { + array_of_Location: []const Location, + array_of_DefinitionLink: []const DefinitionLink, + pub usingnamespace UnionParser(@This()); + }, + .ErrorData = null, + .registration = .{ .method = null, .Options = TypeDefinitionRegistrationOptions }, + }, + // The `workspace/workspaceFolders` is sent from the server to the client to fetch the open workspace folders. + .{ + .method = "workspace/workspaceFolders", + .documentation = "The `workspace/workspaceFolders` is sent from the server to the client to fetch the open workspace folders.", + .direction = .serverToClient, + .Params = null, + .Result = ?[]const WorkspaceFolder, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // The 'workspace/configuration' request is sent from the server to the client to fetch a certain + // configuration setting. + // + // This pull model replaces the old push model were the client signaled configuration change via an + // event. If the server still needs to react to configuration changes (since the server caches the + // result of `workspace/configuration` requests) the server should register for an empty configuration + // change event and empty the cache if such an event is received. + .{ + .method = "workspace/configuration", + .documentation = "The 'workspace/configuration' request is sent from the server to the client to fetch a certain\nconfiguration setting.\n\nThis pull model replaces the old push model were the client signaled configuration change via an\nevent. If the server still needs to react to configuration changes (since the server caches the\nresult of `workspace/configuration` requests) the server should register for an empty configuration\nchange event and empty the cache if such an event is received.", + .direction = .serverToClient, + .Params = ConfigurationParams, + .Result = []const LSPAny, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to list all color symbols found in a given text document. The request's + // parameter is of type {@link DocumentColorParams} the + // response is of type {@link ColorInformation ColorInformation[]} or a Thenable + // that resolves to such. + .{ + .method = "textDocument/documentColor", + .documentation = "A request to list all color symbols found in a given text document. The request's\nparameter is of type {@link DocumentColorParams} the\nresponse is of type {@link ColorInformation ColorInformation[]} or a Thenable\nthat resolves to such.", + .direction = .clientToServer, + .Params = DocumentColorParams, + .Result = []const ColorInformation, + .PartialResult = []const ColorInformation, + .ErrorData = null, + .registration = .{ .method = null, .Options = DocumentColorRegistrationOptions }, + }, + // A request to list all presentation for a color. The request's + // parameter is of type {@link ColorPresentationParams} the + // response is of type {@link ColorInformation ColorInformation[]} or a Thenable + // that resolves to such. + .{ + .method = "textDocument/colorPresentation", + .documentation = "A request to list all presentation for a color. The request's\nparameter is of type {@link ColorPresentationParams} the\nresponse is of type {@link ColorInformation ColorInformation[]} or a Thenable\nthat resolves to such.", + .direction = .clientToServer, + .Params = ColorPresentationParams, + .Result = []const ColorPresentation, + .PartialResult = []const ColorPresentation, + .ErrorData = null, + .registration = .{ + .method = null, + .Options = struct { + // And WorkDoneProgressOptions + workDoneProgress: ?bool = null, + // And TextDocumentRegistrationOptions + /// A document selector to identify the scope of the registration. If set to null + /// the document selector provided on the client side will be used. + documentSelector: ?DocumentSelector = null, + }, + }, + }, + // A request to provide folding ranges in a document. The request's + // parameter is of type {@link FoldingRangeParams}, the + // response is of type {@link FoldingRangeList} or a Thenable + // that resolves to such. + .{ + .method = "textDocument/foldingRange", + .documentation = "A request to provide folding ranges in a document. The request's\nparameter is of type {@link FoldingRangeParams}, the\nresponse is of type {@link FoldingRangeList} or a Thenable\nthat resolves to such.", + .direction = .clientToServer, + .Params = FoldingRangeParams, + .Result = ?[]const FoldingRange, + .PartialResult = []const FoldingRange, + .ErrorData = null, + .registration = .{ .method = null, .Options = FoldingRangeRegistrationOptions }, + }, + // @since 3.18.0 + // @proposed + .{ + .method = "workspace/foldingRange/refresh", + .documentation = "@since 3.18.0\n@proposed", + .direction = .serverToClient, + .Params = null, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to resolve the type definition locations of a symbol at a given text + // document position. The request's parameter is of type {@link TextDocumentPositionParams} + // the response is of type {@link Declaration} or a typed array of {@link DeclarationLink} + // or a Thenable that resolves to such. + .{ + .method = "textDocument/declaration", + .documentation = "A request to resolve the type definition locations of a symbol at a given text\ndocument position. The request's parameter is of type {@link TextDocumentPositionParams}\nthe response is of type {@link Declaration} or a typed array of {@link DeclarationLink}\nor a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = DeclarationParams, + .Result = ?union(enum) { + Declaration: Declaration, + array_of_DeclarationLink: []const DeclarationLink, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = union(enum) { + array_of_Location: []const Location, + array_of_DeclarationLink: []const DeclarationLink, + pub usingnamespace UnionParser(@This()); + }, + .ErrorData = null, + .registration = .{ .method = null, .Options = DeclarationRegistrationOptions }, + }, + // A request to provide selection ranges in a document. The request's + // parameter is of type {@link SelectionRangeParams}, the + // response is of type {@link SelectionRange SelectionRange[]} or a Thenable + // that resolves to such. + .{ + .method = "textDocument/selectionRange", + .documentation = "A request to provide selection ranges in a document. The request's\nparameter is of type {@link SelectionRangeParams}, the\nresponse is of type {@link SelectionRange SelectionRange[]} or a Thenable\nthat resolves to such.", + .direction = .clientToServer, + .Params = SelectionRangeParams, + .Result = ?[]const SelectionRange, + .PartialResult = []const SelectionRange, + .ErrorData = null, + .registration = .{ .method = null, .Options = SelectionRangeRegistrationOptions }, + }, + // The `window/workDoneProgress/create` request is sent from the server to the client to initiate progress + // reporting from the server. + .{ + .method = "window/workDoneProgress/create", + .documentation = "The `window/workDoneProgress/create` request is sent from the server to the client to initiate progress\nreporting from the server.", + .direction = .serverToClient, + .Params = WorkDoneProgressCreateParams, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to result a `CallHierarchyItem` in a document at a given position. + // Can be used as an input to an incoming or outgoing call hierarchy. + // + // @since 3.16.0 + .{ + .method = "textDocument/prepareCallHierarchy", + .documentation = "A request to result a `CallHierarchyItem` in a document at a given position.\nCan be used as an input to an incoming or outgoing call hierarchy.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = CallHierarchyPrepareParams, + .Result = ?[]const CallHierarchyItem, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = CallHierarchyRegistrationOptions }, + }, + // A request to resolve the incoming calls for a given `CallHierarchyItem`. + // + // @since 3.16.0 + .{ + .method = "callHierarchy/incomingCalls", + .documentation = "A request to resolve the incoming calls for a given `CallHierarchyItem`.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = CallHierarchyIncomingCallsParams, + .Result = ?[]const CallHierarchyIncomingCall, + .PartialResult = []const CallHierarchyIncomingCall, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to resolve the outgoing calls for a given `CallHierarchyItem`. + // + // @since 3.16.0 + .{ + .method = "callHierarchy/outgoingCalls", + .documentation = "A request to resolve the outgoing calls for a given `CallHierarchyItem`.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = CallHierarchyOutgoingCallsParams, + .Result = ?[]const CallHierarchyOutgoingCall, + .PartialResult = []const CallHierarchyOutgoingCall, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // @since 3.16.0 + .{ + .method = "textDocument/semanticTokens/full", + .documentation = "@since 3.16.0", + .direction = .clientToServer, + .Params = SemanticTokensParams, + .Result = ?SemanticTokens, + .PartialResult = SemanticTokensPartialResult, + .ErrorData = null, + .registration = .{ .method = "textDocument/semanticTokens", .Options = SemanticTokensRegistrationOptions }, + }, + // @since 3.16.0 + .{ + .method = "textDocument/semanticTokens/full/delta", + .documentation = "@since 3.16.0", + .direction = .clientToServer, + .Params = SemanticTokensDeltaParams, + .Result = ?union(enum) { + SemanticTokens: SemanticTokens, + SemanticTokensDelta: SemanticTokensDelta, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = union(enum) { + SemanticTokensPartialResult: SemanticTokensPartialResult, + SemanticTokensDeltaPartialResult: SemanticTokensDeltaPartialResult, + pub usingnamespace UnionParser(@This()); + }, + .ErrorData = null, + .registration = .{ .method = "textDocument/semanticTokens", .Options = SemanticTokensRegistrationOptions }, + }, + // @since 3.16.0 + .{ + .method = "textDocument/semanticTokens/range", + .documentation = "@since 3.16.0", + .direction = .clientToServer, + .Params = SemanticTokensRangeParams, + .Result = ?SemanticTokens, + .PartialResult = SemanticTokensPartialResult, + .ErrorData = null, + .registration = .{ .method = "textDocument/semanticTokens", .Options = null }, + }, + // @since 3.16.0 + .{ + .method = "workspace/semanticTokens/refresh", + .documentation = "@since 3.16.0", + .direction = .serverToClient, + .Params = null, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to show a document. This request might open an + // external program depending on the value of the URI to open. + // For example a request to open `https://code.visualstudio.com/` + // will very likely open the URI in a WEB browser. + // + // @since 3.16.0 + .{ + .method = "window/showDocument", + .documentation = "A request to show a document. This request might open an\nexternal program depending on the value of the URI to open.\nFor example a request to open `https://code.visualstudio.com/`\nwill very likely open the URI in a WEB browser.\n\n@since 3.16.0", + .direction = .serverToClient, + .Params = ShowDocumentParams, + .Result = ShowDocumentResult, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to provide ranges that can be edited together. + // + // @since 3.16.0 + .{ + .method = "textDocument/linkedEditingRange", + .documentation = "A request to provide ranges that can be edited together.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = LinkedEditingRangeParams, + .Result = ?LinkedEditingRanges, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = LinkedEditingRangeRegistrationOptions }, + }, + // The will create files request is sent from the client to the server before files are actually + // created as long as the creation is triggered from within the client. + // + // The request can return a `WorkspaceEdit` which will be applied to workspace before the + // files are created. Hence the `WorkspaceEdit` can not manipulate the content of the file + // to be created. + // + // @since 3.16.0 + .{ + .method = "workspace/willCreateFiles", + .documentation = "The will create files request is sent from the client to the server before files are actually\ncreated as long as the creation is triggered from within the client.\n\nThe request can return a `WorkspaceEdit` which will be applied to workspace before the\nfiles are created. Hence the `WorkspaceEdit` can not manipulate the content of the file\nto be created.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = CreateFilesParams, + .Result = ?WorkspaceEdit, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = FileOperationRegistrationOptions }, + }, + // The will rename files request is sent from the client to the server before files are actually + // renamed as long as the rename is triggered from within the client. + // + // @since 3.16.0 + .{ + .method = "workspace/willRenameFiles", + .documentation = "The will rename files request is sent from the client to the server before files are actually\nrenamed as long as the rename is triggered from within the client.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = RenameFilesParams, + .Result = ?WorkspaceEdit, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = FileOperationRegistrationOptions }, + }, + // The did delete files notification is sent from the client to the server when + // files were deleted from within the client. + // + // @since 3.16.0 + .{ + .method = "workspace/willDeleteFiles", + .documentation = "The did delete files notification is sent from the client to the server when\nfiles were deleted from within the client.\n\n@since 3.16.0", + .direction = .clientToServer, + .Params = DeleteFilesParams, + .Result = ?WorkspaceEdit, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = FileOperationRegistrationOptions }, + }, + // A request to get the moniker of a symbol at a given text document position. + // The request parameter is of type {@link TextDocumentPositionParams}. + // The response is of type {@link Moniker Moniker[]} or `null`. + .{ + .method = "textDocument/moniker", + .documentation = "A request to get the moniker of a symbol at a given text document position.\nThe request parameter is of type {@link TextDocumentPositionParams}.\nThe response is of type {@link Moniker Moniker[]} or `null`.", + .direction = .clientToServer, + .Params = MonikerParams, + .Result = ?[]const Moniker, + .PartialResult = []const Moniker, + .ErrorData = null, + .registration = .{ .method = null, .Options = MonikerRegistrationOptions }, + }, + // A request to result a `TypeHierarchyItem` in a document at a given position. + // Can be used as an input to a subtypes or supertypes type hierarchy. + // + // @since 3.17.0 + .{ + .method = "textDocument/prepareTypeHierarchy", + .documentation = "A request to result a `TypeHierarchyItem` in a document at a given position.\nCan be used as an input to a subtypes or supertypes type hierarchy.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = TypeHierarchyPrepareParams, + .Result = ?[]const TypeHierarchyItem, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = TypeHierarchyRegistrationOptions }, + }, + // A request to resolve the supertypes for a given `TypeHierarchyItem`. + // + // @since 3.17.0 + .{ + .method = "typeHierarchy/supertypes", + .documentation = "A request to resolve the supertypes for a given `TypeHierarchyItem`.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = TypeHierarchySupertypesParams, + .Result = ?[]const TypeHierarchyItem, + .PartialResult = []const TypeHierarchyItem, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to resolve the subtypes for a given `TypeHierarchyItem`. + // + // @since 3.17.0 + .{ + .method = "typeHierarchy/subtypes", + .documentation = "A request to resolve the subtypes for a given `TypeHierarchyItem`.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = TypeHierarchySubtypesParams, + .Result = ?[]const TypeHierarchyItem, + .PartialResult = []const TypeHierarchyItem, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to provide inline values in a document. The request's parameter is of + // type {@link InlineValueParams}, the response is of type + // {@link InlineValue InlineValue[]} or a Thenable that resolves to such. + // + // @since 3.17.0 + .{ + .method = "textDocument/inlineValue", + .documentation = "A request to provide inline values in a document. The request's parameter is of\ntype {@link InlineValueParams}, the response is of type\n{@link InlineValue InlineValue[]} or a Thenable that resolves to such.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = InlineValueParams, + .Result = ?[]const InlineValue, + .PartialResult = []const InlineValue, + .ErrorData = null, + .registration = .{ .method = null, .Options = InlineValueRegistrationOptions }, + }, + // @since 3.17.0 + .{ + .method = "workspace/inlineValue/refresh", + .documentation = "@since 3.17.0", + .direction = .serverToClient, + .Params = null, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to provide inlay hints in a document. The request's parameter is of + // type {@link InlayHintsParams}, the response is of type + // {@link InlayHint InlayHint[]} or a Thenable that resolves to such. + // + // @since 3.17.0 + .{ + .method = "textDocument/inlayHint", + .documentation = "A request to provide inlay hints in a document. The request's parameter is of\ntype {@link InlayHintsParams}, the response is of type\n{@link InlayHint InlayHint[]} or a Thenable that resolves to such.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = InlayHintParams, + .Result = ?[]const InlayHint, + .PartialResult = []const InlayHint, + .ErrorData = null, + .registration = .{ .method = null, .Options = InlayHintRegistrationOptions }, + }, + // A request to resolve additional properties for an inlay hint. + // The request's parameter is of type {@link InlayHint}, the response is + // of type {@link InlayHint} or a Thenable that resolves to such. + // + // @since 3.17.0 + .{ + .method = "inlayHint/resolve", + .documentation = "A request to resolve additional properties for an inlay hint.\nThe request's parameter is of type {@link InlayHint}, the response is\nof type {@link InlayHint} or a Thenable that resolves to such.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = InlayHint, + .Result = InlayHint, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // @since 3.17.0 + .{ + .method = "workspace/inlayHint/refresh", + .documentation = "@since 3.17.0", + .direction = .serverToClient, + .Params = null, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // The document diagnostic request definition. + // + // @since 3.17.0 + .{ + .method = "textDocument/diagnostic", + .documentation = "The document diagnostic request definition.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = DocumentDiagnosticParams, + .Result = DocumentDiagnosticReport, + .PartialResult = DocumentDiagnosticReportPartialResult, + .ErrorData = DiagnosticServerCancellationData, + .registration = .{ .method = null, .Options = DiagnosticRegistrationOptions }, + }, + // The workspace diagnostic request definition. + // + // @since 3.17.0 + .{ + .method = "workspace/diagnostic", + .documentation = "The workspace diagnostic request definition.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = WorkspaceDiagnosticParams, + .Result = WorkspaceDiagnosticReport, + .PartialResult = WorkspaceDiagnosticReportPartialResult, + .ErrorData = DiagnosticServerCancellationData, + .registration = .{ .method = null, .Options = null }, + }, + // The diagnostic refresh request definition. + // + // @since 3.17.0 + .{ + .method = "workspace/diagnostic/refresh", + .documentation = "The diagnostic refresh request definition.\n\n@since 3.17.0", + .direction = .serverToClient, + .Params = null, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to provide inline completions in a document. The request's parameter is of + // type {@link InlineCompletionParams}, the response is of type + // {@link InlineCompletion InlineCompletion[]} or a Thenable that resolves to such. + // + // @since 3.18.0 + // @proposed + .{ + .method = "textDocument/inlineCompletion", + .documentation = "A request to provide inline completions in a document. The request's parameter is of\ntype {@link InlineCompletionParams}, the response is of type\n{@link InlineCompletion InlineCompletion[]} or a Thenable that resolves to such.\n\n@since 3.18.0\n@proposed", + .direction = .clientToServer, + .Params = InlineCompletionParams, + .Result = ?union(enum) { + InlineCompletionList: InlineCompletionList, + array_of_InlineCompletionItem: []const InlineCompletionItem, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = []const InlineCompletionItem, + .ErrorData = null, + .registration = .{ .method = null, .Options = InlineCompletionRegistrationOptions }, + }, + // The `client/registerCapability` request is sent from the server to the client to register a new capability + // handler on the client side. + .{ + .method = "client/registerCapability", + .documentation = "The `client/registerCapability` request is sent from the server to the client to register a new capability\nhandler on the client side.", + .direction = .serverToClient, + .Params = RegistrationParams, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // The `client/unregisterCapability` request is sent from the server to the client to unregister a previously registered capability + // handler on the client side. + .{ + .method = "client/unregisterCapability", + .documentation = "The `client/unregisterCapability` request is sent from the server to the client to unregister a previously registered capability\nhandler on the client side.", + .direction = .serverToClient, + .Params = UnregistrationParams, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // The initialize request is sent from the client to the server. + // It is sent once as the request after starting up the server. + // The requests parameter is of type {@link InitializeParams} + // the response if of type {@link InitializeResult} of a Thenable that + // resolves to such. + .{ + .method = "initialize", + .documentation = "The initialize request is sent from the client to the server.\nIt is sent once as the request after starting up the server.\nThe requests parameter is of type {@link InitializeParams}\nthe response if of type {@link InitializeResult} of a Thenable that\nresolves to such.", + .direction = .clientToServer, + .Params = InitializeParams, + .Result = InitializeResult, + .PartialResult = null, + .ErrorData = InitializeError, + .registration = .{ .method = null, .Options = null }, + }, + // A shutdown request is sent from the client to the server. + // It is sent once when the client decides to shutdown the + // server. The only notification that is sent after a shutdown request + // is the exit event. + .{ + .method = "shutdown", + .documentation = "A shutdown request is sent from the client to the server.\nIt is sent once when the client decides to shutdown the\nserver. The only notification that is sent after a shutdown request\nis the exit event.", + .direction = .clientToServer, + .Params = null, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // The show message request is sent from the server to the client to show a message + // and a set of options actions to the user. + .{ + .method = "window/showMessageRequest", + .documentation = "The show message request is sent from the server to the client to show a message\nand a set of options actions to the user.", + .direction = .serverToClient, + .Params = ShowMessageRequestParams, + .Result = ?MessageActionItem, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A document will save request is sent from the client to the server before + // the document is actually saved. The request can return an array of TextEdits + // which will be applied to the text document before it is saved. Please note that + // clients might drop results if computing the text edits took too long or if a + // server constantly fails on this request. This is done to keep the save fast and + // reliable. + .{ + .method = "textDocument/willSaveWaitUntil", + .documentation = "A document will save request is sent from the client to the server before\nthe document is actually saved. The request can return an array of TextEdits\nwhich will be applied to the text document before it is saved. Please note that\nclients might drop results if computing the text edits took too long or if a\nserver constantly fails on this request. This is done to keep the save fast and\nreliable.", + .direction = .clientToServer, + .Params = WillSaveTextDocumentParams, + .Result = ?[]const TextEdit, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = TextDocumentRegistrationOptions }, + }, + // Request to request completion at a given text document position. The request's + // parameter is of type {@link TextDocumentPosition} the response + // is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList} + // or a Thenable that resolves to such. + // + // The request can delay the computation of the {@link CompletionItem.detail `detail`} + // and {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve` + // request. However, properties that are needed for the initial sorting and filtering, like `sortText`, + // `filterText`, `insertText`, and `textEdit`, must not be changed during resolve. + .{ + .method = "textDocument/completion", + .documentation = "Request to request completion at a given text document position. The request's\nparameter is of type {@link TextDocumentPosition} the response\nis of type {@link CompletionItem CompletionItem[]} or {@link CompletionList}\nor a Thenable that resolves to such.\n\nThe request can delay the computation of the {@link CompletionItem.detail `detail`}\nand {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve`\nrequest. However, properties that are needed for the initial sorting and filtering, like `sortText`,\n`filterText`, `insertText`, and `textEdit`, must not be changed during resolve.", + .direction = .clientToServer, + .Params = CompletionParams, + .Result = ?union(enum) { + array_of_CompletionItem: []const CompletionItem, + CompletionList: CompletionList, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = []const CompletionItem, + .ErrorData = null, + .registration = .{ .method = null, .Options = CompletionRegistrationOptions }, + }, + // Request to resolve additional information for a given completion item.The request's + // parameter is of type {@link CompletionItem} the response + // is of type {@link CompletionItem} or a Thenable that resolves to such. + .{ + .method = "completionItem/resolve", + .documentation = "Request to resolve additional information for a given completion item.The request's\nparameter is of type {@link CompletionItem} the response\nis of type {@link CompletionItem} or a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = CompletionItem, + .Result = CompletionItem, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // Request to request hover information at a given text document position. The request's + // parameter is of type {@link TextDocumentPosition} the response is of + // type {@link Hover} or a Thenable that resolves to such. + .{ + .method = "textDocument/hover", + .documentation = "Request to request hover information at a given text document position. The request's\nparameter is of type {@link TextDocumentPosition} the response is of\ntype {@link Hover} or a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = HoverParams, + .Result = ?Hover, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = HoverRegistrationOptions }, + }, + .{ + .method = "textDocument/signatureHelp", + .documentation = null, + .direction = .clientToServer, + .Params = SignatureHelpParams, + .Result = ?SignatureHelp, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = SignatureHelpRegistrationOptions }, + }, + // A request to resolve the definition location of a symbol at a given text + // document position. The request's parameter is of type {@link TextDocumentPosition} + // the response is of either type {@link Definition} or a typed array of + // {@link DefinitionLink} or a Thenable that resolves to such. + .{ + .method = "textDocument/definition", + .documentation = "A request to resolve the definition location of a symbol at a given text\ndocument position. The request's parameter is of type {@link TextDocumentPosition}\nthe response is of either type {@link Definition} or a typed array of\n{@link DefinitionLink} or a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = DefinitionParams, + .Result = ?union(enum) { + Definition: Definition, + array_of_DefinitionLink: []const DefinitionLink, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = union(enum) { + array_of_Location: []const Location, + array_of_DefinitionLink: []const DefinitionLink, + pub usingnamespace UnionParser(@This()); + }, + .ErrorData = null, + .registration = .{ .method = null, .Options = DefinitionRegistrationOptions }, + }, + // A request to resolve project-wide references for the symbol denoted + // by the given text document position. The request's parameter is of + // type {@link ReferenceParams} the response is of type + // {@link Location Location[]} or a Thenable that resolves to such. + .{ + .method = "textDocument/references", + .documentation = "A request to resolve project-wide references for the symbol denoted\nby the given text document position. The request's parameter is of\ntype {@link ReferenceParams} the response is of type\n{@link Location Location[]} or a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = ReferenceParams, + .Result = ?[]const Location, + .PartialResult = []const Location, + .ErrorData = null, + .registration = .{ .method = null, .Options = ReferenceRegistrationOptions }, + }, + // Request to resolve a {@link DocumentHighlight} for a given + // text document position. The request's parameter is of type {@link TextDocumentPosition} + // the request response is an array of type {@link DocumentHighlight} + // or a Thenable that resolves to such. + .{ + .method = "textDocument/documentHighlight", + .documentation = "Request to resolve a {@link DocumentHighlight} for a given\ntext document position. The request's parameter is of type {@link TextDocumentPosition}\nthe request response is an array of type {@link DocumentHighlight}\nor a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = DocumentHighlightParams, + .Result = ?[]const DocumentHighlight, + .PartialResult = []const DocumentHighlight, + .ErrorData = null, + .registration = .{ .method = null, .Options = DocumentHighlightRegistrationOptions }, + }, + // A request to list all symbols found in a given text document. The request's + // parameter is of type {@link TextDocumentIdentifier} the + // response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable + // that resolves to such. + .{ + .method = "textDocument/documentSymbol", + .documentation = "A request to list all symbols found in a given text document. The request's\nparameter is of type {@link TextDocumentIdentifier} the\nresponse is of type {@link SymbolInformation SymbolInformation[]} or a Thenable\nthat resolves to such.", + .direction = .clientToServer, + .Params = DocumentSymbolParams, + .Result = ?union(enum) { + array_of_SymbolInformation: []const SymbolInformation, + array_of_DocumentSymbol: []const DocumentSymbol, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = union(enum) { + array_of_SymbolInformation: []const SymbolInformation, + array_of_DocumentSymbol: []const DocumentSymbol, + pub usingnamespace UnionParser(@This()); + }, + .ErrorData = null, + .registration = .{ .method = null, .Options = DocumentSymbolRegistrationOptions }, + }, + // A request to provide commands for the given text document and range. + .{ + .method = "textDocument/codeAction", + .documentation = "A request to provide commands for the given text document and range.", + .direction = .clientToServer, + .Params = CodeActionParams, + .Result = ?[]const union(enum) { + Command: Command, + CodeAction: CodeAction, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = []const union(enum) { + Command: Command, + CodeAction: CodeAction, + pub usingnamespace UnionParser(@This()); + }, + .ErrorData = null, + .registration = .{ .method = null, .Options = CodeActionRegistrationOptions }, + }, + // Request to resolve additional information for a given code action.The request's + // parameter is of type {@link CodeAction} the response + // is of type {@link CodeAction} or a Thenable that resolves to such. + .{ + .method = "codeAction/resolve", + .documentation = "Request to resolve additional information for a given code action.The request's\nparameter is of type {@link CodeAction} the response\nis of type {@link CodeAction} or a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = CodeAction, + .Result = CodeAction, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to list project-wide symbols matching the query string given + // by the {@link WorkspaceSymbolParams}. The response is + // of type {@link SymbolInformation SymbolInformation[]} or a Thenable that + // resolves to such. + // + // @since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients + // need to advertise support for WorkspaceSymbols via the client capability + // `workspace.symbol.resolveSupport`. + // + .{ + .method = "workspace/symbol", + .documentation = "A request to list project-wide symbols matching the query string given\nby the {@link WorkspaceSymbolParams}. The response is\nof type {@link SymbolInformation SymbolInformation[]} or a Thenable that\nresolves to such.\n\n@since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients\n need to advertise support for WorkspaceSymbols via the client capability\n `workspace.symbol.resolveSupport`.\n", + .direction = .clientToServer, + .Params = WorkspaceSymbolParams, + .Result = ?union(enum) { + array_of_SymbolInformation: []const SymbolInformation, + array_of_WorkspaceSymbol: []const WorkspaceSymbol, + pub usingnamespace UnionParser(@This()); + }, + .PartialResult = union(enum) { + array_of_SymbolInformation: []const SymbolInformation, + array_of_WorkspaceSymbol: []const WorkspaceSymbol, + pub usingnamespace UnionParser(@This()); + }, + .ErrorData = null, + .registration = .{ .method = null, .Options = WorkspaceSymbolRegistrationOptions }, + }, + // A request to resolve the range inside the workspace + // symbol's location. + // + // @since 3.17.0 + .{ + .method = "workspaceSymbol/resolve", + .documentation = "A request to resolve the range inside the workspace\nsymbol's location.\n\n@since 3.17.0", + .direction = .clientToServer, + .Params = WorkspaceSymbol, + .Result = WorkspaceSymbol, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to provide code lens for the given text document. + .{ + .method = "textDocument/codeLens", + .documentation = "A request to provide code lens for the given text document.", + .direction = .clientToServer, + .Params = CodeLensParams, + .Result = ?[]const CodeLens, + .PartialResult = []const CodeLens, + .ErrorData = null, + .registration = .{ .method = null, .Options = CodeLensRegistrationOptions }, + }, + // A request to resolve a command for a given code lens. + .{ + .method = "codeLens/resolve", + .documentation = "A request to resolve a command for a given code lens.", + .direction = .clientToServer, + .Params = CodeLens, + .Result = CodeLens, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to refresh all code actions + // + // @since 3.16.0 + .{ + .method = "workspace/codeLens/refresh", + .documentation = "A request to refresh all code actions\n\n@since 3.16.0", + .direction = .serverToClient, + .Params = null, + .Result = ?void, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to provide document links + .{ + .method = "textDocument/documentLink", + .documentation = "A request to provide document links", + .direction = .clientToServer, + .Params = DocumentLinkParams, + .Result = ?[]const DocumentLink, + .PartialResult = []const DocumentLink, + .ErrorData = null, + .registration = .{ .method = null, .Options = DocumentLinkRegistrationOptions }, + }, + // Request to resolve additional information for a given document link. The request's + // parameter is of type {@link DocumentLink} the response + // is of type {@link DocumentLink} or a Thenable that resolves to such. + .{ + .method = "documentLink/resolve", + .documentation = "Request to resolve additional information for a given document link. The request's\nparameter is of type {@link DocumentLink} the response\nis of type {@link DocumentLink} or a Thenable that resolves to such.", + .direction = .clientToServer, + .Params = DocumentLink, + .Result = DocumentLink, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request to format a whole document. + .{ + .method = "textDocument/formatting", + .documentation = "A request to format a whole document.", + .direction = .clientToServer, + .Params = DocumentFormattingParams, + .Result = ?[]const TextEdit, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = DocumentFormattingRegistrationOptions }, + }, + // A request to format a range in a document. + .{ + .method = "textDocument/rangeFormatting", + .documentation = "A request to format a range in a document.", + .direction = .clientToServer, + .Params = DocumentRangeFormattingParams, + .Result = ?[]const TextEdit, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = DocumentRangeFormattingRegistrationOptions }, + }, + // A request to format ranges in a document. + // + // @since 3.18.0 + // @proposed + .{ + .method = "textDocument/rangesFormatting", + .documentation = "A request to format ranges in a document.\n\n@since 3.18.0\n@proposed", + .direction = .clientToServer, + .Params = DocumentRangesFormattingParams, + .Result = ?[]const TextEdit, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = DocumentRangeFormattingRegistrationOptions }, + }, + // A request to format a document on type. + .{ + .method = "textDocument/onTypeFormatting", + .documentation = "A request to format a document on type.", + .direction = .clientToServer, + .Params = DocumentOnTypeFormattingParams, + .Result = ?[]const TextEdit, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = DocumentOnTypeFormattingRegistrationOptions }, + }, + // A request to rename a symbol. + .{ + .method = "textDocument/rename", + .documentation = "A request to rename a symbol.", + .direction = .clientToServer, + .Params = RenameParams, + .Result = ?WorkspaceEdit, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = RenameRegistrationOptions }, + }, + // A request to test and perform the setup necessary for a rename. + // + // @since 3.16 - support for default behavior + .{ + .method = "textDocument/prepareRename", + .documentation = "A request to test and perform the setup necessary for a rename.\n\n@since 3.16 - support for default behavior", + .direction = .clientToServer, + .Params = PrepareRenameParams, + .Result = ?PrepareRenameResult, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, + // A request send from the client to the server to execute a command. The request might return + // a workspace edit which the client will apply to the workspace. + .{ + .method = "workspace/executeCommand", + .documentation = "A request send from the client to the server to execute a command. The request might return\na workspace edit which the client will apply to the workspace.", + .direction = .clientToServer, + .Params = ExecuteCommandParams, + .Result = ?LSPAny, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = ExecuteCommandRegistrationOptions }, + }, + // A request sent from the server to the client to modified certain resources. + .{ + .method = "workspace/applyEdit", + .documentation = "A request sent from the server to the client to modified certain resources.", + .direction = .serverToClient, + .Params = ApplyWorkspaceEditParams, + .Result = ApplyWorkspaceEditResult, + .PartialResult = null, + .ErrorData = null, + .registration = .{ .method = null, .Options = null }, + }, +}; diff --git a/src/main.zig b/src/main.zig index db7d2093..e45460fe 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,19 +5,17 @@ const _vm = @import("vm.zig"); const VM = _vm.VM; const RunFlavor = _vm.RunFlavor; const ImportRegistry = _vm.ImportRegistry; -const _parser = @import("parser.zig"); -const Parser = _parser.Parser; -const CompileError = _parser.CompileError; -const CodeGen = @import("codegen.zig").CodeGen; +const Parser = @import("Parser.zig"); +const CodeGen = @import("Codegen.zig"); const _obj = @import("obj.zig"); const ObjString = _obj.ObjString; const ObjTypeDef = _obj.ObjTypeDef; const TypeRegistry = @import("memory.zig").TypeRegistry; -const FunctionNode = @import("node.zig").FunctionNode; +const Ast = @import("Ast.zig"); const BuildOptions = @import("build_options"); const clap = @import("ext/clap/clap.zig"); const GarbageCollector = @import("memory.zig").GarbageCollector; -const MIRJIT = @import("mirjit.zig"); +const JIT = @import("Jit.zig"); const _repl = @import("repl.zig"); const repl = _repl.repl; const printBanner = _repl.printBanner; @@ -32,14 +30,14 @@ fn runFile(allocator: Allocator, file_name: []const u8, args: [][:0]u8, flavor: }; var imports = std.StringHashMap(Parser.ScriptImport).init(allocator); var vm = try VM.init(&gc, &import_registry, flavor); - vm.mir_jit = if (BuildOptions.jit) - MIRJIT.init(&vm) + vm.jit = if (BuildOptions.jit) + JIT.init(&vm) else null; defer { - if (vm.mir_jit != null) { - vm.mir_jit.?.deinit(); - vm.mir_jit = null; + if (vm.jit != null) { + vm.jit.?.deinit(); + vm.jit = null; } } var parser = Parser.init( @@ -52,7 +50,7 @@ fn runFile(allocator: Allocator, file_name: []const u8, args: [][:0]u8, flavor: &gc, &parser, flavor, - if (vm.mir_jit) |*jit| jit else null, + if (vm.jit) |*jit| jit else null, ); defer { codegen.deinit(); @@ -86,53 +84,32 @@ fn runFile(allocator: Allocator, file_name: []const u8, args: [][:0]u8, flavor: var codegen_time: u64 = undefined; var running_time: u64 = undefined; - if (try parser.parse(source, file_name)) |function_node| { + if (try parser.parse(source, file_name)) |ast| { parsing_time = timer.read(); timer.reset(); if (flavor == .Run or flavor == .Test) { - if (try codegen.generate(FunctionNode.cast(function_node).?)) |function| { + if (try codegen.generate(ast)) |function| { codegen_time = timer.read(); timer.reset(); - switch (flavor) { - .Run, .Test => try vm.interpret( - function, - args, - ), - .Fmt => { - var formatted = std.ArrayList(u8).init(allocator); - defer formatted.deinit(); - - try function_node.render(function_node, &formatted.writer(), 0); - - std.debug.print("{s}", .{formatted.items}); - }, - .Ast => { - var json = std.ArrayList(u8).init(allocator); - defer json.deinit(); - - try function_node.toJson(function_node, &json.writer()); - - var without_nl = try std.mem.replaceOwned(u8, allocator, json.items, "\n", " "); - defer allocator.free(without_nl); - - _ = try std.io.getStdOut().write(without_nl); - }, - else => {}, - } + try vm.interpret( + ast, + function, + args, + ); running_time = timer.read(); } else { - return CompileError.Recoverable; + return Parser.CompileError.Recoverable; } if (BuildOptions.show_perf and flavor != .Check and flavor != .Fmt) { - const parsing_ms: f64 = @as(f64, @floatFromInt(parsing_time)) / 1000000; - const codegen_ms: f64 = @as(f64, @floatFromInt(codegen_time)) / 1000000; - const running_ms: f64 = @as(f64, @floatFromInt(running_time)) / 1000000; - const gc_ms: f64 = @as(f64, @floatFromInt(gc.gc_time)) / 1000000; - const jit_ms: f64 = if (vm.mir_jit) |jit| + const parsing_ms = @as(f64, @floatFromInt(parsing_time)) / 1000000; + const codegen_ms = @as(f64, @floatFromInt(codegen_time)) / 1000000; + const running_ms = @as(f64, @floatFromInt(running_time)) / 1000000; + const gc_ms = @as(f64, @floatFromInt(gc.gc_time)) / 1000000; + const jit_ms = if (vm.jit) |jit| @as(f64, @floatFromInt(jit.jit_time)) / 1000000 else 0; @@ -152,32 +129,33 @@ fn runFile(allocator: Allocator, file_name: []const u8, args: [][:0]u8, flavor: ); } } else { - switch (flavor) { - .Run, .Test => unreachable, - .Fmt => { - var formatted = std.ArrayList(u8).init(allocator); - defer formatted.deinit(); - - try function_node.render(function_node, &formatted.writer(), 0); - - std.debug.print("{s}", .{formatted.items}); - }, - .Ast => { - var json = std.ArrayList(u8).init(allocator); - defer json.deinit(); - - try function_node.toJson(function_node, &json.writer()); - - var without_nl = try std.mem.replaceOwned(u8, allocator, json.items, "\n", " "); - defer allocator.free(without_nl); - - _ = try std.io.getStdOut().write(without_nl); - }, - else => {}, - } + std.debug.print("Formatting and Ast dump is deactivated", .{}); + // switch (flavor) { + // .Run, .Test => unreachable, + // .Fmt => { + // var formatted = std.ArrayList(u8).init(allocator); + // defer formatted.deinit(); + // + // try function_node.render(function_node, &formatted.writer(), 0); + // + // std.debug.print("{s}", .{formatted.items}); + // }, + // .Ast => { + // var json = std.ArrayList(u8).init(allocator); + // defer json.deinit(); + // + // try function_node.toJson(function_node, &json.writer()); + // + // var without_nl = try std.mem.replaceOwned(u8, allocator, json.items, "\n", " "); + // defer allocator.free(without_nl); + // + // _ = try std.io.getStdOut().write(without_nl); + // }, + // else => {}, + // } } } else { - return CompileError.Recoverable; + return Parser.CompileError.Recoverable; } } @@ -203,9 +181,15 @@ pub fn main() !void { ); var diag = clap.Diagnostic{}; - var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ - .diagnostic = &diag, - }) catch |err| { + var res = clap.parse( + clap.Help, + ¶ms, + clap.parsers.default, + .{ + .allocator = allocator, + .diagnostic = &diag, + }, + ) catch |err| { // Report useful error and exit diag.report(std.io.getStdErr().writer(), err) catch {}; return err; @@ -313,12 +297,17 @@ test "Testing behavior" { var count: usize = 0; var fail_count: usize = 0; { - var test_dir = try std.fs.cwd().openIterableDir("tests", .{}); + var test_dir = try std.fs.cwd().openDir( + "tests", + .{ + .iterate = true, + }, + ); var it = test_dir.iterate(); while (try it.next()) |file| : (count += 1) { if (file.kind == .file and std.mem.endsWith(u8, file.name, ".buzz")) { - var file_name: []u8 = try allocator.alloc(u8, 6 + file.name.len); + const file_name: []u8 = try allocator.alloc(u8, 6 + file.name.len); defer allocator.free(file_name); std.debug.print("{s}\n", .{file.name}); @@ -343,12 +332,17 @@ test "Testing behavior" { } { - var test_dir = try std.fs.cwd().openIterableDir("tests/compile_errors", .{}); + var test_dir = try std.fs.cwd().openDir( + "tests/compile_errors", + .{ + .iterate = true, + }, + ); var it = test_dir.iterate(); while (try it.next()) |file| : (count += 1) { if (file.kind == .file and std.mem.endsWith(u8, file.name, ".buzz")) { - var file_name: []u8 = try allocator.alloc(u8, 21 + file.name.len); + const file_name: []u8 = try allocator.alloc(u8, 21 + file.name.len); defer allocator.free(file_name); _ = try std.fmt.bufPrint(file_name, "tests/compile_errors/{s}", .{file.name}); @@ -357,7 +351,7 @@ test "Testing behavior" { const reader = test_file.reader(); const first_line = try reader.readUntilDelimiterAlloc(allocator, '\n', std.math.maxInt(usize)); defer allocator.free(first_line); - const arg0 = std.fmt.allocPrintZ(allocator, "{s}/bin/buzz", .{_parser.buzz_prefix()}) catch unreachable; + const arg0 = std.fmt.allocPrintZ(allocator, "{s}/bin/buzz", .{Parser.buzz_prefix()}) catch unreachable; defer allocator.free(arg0); const result = try std.ChildProcess.run( diff --git a/src/memory.zig b/src/memory.zig index 72c0ba1e..d832e688 100644 --- a/src/memory.zig +++ b/src/memory.zig @@ -8,9 +8,9 @@ const dumpStack = @import("disassembler.zig").dumpStack; const BuildOptions = @import("build_options"); const VM = @import("vm.zig").VM; const assert = std.debug.assert; -const Token = @import("token.zig").Token; +const Token = @import("Token.zig"); const buzz_api = @import("buzz_api.zig"); -const Reporter = @import("reporter.zig"); +const Reporter = @import("Reporter.zig"); const Value = _value.Value; const valueToStringAlloc = _value.valueToStringAlloc; @@ -40,7 +40,10 @@ pub const TypeRegistry = struct { registry: std.StringHashMap(*ObjTypeDef), pub fn init(gc: *GarbageCollector) Self { - return .{ .gc = gc, .registry = std.StringHashMap(*ObjTypeDef).init(gc.allocator) }; + return .{ + .gc = gc, + .registry = std.StringHashMap(*ObjTypeDef).init(gc.allocator), + }; } pub fn deinit(self: *Self) void { @@ -62,7 +65,7 @@ pub const TypeRegistry = struct { } } - var type_def_ptr: *ObjTypeDef = try self.gc.allocateObject(ObjTypeDef, type_def); + const type_def_ptr = try self.gc.allocateObject(ObjTypeDef, type_def); if (BuildOptions.debug_placeholders) { std.debug.print("`{s}` @{}\n", .{ type_def_str, @intFromPtr(type_def_ptr) }); @@ -87,7 +90,7 @@ pub const TypeRegistry = struct { pub fn mark(self: *Self) !void { var it = self.registry.iterator(); while (it.next()) |kv| { - try self.gc.markObj(kv.value_ptr.*.toObj()); + try self.gc.markObj(@constCast(kv.value_ptr.*).toObj()); } } }; @@ -212,7 +215,7 @@ pub const GarbageCollector = struct { try self.collectGarbage(); } - var allocated = try self.allocator.create(T); + const allocated = try self.allocator.create(T); if (BuildOptions.gc_debug) { std.debug.print("Allocated @{} {}\n", .{ @intFromPtr(allocated), T }); @@ -242,10 +245,10 @@ pub const GarbageCollector = struct { pub fn allocateObject(self: *Self, comptime T: type, data: T) !*T { // var before: usize = self.bytes_allocated; - var obj: *T = try self.allocate(T); + const obj: *T = try self.allocate(T); obj.* = data; - var object: *Obj = switch (T) { + const object: *Obj = switch (T) { ObjString => ObjString.toObj(obj), ObjTypeDef => ObjTypeDef.toObj(obj), ObjUpValue => ObjUpValue.toObj(obj), @@ -305,7 +308,7 @@ pub const GarbageCollector = struct { } fn addObject(self: *Self, obj: *Obj) !void { - var new_node = try self.allocator.create(std.TailQueue(*Obj).Node); + const new_node = try self.allocator.create(std.TailQueue(*Obj).Node); new_node.* = .{ .data = obj, }; @@ -314,7 +317,7 @@ pub const GarbageCollector = struct { } pub fn allocateString(self: *Self, chars: []const u8) !*ObjString { - var string: *ObjString = try allocateObject( + const string: *ObjString = try allocateObject( self, ObjString, ObjString{ .string = chars }, @@ -330,8 +333,8 @@ pub const GarbageCollector = struct { return interned; } - var copy: []u8 = try self.allocateMany(u8, chars.len); - std.mem.copy(u8, copy, chars); + const copy: []u8 = try self.allocateMany(u8, chars.len); + std.mem.copyForwards(u8, copy, chars); if (BuildOptions.gc_debug) { std.debug.print("Allocated slice {*} `{s}`\n", .{ copy, copy }); @@ -504,7 +507,7 @@ pub const GarbageCollector = struct { switch (obj.obj_type) { .String => { - var obj_string = ObjString.cast(obj).?; + const obj_string = ObjString.cast(obj).?; // Remove it from interned strings _ = self.strings.remove(obj_string.string); @@ -550,7 +553,7 @@ pub const GarbageCollector = struct { free(self, ObjTypeDef, obj_typedef); }, .UpValue => { - var obj_upvalue = ObjUpValue.cast(obj).?; + const obj_upvalue = ObjUpValue.cast(obj).?; if (obj_upvalue.closed) |value| { if (value.isObj()) { try freeObj(self, value.obj()); @@ -634,10 +637,12 @@ pub const GarbageCollector = struct { var obj_fiber = ObjFiber.cast(obj).?; obj_fiber.fiber.deinit(); + self.allocator.destroy(obj_fiber.fiber); + free(self, ObjFiber, obj_fiber); }, .ForeignContainer => { - var obj_foreignstruct = ObjForeignContainer.cast(obj).?; + const obj_foreignstruct = ObjForeignContainer.cast(obj).?; self.freeMany(u8, obj_foreignstruct.data); @@ -657,7 +662,7 @@ pub const GarbageCollector = struct { pub fn markFiber(self: *Self, fiber: *Fiber) !void { var current_fiber: ?*Fiber = fiber; while (current_fiber) |ufiber| { - try self.markObj(ufiber.type_def.toObj()); + try self.markObj(@constCast(ufiber.type_def.toObj())); // Mark main fiber if (BuildOptions.gc_debug) { std.debug.print("MARKING STACK OF FIBER @{}\n", .{@intFromPtr(ufiber)}); @@ -721,7 +726,7 @@ pub const GarbageCollector = struct { { var it = self.objfiber_memberDefs.iterator(); while (it.next()) |umember| { - try self.markObj(umember.value_ptr.*.toObj()); + try self.markObj(@constCast(umember.value_ptr.*.toObj())); } } @@ -736,7 +741,7 @@ pub const GarbageCollector = struct { { var it = self.objpattern_memberDefs.iterator(); while (it.next()) |kv| { - try self.markObj(kv.value_ptr.*.toObj()); + try self.markObj(@constCast(kv.value_ptr.*.toObj())); } } @@ -751,7 +756,7 @@ pub const GarbageCollector = struct { { var it = self.objstring_memberDefs.iterator(); while (it.next()) |kv| { - try self.markObj(kv.value_ptr.*.toObj()); + try self.markObj(@constCast(kv.value_ptr.*.toObj())); } } @@ -793,6 +798,14 @@ pub const GarbageCollector = struct { if (BuildOptions.gc_debug) { std.debug.print("DONE MARKING GLOBALS OF VM @{}\n", .{@intFromPtr(vm)}); } + + // Mark ast constant values (some are only referenced by the JIT so might be collected before) + // TODO: does this takes too long or are we saved by vertue of MultiArrayList? + for (vm.current_ast.nodes.items(.value)) |valueOpt| { + if (valueOpt) |value| { + try self.markValue(value); + } + } } fn traceReference(self: *Self) !void { @@ -808,7 +821,7 @@ pub const GarbageCollector = struct { } fn sweep(self: *Self, mode: Mode) !void { - var swept: usize = self.bytes_allocated; + const swept: usize = self.bytes_allocated; var obj_count: usize = 0; var obj_node: ?*std.TailQueue(*Obj).Node = self.objects.first; @@ -829,7 +842,7 @@ pub const GarbageCollector = struct { obj_node = node.next; } else { - var unreached: *Obj = node.data; + const unreached: *Obj = node.data; obj_node = node.next; self.objects.remove(node); diff --git a/src/node.zig b/src/node.zig deleted file mode 100644 index ec047ec2..00000000 --- a/src/node.zig +++ /dev/null @@ -1,8502 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const mem = std.mem; -const Allocator = mem.Allocator; -const assert = std.debug.assert; - -const _obj = @import("obj.zig"); -const _token = @import("token.zig"); -const _value = @import("value.zig"); -const _codegen = @import("codegen.zig"); -const _parser = @import("parser.zig"); -const _chunk = @import("chunk.zig"); -const disassembler = @import("disassembler.zig"); -const BuildOptions = @import("build_options"); -const VM = @import("vm.zig").VM; -const GarbageCollector = @import("memory.zig").GarbageCollector; -const global_allocator = @import("buzz_api.zig").allocator; -const ZigType = @import("zigtypes.zig").Type; -const FFI = @import("ffi.zig"); - -const disassembleChunk = disassembler.disassembleChunk; -const ObjTypeDef = _obj.ObjTypeDef; -const ObjString = _obj.ObjString; -const ObjNative = _obj.ObjNative; -const ObjFunction = _obj.ObjFunction; -const ObjObject = _obj.ObjObject; -const ObjList = _obj.ObjList; -const ObjEnum = _obj.ObjEnum; -const ObjEnumInstance = _obj.ObjEnumInstance; -const ObjPattern = _obj.ObjPattern; -const ObjMap = _obj.ObjMap; -const FunctionType = ObjFunction.FunctionType; -const copyObj = _obj.copyObj; -const Value = _value.Value; -const valueToString = _value.valueToString; -const Token = _token.Token; -const TokenType = _token.TokenType; -const CodeGen = _codegen.CodeGen; -const Parser = _parser.Parser; -const Frame = _codegen.Frame; -const Local = _parser.Local; -const Global = _parser.Global; -const UpValue = _parser.UpValue; -const OpCode = _chunk.OpCode; - -pub const GenError = error{NotConstant}; - -pub const ParsedArg = struct { - name: ?Token, - arg: *ParseNode, -}; - -pub const ParseNodeType = enum(u8) { - Function, - Enum, - VarDeclaration, - FunDeclaration, - ObjectDeclaration, - ProtocolDeclaration, - Binary, - Unary, - Subscript, - Unwrap, - ForceUnwrap, - Is, - As, - Expression, - Grouping, - NamedVariable, - Integer, - Float, - String, - StringLiteral, - Pattern, - Boolean, - Null, - Void, - List, - Map, - Dot, - ObjectInit, - Throw, - Break, - Continue, - Call, - AsyncCall, - Resume, - Resolve, - Yield, - If, - Block, - Return, - For, - ForEach, - DoUntil, - While, - Export, - Import, - Try, - Range, - Zdef, - TypeExpression, - TypeOfExpression, - GenericResolve, -}; - -pub const RenderError = Allocator.Error || std.fmt.BufPrintError; - -pub const ParseNode = struct { - const Self = @This(); - - node_type: ParseNodeType, - // If null, either its a statement or its a reference to something unknown that should ultimately raise a compile error - type_def: ?*ObjTypeDef = null, - location: Token = undefined, - end_location: Token = undefined, - // Wether optional jumps must be patch before generate this node bytecode - patch_opt_jumps: bool = false, - docblock: ?Token = null, - - // Does this node closes a scope - ends_scope: ?std.ArrayList(OpCode) = null, - - // Because of https://github.com/ziglang/zig/issues/12325 we can't reference ParseNode in any of those signatures - toJson: *const fn (*anyopaque, *const std.ArrayList(u8).Writer) RenderError!void = stringify, - toByteCode: *const fn (*anyopaque, *anyopaque, ?*std.ArrayList(usize)) anyerror!?*ObjFunction, - toValue: *const fn (*anyopaque, *GarbageCollector) anyerror!Value, - render: *const fn (*anyopaque, *const std.ArrayList(u8).Writer, usize) RenderError!void, - isConstant: *const fn (*anyopaque) bool, - - // If returns true, node must be skipped - pub fn synchronize(self: *Self, codegen: *CodeGen) bool { - if (codegen.reporter.panic_mode) { - switch (self.node_type) { - .ObjectDeclaration, - .Enum, - .FunDeclaration, - .If, - .While, - .DoUntil, - .For, - .ForEach, - .Return, - .VarDeclaration, - .Throw, - .Break, - .Continue, - .Export, - .Import, - => { - codegen.reporter.panic_mode = false; - return false; - }, - else => {}, - } - - return true; - } - - return false; - } - - fn patchOptJumps(self: *Self, codegen: *CodeGen) !void { - if (self.patch_opt_jumps) { - assert(codegen.opt_jumps != null); - - // Hope over OP_POP if actual value - const njump: usize = try codegen.emitJump(self.location, .OP_JUMP); - - for (codegen.opt_jumps.?.items) |jump| { - codegen.patchJump(jump); - } - // If aborted by a null optional, will result in null on the stack - try codegen.emitOpCode(self.location, .OP_POP); - - codegen.patchJump(njump); - - codegen.opt_jumps.?.deinit(); - codegen.opt_jumps = null; - } - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = @as(*Self, @ptrCast(node)); - - try out.writeAll("\"type_def\": \""); - if (self.type_def) |type_def| { - try type_def.toString(out); - } else { - try out.writeAll("N/A"); - } - - // TODO: assumes a token is on one line, right now it's completely true except for a .Docblock token - const end_col: usize = if (self.location.eql(self.end_location)) self.location.column + self.location.lexeme.len else self.end_location.column; - - try out.print( - "\", \"location\": {{ \"start_line\": {}, \"start_column\": {}, \"end_line\": {}, \"end_column\": {}, \"script\": \"{s}\"}}", - .{ - self.location.line, - self.location.column, - self.end_location.line, - end_col, - self.location.script_name, - }, - ); - - if (self.docblock != null) { - var escaped = try escape(global_allocator, self.docblock.?.literal_string.?); - defer escaped.deinit(); - try out.print(", \"docblock\": \"{s}\"", .{escaped.items}); - } - } - - fn endScope(self: *Self, codegen: *CodeGen) anyerror!void { - if (self.ends_scope) |closing| { - for (closing.items) |op| { - try codegen.emitOpCode(self.location, op); - } - } - } - - pub fn deinit(self: *Self) void { - if (self.ends_scope) { - self.ends_scope.?.deinit(); - } - } -}; - -pub const ExpressionNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Expression, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - expression: *ParseNode, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.expression.isConstant(self.expression); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - return self.expression.toValue(self.expression, gc); - } - - return GenError.NotConstant; - } - - fn isLoneExpression(self: *Self) bool { - // zig fmt: off - return (self.expression.node_type != .NamedVariable or NamedVariableNode.cast(self.expression).?.value == null) - and (self.expression.node_type != .Subscript or SubscriptNode.cast(self.expression).?.value == null) - and (self.expression.node_type != .Dot or DotNode.cast(self.expression).?.value == null) - and self.expression.type_def != null - and self.expression.type_def.?.def_type != .Void; - // zig fmt: on - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - _ = try self.expression.toByteCode(self.expression, codegen, breaks); - - try codegen.emitOpCode(node.location, .OP_POP); - - if (codegen.flavor != .Repl and self.isLoneExpression() and self.expression.type_def.?.def_type != .Placeholder) { - const type_def_str = self.expression.type_def.?.toStringAlloc(codegen.gc.allocator) catch unreachable; - defer type_def_str.deinit(); - - codegen.reporter.warnFmt( - .discarded_value, - node.location, - "Discarded value of type `{s}`", - .{ - type_def_str.items, - }, - ); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.print("{{\"node\": \"Expression\", ", .{}); - - try ParseNode.stringify(node, out); - - try out.writeAll(",\"expression\": "); - - try self.expression.toJson(self.expression, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - // Its a statement, should be on its own line - try out.writeByteNTimes(' ', depth * 4); - try self.expression.render(self.expression, out, depth); - try out.writeAll(";\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Expression) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const GenericResolveNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .GenericResolve, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - expression: *ParseNode, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.expression.isConstant(self.expression); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - return self.expression.toValue(self.expression, gc); - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - switch (node.type_def.?.def_type) { - .Function => { - const function_type = node.type_def.?.resolved_type.?.Function; - - if (function_type.generic_types.count() > 0 and (function_type.resolved_generics == null or function_type.resolved_generics.?.len < function_type.generic_types.count())) { - codegen.reporter.reportErrorFmt( - .generic_type, - node.location, - "Missing generic types. Expected {} got {}.", - .{ - function_type.generic_types.count(), - if (function_type.resolved_generics == null) 0 else function_type.resolved_generics.?.len, - }, - ); - } else if (function_type.resolved_generics != null and function_type.resolved_generics.?.len > function_type.generic_types.count()) { - codegen.reporter.reportErrorFmt( - .generic_type, - node.location, - "Too many generic types. Expected {} got {}.", - .{ - function_type.generic_types.count(), - if (function_type.resolved_generics == null) 0 else function_type.resolved_generics.?.len, - }, - ); - } - }, - .Object => { - const object_type = node.type_def.?.resolved_type.?.Object; - - if (object_type.generic_types.count() > 0 and (object_type.resolved_generics == null or object_type.resolved_generics.?.len < object_type.generic_types.count())) { - codegen.reporter.reportErrorFmt( - .generic_type, - node.location, - "Missing generic types. Expected {} got {}.", - .{ - object_type.generic_types.count(), - if (object_type.resolved_generics == null) 0 else object_type.resolved_generics.?.len, - }, - ); - } else if (object_type.resolved_generics != null and object_type.resolved_generics.?.len > object_type.generic_types.count()) { - codegen.reporter.reportErrorFmt( - .generic_type, - node.location, - "Too many generic types. Expected {} got {}.", - .{ - object_type.generic_types.count(), - if (object_type.resolved_generics == null) 0 else object_type.resolved_generics.?.len, - }, - ); - } - }, - else => { - const type_def_str = node.type_def.?.toStringAlloc(codegen.gc.allocator) catch unreachable; - defer type_def_str.deinit(); - - codegen.reporter.reportErrorFmt( - .generic_type, - node.location, - "Type `{s}` does not support generic types", - .{ - type_def_str.items, - }, - ); - }, - } - - _ = try self.expression.toByteCode(self.expression, codegen, breaks); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.print("{{\"node\": \"GenericResolve\", ", .{}); - - try ParseNode.stringify(node, out); - - try out.writeAll(",\"expression\": "); - - try self.expression.toJson(self.expression, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - // Its a statement, should be on its own line - try out.writeByteNTimes(' ', depth * 4); - try self.expression.render(self.expression, out, depth); - try out.writeAll(";\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .GenericResolve) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const GroupingNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Grouping, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - expression: *ParseNode, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.expression.isConstant(self.expression); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - return self.expression.toValue(self.expression, gc); - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - _ = try self.expression.toByteCode(self.expression, codegen, breaks); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.print("{{\"node\": \"Grouping\", ", .{}); - - try ParseNode.stringify(node, out); - - try out.writeAll(",\"expression\": "); - - try self.expression.toJson(self.expression, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - // Its a statement, should be on its own line - try out.writeAll("("); - try self.expression.render(self.expression, out, depth); - try out.writeAll(")"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Grouping) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const SlotType = enum(u8) { - Local, - UpValue, - Global, -}; - -pub const NamedVariableNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .NamedVariable, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - identifier: Token, - value: ?*ParseNode = null, - slot: usize, - slot_type: SlotType, - slot_constant: bool, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - var get_op: OpCode = undefined; - var set_op: OpCode = undefined; - - switch (self.slot_type) { - .Local => { - get_op = .OP_GET_LOCAL; - set_op = .OP_SET_LOCAL; - }, - .Global => { - get_op = .OP_GET_GLOBAL; - set_op = .OP_SET_GLOBAL; - }, - .UpValue => { - get_op = .OP_GET_UPVALUE; - set_op = .OP_SET_UPVALUE; - }, - } - - if (self.value) |value| { - // Type checking - if (node.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(node.type_def.?.resolved_type.?.Placeholder); - } - - if (!node.type_def.?.eql(value.type_def.?)) { - codegen.reporter.reportTypeCheck( - .assignment_value_type, - node.location, - node.type_def.?, - value.location, - value.type_def.?, - "Bad value type", - ); - } - - _ = try value.toByteCode(value, codegen, breaks); - - try codegen.emitCodeArg(self.node.location, set_op, @intCast(self.slot)); - } else { - try codegen.emitCodeArg(self.node.location, get_op, @intCast(self.slot)); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print( - "{{\"node\": \"NamedVariable\", \"identifier\": \"{s}\", \"slot\": \"{}\", \"slot_type\": \"{}\",", - .{ - if (self.identifier.literal_string) |literal| literal else "unknown", - self.slot, - self.slot_type, - }, - ); - - try ParseNode.stringify(node, out); - - try out.writeAll(",\"value\": "); - - if (self.value) |value| { - try value.toJson(value, out); - } else { - try out.writeAll("null"); - } - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll(self.identifier.lexeme); - if (self.value) |value| { - try out.writeAll(" = "); - try value.render(value, out, depth); - } - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .NamedVariable) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const IntegerNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Integer, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = cnst, - .render = render, - }, - - integer_constant: i32, - - fn cnst(_: *anyopaque) bool { - return true; - } - - fn val(nodePtr: *anyopaque, _: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return Value.fromInteger(self.integer_constant); - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - try codegen.emitConstant(self.node.location, Value.fromInteger(self.integer_constant)); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"Integer\", \"constant\": ", .{}); - - try out.print("{d}, ", .{self.integer_constant}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, _: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.print("{d}", .{self.integer_constant}); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Integer) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const RangeNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Range, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = cnst, - .render = render, - }, - - low: *ParseNode, - hi: *ParseNode, - - fn cnst(_: *anyopaque) bool { - // const node: *ParseNode = @ptrCast( @alignCast( nodePtr)); - // const self = Self.cast(node).?; - - // return self.low.isConstant(self.low) and self.hi.isConstant(self.hi); - - // If true, we'd still need to clone the value - return false; - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - var list = try gc.allocateObject( - ObjList, - _obj.ObjList.init( - gc.allocator, - try gc.type_registry.getTypeDef(.{ - .def_type = .Integer, - }), - ), - ); - - const low = (try self.low.toValue(self.low, gc)).integer(); - const hi = (try self.hi.toValue(self.hi, gc)).integer(); - const delta: i32 = if (low < hi) 1 else -1; - - var i = low; - while ((low < hi and i < hi) or (low >= hi and i > hi)) : (i += delta) { - try list.items.append(Value.fromInteger(i)); - } - - return list.toValue(); - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - const codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - if (node.synchronize(codegen)) { - return null; - } - - // Type checking - if (self.low.type_def == null or self.low.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.low.type_def.?.resolved_type.?.Placeholder); - } - - if (self.hi.type_def == null or self.hi.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.hi.type_def.?.resolved_type.?.Placeholder); - } - - if (self.low.type_def.?.def_type != .Integer) { - codegen.reporter.reportTypeCheck( - .range_type, - null, - try codegen.gc.type_registry.getTypeDef(.{ - .def_type = .Integer, - }), - self.low.location, - self.low.type_def.?, - "Bad low range limit type", - ); - } - - if (self.hi.type_def.?.def_type != .Integer) { - codegen.reporter.reportTypeCheck( - .range_type, - null, - try codegen.gc.type_registry.getTypeDef(.{ - .def_type = .Integer, - }), - self.hi.location, - self.hi.type_def.?, - "Bad high range limit type", - ); - } - - _ = try self.low.toByteCode(self.low, codegen, breaks); - _ = try self.hi.toByteCode(self.hi, codegen, breaks); - - try codegen.emitOpCode(node.location, .OP_RANGE); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"Range\", \"low\": ", .{}); - try self.low.toJson(self.low, out); - try out.print(", \"high\": ", .{}); - try self.hi.toJson(self.hi, out); - try out.print(", ", .{}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, inc: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try self.low.render(self.low, out, inc); - try out.print("..", .{}); - try self.hi.render(self.hi, out, inc); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Range) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const FloatNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Float, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = cnst, - .render = render, - }, - - float_constant: f64, - - fn cnst(_: *anyopaque) bool { - return true; - } - - fn val(nodePtr: *anyopaque, _: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return Value.fromFloat(self.float_constant); - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - try codegen.emitConstant(self.node.location, Value.fromFloat(self.float_constant)); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"Float\", \"constant\": ", .{}); - - try out.print("{d}, ", .{self.float_constant}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, _: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.print("{d}", .{self.float_constant}); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Float) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const BooleanNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Boolean, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = cnts, - .render = render, - }, - - constant: bool, - - fn cnts(_: *anyopaque) bool { - return true; - } - - fn val(nodePtr: *anyopaque, _: *GarbageCollector) anyerror!Value { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - return Value.fromBoolean(Self.cast(node).?.constant); - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - try codegen.emitOpCode(self.node.location, if (self.constant) .OP_TRUE else .OP_FALSE); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"Boolean\", \"constant\": \"{}\", ", .{self.constant}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, _: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - if (self.constant) { - try out.writeAll("true"); - } else { - try out.writeAll("false"); - } - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Boolean) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const StringLiteralNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .StringLiteral, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = cnst, - .render = render, - }, - - constant: *ObjString, - - fn cnst(_: *anyopaque) bool { - return true; - } - - fn val(nodePtr: *anyopaque, _: *GarbageCollector) anyerror!Value { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - return Self.cast(node).?.constant.toValue(); - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - try codegen.emitConstant(self.node.location, self.constant.toValue()); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - var escaped = try escape(global_allocator, self.constant.string); - defer escaped.deinit(); - try out.print("{{\"node\": \"StringLiteral\", \"constant\": \"{s}\", ", .{escaped.items}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, _: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - const multiline = std.mem.indexOf(u8, self.constant.string, "\n") != null; - - if (multiline) { - try out.print("`{s}`", .{self.constant.string}); - } else { - try out.print("\"{s}\"", .{self.constant.string}); - } - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .StringLiteral) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const PatternNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Pattern, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = cnst, - .render = render, - }, - - constant: *ObjPattern, - - fn cnst(_: *anyopaque) bool { - return true; - } - - fn val(nodePtr: *anyopaque, _: *GarbageCollector) anyerror!Value { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - return Self.cast(node).?.constant.toValue(); - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - try codegen.emitConstant(self.node.location, self.constant.toValue()); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - var escaped = try escape(global_allocator, self.constant.source); - defer escaped.deinit(); - try out.print("{{\"node\": \"Pattern\", \"constant\": \"{s}\", ", .{escaped.items}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, _: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.print("_{s}_", .{self.constant.source}); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Pattern) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const StringNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .String, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - // List of nodes that will eventually be converted to strings concatened together - elements: []*ParseNode, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - for (self.elements) |element| { - if (!element.isConstant(element)) { - return false; - } - } - - return true; - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - var list = std.ArrayList(*ObjString).init(gc.allocator); - defer list.deinit(); - - var str_value = std.ArrayList(u8).init(gc.allocator); - var writer = &str_value.writer(); - for (self.elements) |element| { - assert(element.isConstant(element)); - - try valueToString(writer, try element.toValue(element, gc)); - } - - return (try gc.copyString(str_value.items)).toValue(); - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - if (self.elements.len == 0) { - // Push the empty string which is always the constant 0 - try codegen.emitCodeArg(self.node.location, .OP_CONSTANT, 0); - - try node.endScope(codegen); - - return null; - } - - for (self.elements, 0..) |element, index| { - if (element.type_def == null or element.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(element.type_def.?.resolved_type.?.Placeholder); - - continue; - } - - _ = try element.toByteCode(element, codegen, breaks); - if (element.type_def.?.def_type != .String or element.type_def.?.optional) { - try codegen.emitOpCode(self.node.location, .OP_TO_STRING); - } - - if (index >= 1) { - try codegen.emitOpCode(self.node.location, .OP_ADD_STRING); - } - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"String\", \"elements\": ["); - - for (self.elements, 0..) |element, i| { - try element.toJson(element, out); - - if (i < self.elements.len - 1) { - try out.writeAll(","); - } - } - - try out.writeAll("], "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - // A different token would be more efficient but it would complexify everything just for this case - var multiline = false; - for (self.elements) |element| { - if (element.node_type == .StringLiteral and std.mem.indexOf(u8, StringLiteralNode.cast(element).?.constant.string, "\n") != null) { - multiline = true; - break; - } - } - - try out.writeAll(if (multiline) "`" else "\""); - - for (self.elements) |element| { - if (element.node_type == .StringLiteral) { - try out.writeAll(StringLiteralNode.cast(element).?.constant.string); - } else { - // Interpolation - try out.writeAll("{"); - try element.render(element, out, depth); - try out.writeAll("}"); - } - } - - try out.writeAll(if (multiline) "`" else "\""); - } - - pub fn init(allocator: Allocator) Self { - return .{ - .elements = std.ArrayList(*ParseNode).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.elements.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .String) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const NullNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Null, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - fn constant(_: *anyopaque) bool { - return true; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return Value.Null; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - try codegen.emitOpCode(node.location, .OP_NULL); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - try out.writeAll("{\"node\": \"Null\", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(_: *anyopaque, out: *const std.ArrayList(u8).Writer, _: usize) RenderError!void { - try out.writeAll("null"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Null) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const VoidNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Void, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - fn constant(_: *anyopaque) bool { - return true; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return Value.Void; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - try codegen.emitOpCode(node.location, .OP_VOID); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - try out.writeAll("{\"node\": \"Void\", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(_: *anyopaque, out: *const std.ArrayList(u8).Writer, _: usize) RenderError!void { - try out.writeAll("void"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Void) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ListNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .List, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - items: []*ParseNode, - trailing_comma: bool, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - for (self.items) |item| { - // A constant list can't contain other list/maps since they won't be cloned - if (item.node_type == .List or item.node_type == .Map or !item.isConstant(item)) { - return false; - } - } - - return true; - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - assert(node.type_def != null and node.type_def.?.def_type != .Placeholder); - - var list = try gc.allocateObject(ObjList, _obj.ObjList.init(gc.allocator, node.type_def.?)); - - for (self.items) |item| { - try list.items.append(try item.toValue(item, gc)); - } - - return list.toValue(); - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - const item_type = self.node.type_def.?.resolved_type.?.List.item_type; - const list_offset: usize = try codegen.emitList(self.node.location); - - for (self.items) |item| { - if (item.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(item.type_def.?.resolved_type.?.Placeholder); - } else if (!item_type.eql(item.type_def.?)) { - codegen.reporter.reportTypeCheck( - .list_item_type, - node.location, - item_type, - item.location, - item.type_def.?, - "Bad list type", - ); - } else { - _ = try item.toByteCode(item, codegen, breaks); - - try codegen.emitOpCode(item.location, .OP_LIST_APPEND); - } - } - - const list_type_constant: u24 = try codegen.makeConstant(Value.fromObj(node.type_def.?.toObj())); - try codegen.patchList(list_offset, list_type_constant); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"List\", \"items\": ["); - - for (self.items, 0..) |item, i| { - try item.toJson(item, out); - - if (i < self.items.len - 1) { - try out.writeAll(","); - } - } - - try out.writeAll("], "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll("["); - if (!self.trailing_comma) { - try out.writeAll(" "); - } - - if (self.items.len == 0) { - try out.writeAll("<"); - try self.node.type_def.?.resolved_type.?.List.item_type.toStringUnqualified(out); - try out.writeAll(">"); - } - - for (self.items, 0..) |item, i| { - // If trailing comma, means we want all element on its own line - if (self.trailing_comma) { - try out.writeAll("\n"); - try out.writeByteNTimes(' ', (depth + 1) * 4); - } - - try item.render(item, out, depth + 1); - - if (i != self.items.len - 1 or self.trailing_comma) { - try out.writeAll(","); - if (!self.trailing_comma) { - try out.writeAll(" "); - } - } - } - - if (self.trailing_comma) { - try out.writeAll("\n"); - } else { - try out.writeAll(" "); - } - try out.writeAll("]"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .List) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const MapNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Map, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - keys: []*ParseNode, - values: []*ParseNode, - trailing_comma: bool, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - for (self.keys) |key| { - if (key.node_type == .List or key.node_type == .Map or !key.isConstant(key)) { - return false; - } - } - - for (self.values) |value| { - if (value.node_type == .List or value.node_type == .Map or !value.isConstant(value)) { - return false; - } - } - - return true; - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - assert(node.type_def != null and node.type_def.?.def_type != .Placeholder); - - var map = try gc.allocateObject(ObjMap, _obj.ObjMap.init(gc.allocator, node.type_def.?)); - - assert(self.keys.len == self.values.len); - - for (self.keys, 0..) |key, index| { - const value = self.values[index]; - try map.map.put( - try key.toValue(key, gc), - try value.toValue(value, gc), - ); - } - - return map.toValue(); - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - const key_type = self.node.type_def.?.resolved_type.?.Map.key_type; - const value_type = self.node.type_def.?.resolved_type.?.Map.value_type; - - const map_offset: usize = try codegen.emitMap(self.node.location); - - assert(self.keys.len == self.values.len); - - for (self.keys, 0..) |key, i| { - const value = self.values[i]; - - _ = try key.toByteCode(key, codegen, breaks); - _ = try value.toByteCode(value, codegen, breaks); - - try codegen.emitOpCode(self.node.location, .OP_SET_MAP); - - if (key.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(key.type_def.?.resolved_type.?.Placeholder); - } - - if (value.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(value.type_def.?.resolved_type.?.Placeholder); - } - - if (!key_type.eql(key.type_def.?)) { - codegen.reporter.reportTypeCheck( - .map_key_type, - node.location, - key_type, - key.location, - key.type_def.?, - "Bad key type", - ); - } - - if (!value_type.eql(value.type_def.?)) { - codegen.reporter.reportTypeCheck( - .map_value_type, - node.location, - value_type, - value.location, - value.type_def.?, - "Bad value type", - ); - } - } - - const map_type_constant: u24 = try codegen.makeConstant(Value.fromObj(node.type_def.?.toObj())); - try codegen.patchMap(map_offset, map_type_constant); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Map\", \"items\": ["); - - for (self.keys, 0..) |key, i| { - try out.writeAll("{\"key\":"); - - try key.toJson(key, out); - - try out.writeAll(", \"value\": "); - - try self.values[i].toJson(self.values[i], out); - - try out.writeAll("}"); - - if (i < self.keys.len - 1) { - try out.writeAll(","); - } - } - - try out.writeAll("], "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll("{"); - if (!self.trailing_comma) { - try out.writeAll(" "); - } - - if (self.keys.len == 0) { - try out.writeAll("<"); - try self.node.type_def.?.resolved_type.?.Map.key_type.toStringUnqualified(out); - try out.writeAll(", "); - try self.node.type_def.?.resolved_type.?.Map.value_type.toStringUnqualified(out); - try out.writeAll(">"); - } - - for (self.keys, 0..) |key, i| { - // If trailing comma, means we want all element on its own line - if (self.trailing_comma) { - try out.writeAll("\n"); - try out.writeByteNTimes(' ', (depth + 1) * 4); - } - - try key.render(key, out, depth + 1); - try out.writeAll(": "); - try self.values[i].render(self.values[i], out, depth + 1); - - if (i != self.keys.len - 1 or self.trailing_comma) { - try out.writeAll(","); - if (!self.trailing_comma) { - try out.writeAll(" "); - } - } - } - - if (self.trailing_comma) { - try out.writeAll("\n"); - } else { - try out.writeAll(" "); - } - try out.writeAll("}"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Map) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const UnwrapNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Unwrap, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - unwrapped: *ParseNode, - original_type: ?*ObjTypeDef, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.unwrapped.isConstant(self.unwrapped); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - return try self.unwrapped.toValue(self.unwrapped, gc); - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - if (self.original_type == null or self.original_type.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.original_type.?.resolved_type.?.Placeholder); - } - - if (!self.original_type.?.optional) { - codegen.reporter.reportErrorAt( - .optional, - self.unwrapped.location, - "Not an optional.", - ); - } - - _ = try self.unwrapped.toByteCode(self.unwrapped, codegen, breaks); - - try codegen.emitOpCode(self.node.location, .OP_COPY); - try codegen.emitOpCode(self.node.location, .OP_NULL); - try codegen.emitOpCode(self.node.location, .OP_EQUAL); - try codegen.emitOpCode(self.node.location, .OP_NOT); - - const jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - - if (codegen.opt_jumps == null) { - codegen.opt_jumps = std.ArrayList(usize).init(codegen.gc.allocator); - } - try codegen.opt_jumps.?.append(jump); - - try codegen.emitOpCode(self.node.location, .OP_POP); // Pop test result - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Unwrap\", \"unwrapped\": "); - - try self.unwrapped.toJson(self.unwrapped, out); - try out.writeAll(","); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try self.unwrapped.render(self.unwrapped, out, depth); - try out.writeAll("?"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Unwrap) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ForceUnwrapNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .ForceUnwrap, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - unwrapped: *ParseNode, - original_type: ?*ObjTypeDef, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.unwrapped.isConstant(self.unwrapped); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - const value = try self.unwrapped.toValue(self.unwrapped, gc); - - if (value.isNull()) { - return VM.Error.UnwrappedNull; - } - - return value; - } - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - if (self.original_type == null or self.original_type.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.original_type.?.resolved_type.?.Placeholder); - - return null; - } - - if (!self.original_type.?.optional) { - codegen.reporter.reportErrorAt( - .optional, - self.unwrapped.location, - "Not an optional.", - ); - } - - _ = try self.unwrapped.toByteCode(self.unwrapped, codegen, breaks); - - try codegen.emitOpCode(self.node.location, .OP_UNWRAP); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"ForceUnwrap\", \"unwrapped\": "); - - try self.unwrapped.toJson(self.unwrapped, out); - try out.writeAll(","); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try self.unwrapped.render(self.unwrapped, out, depth); - try out.writeAll("!"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .ForceUnwrap) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const IsNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Is, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = cnts, - .render = render, - }, - - left: *ParseNode, - constant: Value, - - fn cnts(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.left.isConstant(self.left); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - const left = try self.left.toValue(self.left, gc); - - return Value.fromBoolean(_value.valueIs(self.constant, left)); - } - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - assert(self.constant.isObj()); - assert(self.constant.obj().obj_type == .Type); - - if (ObjTypeDef.cast(self.constant.obj()).?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(ObjTypeDef.cast(self.constant.obj()).?.resolved_type.?.Placeholder); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - - try codegen.emitCodeArg( - self.node.location, - .OP_CONSTANT, - try codegen.makeConstant(self.constant), - ); - - try codegen.emitOpCode(self.node.location, .OP_IS); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Is\", \"left\": "); - - try self.left.toJson(self.left, out); - - try out.writeAll(", \"constant\": \""); - try valueToString(out, self.constant); - try out.writeAll("\", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try self.left.render(self.left, out, depth); - try out.writeAll(" is "); - try ObjTypeDef.cast(self.constant.obj()).?.toStringUnqualified(out); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Is) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const AsNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .As, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = cnts, - .render = render, - }, - - left: *ParseNode, - constant: Value, - - fn cnts(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.left.isConstant(self.left); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - const left = try self.left.toValue(self.left, gc); - - if (_value.valueIs(self.constant, left)) { - return left; - } - - return _value.Value.Null; - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - assert(self.constant.isObj()); - assert(self.constant.obj().obj_type == .Type); - - if (ObjTypeDef.cast(self.constant.obj()).?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(ObjTypeDef.cast(self.constant.obj()).?.resolved_type.?.Placeholder); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - - try codegen.emitOpCode(self.left.location, .OP_COPY); - try codegen.emitCodeArg(self.node.location, .OP_CONSTANT, try codegen.makeConstant(self.constant)); - try codegen.emitOpCode(self.node.location, .OP_IS); - try codegen.emitOpCode(self.node.location, .OP_NOT); - const jump = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - try codegen.emitOpCode(self.node.location, .OP_POP); - try codegen.emitOpCode(self.node.location, .OP_POP); - try codegen.emitOpCode(self.node.location, .OP_NULL); - const jump_over = try codegen.emitJump(self.node.location, .OP_JUMP); - codegen.patchJump(jump); - try codegen.emitOpCode(self.node.location, .OP_POP); - codegen.patchJump(jump_over); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"AsOpt\", \"left\": "); - - try self.left.toJson(self.left, out); - - try out.writeAll(", \"constant\": \""); - try valueToString(out, self.constant); - try out.writeAll("\", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try self.left.render(self.left, out, depth); - try out.writeAll(" as? "); - try ObjTypeDef.cast(self.constant.obj()).?.toStringUnqualified(out); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .As) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const UnaryNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Unary, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - left: *ParseNode, - operator: TokenType, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.left.isConstant(self.left); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - const value = try self.left.toValue(self.left, gc); - - return switch (self.operator) { - .Bnot => Value.fromInteger(~(if (value.isInteger()) value.integer() else @as(i32, @intFromFloat(value.float())))), - .Bang => Value.fromBoolean(!value.boolean()), - .Minus => number: { - if (value.isInteger()) { - break :number Value.fromInteger(-%value.integer()); - } else { - break :number Value.fromFloat(-value.float()); - } - }, - else => unreachable, - }; - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - if (self.left.type_def == null or self.left.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.left.type_def.?.resolved_type.?.Placeholder); - - return null; - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - - const left_type = self.left.type_def.?; - switch (self.operator) { - .Bnot => { - if (left_type.def_type != .Integer) { - codegen.reporter.reportErrorFmt( - .bitwise_operand_type, - self.left.location, - "Expected type `int`, got `{s}`", - .{(try left_type.toStringAlloc(codegen.gc.allocator)).items}, - ); - } - - try codegen.emitOpCode(self.node.location, .OP_BNOT); - }, - .Bang => { - if (left_type.def_type != .Bool) { - codegen.reporter.reportErrorFmt( - .bitwise_operand_type, - self.left.location, - "Expected type `bool`, got `{s}`", - .{(try left_type.toStringAlloc(codegen.gc.allocator)).items}, - ); - } - - try codegen.emitOpCode(self.node.location, .OP_NOT); - }, - .Minus => { - if (left_type.def_type != .Integer and left_type.def_type != .Float) { - codegen.reporter.reportErrorFmt( - .arithmetic_operand_type, - self.left.location, - "Expected type `int` or `float`, got `{s}`", - .{(try left_type.toStringAlloc(codegen.gc.allocator)).items}, - ); - } - - try codegen.emitOpCode(self.node.location, .OP_NEGATE); - }, - else => unreachable, - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Unary\", \"left\": "); - - try self.left.toJson(self.left, out); - try out.print(", \"operator\": \"{}\", ", .{self.operator}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByte(switch (self.operator) { - .Minus => '-', - .Bang => '!', - .Bnot => '~', - else => unreachable, - }); - try self.left.render(self.left, out, depth); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Unary) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const BinaryNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Binary, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - left: *ParseNode, - right: *ParseNode, - operator: TokenType, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.left.isConstant(self.left) and self.right.isConstant(self.right); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - const left = try self.left.toValue(self.left, gc); - const right = try self.right.toValue(self.right, gc); - const left_f: ?f64 = if (left.isFloat()) left.float() else null; - const right_f: ?f64 = if (right.isFloat()) right.float() else null; - const left_i: ?i32 = if (left.isInteger()) left.integer() else null; - const right_i: ?i32 = if (right.isInteger()) right.integer() else null; - - switch (self.operator) { - .Ampersand => { - return Value.fromInteger((left_i orelse @as(i32, @intFromFloat(left_f.?))) & (right_i orelse @as(i32, @intFromFloat(right_f.?)))); - }, - .Bor => { - return Value.fromInteger((left_i orelse @as(i32, @intFromFloat(left_f.?))) | (right_i orelse @as(i32, @intFromFloat(right_f.?)))); - }, - .Xor => { - return Value.fromInteger((left_i orelse @as(i32, @intFromFloat(left_f.?))) ^ (right_i orelse @as(i32, @intFromFloat(right_f.?)))); - }, - .ShiftLeft => { - const b: i32 = right_i orelse @intFromFloat(right_f.?); - - if (b < 0) { - if (b * -1 > std.math.maxInt(u6)) { - return Value.fromInteger(0); - } - - return Value.fromInteger( - (left_i orelse @as(i32, @intFromFloat(left_f.?))) >> @as(u5, @truncate(@as(u64, @intCast(b * -1)))), - ); - } else { - if (b > std.math.maxInt(u6)) { - return Value.fromInteger(0); - } - - return Value.fromInteger( - (left_i orelse @as(i32, @intFromFloat(left_f.?))) << @as(u5, @truncate(@as(u64, @intCast(b)))), - ); - } - }, - .ShiftRight => { - const b: i32 = right_i orelse @intFromFloat(right_f.?); - - if (b < 0) { - if (b * -1 > std.math.maxInt(u6)) { - return Value.fromInteger(0); - } - - return Value.fromInteger((left_i orelse @as(i32, @intFromFloat(left_f.?))) << @as(u5, @truncate(@as(u64, @intCast(b * -1))))); - } else { - if (b > std.math.maxInt(u6)) { - return Value.fromInteger(0); - } - - return Value.fromInteger((left_i orelse @as(i32, @intFromFloat(left_f.?))) >> @as(u5, @truncate(@as(u64, @intCast(b))))); - } - }, - .QuestionQuestion => { - if (left.isNull()) { - return right; - } - - return left; - }, - .Greater => { - if (left_f) |lf| { - if (right_f) |rf| { - return Value.fromBoolean(lf > rf); - } else { - return Value.fromBoolean(lf > @as(f64, @floatFromInt(right_i.?))); - } - } else { - if (right_f) |rf| { - return Value.fromBoolean(@as(f64, @floatFromInt(left_i.?)) > rf); - } else { - return Value.fromBoolean(left_i.? > right_i.?); - } - } - return Value.fromBoolean((left_f orelse left_i.?) > (right_f orelse right_i.?)); - }, - .Less => { - if (left_f) |lf| { - if (right_f) |rf| { - return Value.fromBoolean(lf < rf); - } else { - return Value.fromBoolean(lf < @as(f64, @floatFromInt(right_i.?))); - } - } else { - if (right_f) |rf| { - return Value.fromBoolean(@as(f64, @floatFromInt(left_i.?)) < rf); - } else { - return Value.fromBoolean(left_i.? < right_i.?); - } - } - return Value.fromBoolean((left_f orelse left_i.?) < (right_f orelse right_i.?)); - }, - .GreaterEqual => { - if (left_f) |lf| { - if (right_f) |rf| { - return Value.fromBoolean(lf >= rf); - } else { - return Value.fromBoolean(lf >= @as(f64, @floatFromInt(right_i.?))); - } - } else { - if (right_f) |rf| { - return Value.fromBoolean(@as(f64, @floatFromInt(left_i.?)) >= rf); - } else { - return Value.fromBoolean(left_i.? >= right_i.?); - } - } - return Value.fromBoolean((left_f orelse left_i.?) >= (right_f orelse right_i.?)); - }, - .LessEqual => { - if (left_f) |lf| { - if (right_f) |rf| { - return Value.fromBoolean(lf <= rf); - } else { - return Value.fromBoolean(lf <= @as(f64, @floatFromInt(right_i.?))); - } - } else { - if (right_f) |rf| { - return Value.fromBoolean(@as(f64, @floatFromInt(left_i.?)) <= rf); - } else { - return Value.fromBoolean(left_i.? <= right_i.?); - } - } - return Value.fromBoolean((left_f orelse left_i.?) <= (right_f orelse right_i.?)); - }, - .BangEqual => { - return Value.fromBoolean(!_value.valueEql(left, right)); - }, - .EqualEqual => { - return Value.fromBoolean(_value.valueEql(left, right)); - }, - .Plus => { - const right_s: ?*ObjString = if (right.isObj()) ObjString.cast(right.obj()) else null; - const left_s: ?*ObjString = if (left.isObj()) ObjString.cast(left.obj()) else null; - - const right_l: ?*ObjList = if (right.isObj()) ObjList.cast(right.obj()) else null; - const left_l: ?*ObjList = if (left.isObj()) ObjList.cast(left.obj()) else null; - - const right_m: ?*ObjMap = if (right.isObj()) ObjMap.cast(right.obj()) else null; - const left_m: ?*ObjMap = if (left.isObj()) ObjMap.cast(left.obj()) else null; - - if (right_s != null) { - var new_string: std.ArrayList(u8) = std.ArrayList(u8).init(gc.allocator); - try new_string.appendSlice(left_s.?.string); - try new_string.appendSlice(right_s.?.string); - - return (try gc.copyString(new_string.items)).toValue(); - } else if (right_f != null or left_f != null) { - return Value.fromFloat((right_f orelse @as(f64, @floatFromInt(right_i.?))) + (left_f orelse @as(f64, @floatFromInt(left_i.?)))); - } else if (right_i != null or left_i != null) { - return Value.fromInteger(right_i.? +% left_i.?); - } else if (right_l != null) { - var new_list = std.ArrayList(Value).init(gc.allocator); - try new_list.appendSlice(left_l.?.items.items); - try new_list.appendSlice(right_l.?.items.items); - - var list = try gc.allocateObject( - ObjList, - ObjList{ - .type_def = left_l.?.type_def, - .methods = left_l.?.methods, - .items = new_list, - }, - ); - - return list.toValue(); - } - - // map - var new_map = try left_m.?.map.clone(); - var it = right_m.?.map.iterator(); - while (it.next()) |entry| { - try new_map.put(entry.key_ptr.*, entry.value_ptr.*); - } - - var map = try gc.allocateObject( - ObjMap, - ObjMap{ - .type_def = left_m.?.type_def, - .methods = left_m.?.methods, - .map = new_map, - }, - ); - - return map.toValue(); - }, - .Minus => { - if (right_f != null or left_f != null) { - return Value.fromFloat((right_f orelse @as(f64, @floatFromInt(right_i.?))) - (left_f orelse @as(f64, @floatFromInt(left_i.?)))); - } - - return Value.fromInteger(right_i.? -% left_i.?); - }, - .Star => { - if (right_f != null or left_f != null) { - return Value.fromFloat((right_f orelse @as(f64, @floatFromInt(right_i.?))) * (left_f orelse @as(f64, @floatFromInt(left_i.?)))); - } - - return Value.fromInteger(right_i.? *% left_i.?); - }, - .Slash => { - if (right_f != null or left_f != null) { - return Value.fromFloat((right_f orelse @as(f64, @floatFromInt(right_i.?))) / (left_f orelse @as(f64, @floatFromInt(left_i.?)))); - } - - return Value.fromInteger(@divTrunc(right_i.?, left_i.?)); - }, - .Percent => { - if (right_f != null or left_f != null) { - return Value.fromFloat(@mod((right_f orelse @as(f64, @floatFromInt(right_i.?))), (left_f orelse @as(f64, @floatFromInt(left_i.?))))); - } - - return Value.fromInteger(@mod(right_i.?, left_i.?)); - }, - .And => { - return Value.fromBoolean(left.boolean() and right.boolean()); - }, - .Or => { - return Value.fromBoolean(left.boolean() or right.boolean()); - }, - else => unreachable, - } - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - const left_type = self.left.type_def.?; - const right_type = self.right.type_def.?; - - if (self.left.type_def == null or self.left.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.left.type_def.?.resolved_type.?.Placeholder); - } - - if (self.right.type_def == null or self.right.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.right.type_def.?.resolved_type.?.Placeholder); - } - - switch (self.operator) { - .QuestionQuestion, - .Ampersand, - .Bor, - .Xor, - .ShiftLeft, - .ShiftRight, - .Plus, - .Minus, - .Star, - .Slash, - .Percent, - .And, - .Or, - => { - if (!left_type.eql(right_type)) { - codegen.reporter.reportTypeCheck( - .binary_operand_type, - self.left.location, - left_type, - self.right.location, - right_type, - "Type mismatch", - ); - } - }, - - .Greater, - .Less, - .GreaterEqual, - .LessEqual, - .BangEqual, - .EqualEqual, - => { - // We allow comparison between float and int so raise error if type != and one operand is not a number - if (!left_type.eql(right_type) and ((left_type.def_type != .Integer and left_type.def_type != .Float) or (right_type.def_type != .Integer and right_type.def_type != .Float))) { - codegen.reporter.reportTypeCheck( - .comparison_operand_type, - self.left.location, - left_type, - self.right.location, - right_type, - "Type mismatch", - ); - } - }, - - else => unreachable, - } - - switch (self.operator) { - .QuestionQuestion => { - if (!left_type.optional) { - codegen.reporter.reportErrorAt( - .optional, - self.left.location, - "Not an optional", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - - const end_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_NOT_NULL); - try codegen.emitOpCode(self.node.location, .OP_POP); - - _ = try self.right.toByteCode(self.right, codegen, breaks); - - codegen.patchJump(end_jump); - }, - .Ampersand => { - // Checking only left operand since we asserted earlier that both operand have the same type - if (left_type.def_type != .Integer) { - codegen.reporter.reportErrorAt( - .bitwise_operand_type, - self.left.location, - "Expected `int`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_BAND); - }, - .Bor => { - // Checking only left operand since we asserted earlier that both operand have the same type - if (left_type.def_type != .Integer) { - codegen.reporter.reportErrorAt( - .bitwise_operand_type, - self.left.location, - "Expected `int`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_BOR); - }, - .Xor => { - // Checking only left operand since we asserted earlier that both operand have the same type - if (left_type.def_type != .Integer) { - codegen.reporter.reportErrorAt( - .bitwise_operand_type, - self.left.location, - "Expected `int`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_XOR); - }, - .ShiftLeft => { - // Checking only left operand since we asserted earlier that both operand have the same type - if (left_type.def_type != .Integer) { - codegen.reporter.reportErrorAt( - .bitwise_operand_type, - self.left.location, - "Expected `int`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_SHL); - }, - .ShiftRight => { - // Checking only left operand since we asserted earlier that both operand have the same type - if (left_type.def_type != .Integer) { - codegen.reporter.reportErrorAt( - .bitwise_operand_type, - self.left.location, - "Expected `int`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_SHR); - }, - .Greater => { - // Checking only left operand since we asserted earlier that both operand have the same type - if (left_type.def_type != .Integer and left_type.def_type != .Float) { - codegen.reporter.reportErrorAt( - .comparison_operand_type, - self.left.location, - "Expected `int` or `float`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_GREATER); - }, - .Less => { - if (left_type.def_type != .Integer and left_type.def_type != .Float) { - codegen.reporter.reportErrorAt( - .comparison_operand_type, - self.left.location, - "Expected `int` or `float`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_LESS); - }, - .GreaterEqual => { - if (left_type.def_type != .Integer and left_type.def_type != .Float) { - codegen.reporter.reportErrorAt( - .comparison_operand_type, - self.left.location, - "Expected `int` or `float`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_LESS); - try codegen.emitOpCode(self.node.location, .OP_NOT); - }, - .LessEqual => { - if (left_type.def_type != .Integer and left_type.def_type != .Float) { - codegen.reporter.reportErrorAt( - .comparison_operand_type, - self.left.location, - "Expected `int` or `float`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_GREATER); - try codegen.emitOpCode(self.node.location, .OP_NOT); - }, - .BangEqual => { - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_EQUAL); - try codegen.emitOpCode(self.node.location, .OP_NOT); - }, - .EqualEqual => { - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_EQUAL); - }, - .Plus => { - // zig fmt: off - if (left_type.def_type != .Integer - and left_type.def_type != .Float - and left_type.def_type != .String - and left_type.def_type != .List - and left_type.def_type != .Map) { - codegen.reporter.reportErrorAt(.arithmetic_operand_type,self.left.location, "Expected a `int`, `float`, `str`, list or map.",); - } - // zig fmt: on - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, switch (left_type.def_type) { - .String => .OP_ADD_STRING, - .List => .OP_ADD_LIST, - .Map => .OP_ADD_MAP, - .Integer => .OP_ADD_I, - .Float => .OP_ADD_F, - else => unreachable, - }); - }, - .Minus => { - if (left_type.def_type != .Integer and left_type.def_type != .Float) { - codegen.reporter.reportErrorAt( - .arithmetic_operand_type, - self.left.location, - "Expected `int` or `float`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_SUBTRACT); - }, - .Star => { - if (left_type.def_type != .Integer and left_type.def_type != .Float) { - codegen.reporter.reportErrorAt( - .arithmetic_operand_type, - self.left.location, - "Expected `int` or `float`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_MULTIPLY); - }, - .Slash => { - if (left_type.def_type != .Integer and left_type.def_type != .Float) { - codegen.reporter.reportErrorAt( - .arithmetic_operand_type, - self.left.location, - "Expected `int` or `float`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_DIVIDE); - }, - .Percent => { - if (left_type.def_type != .Integer and left_type.def_type != .Float) { - codegen.reporter.reportErrorAt( - .arithmetic_operand_type, - self.left.location, - "Expected `int` or `float`.", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - _ = try self.right.toByteCode(self.right, codegen, breaks); - try codegen.emitOpCode(self.node.location, .OP_MOD); - }, - .And => { - if (left_type.def_type != .Bool) { - codegen.reporter.reportErrorAt( - .logical_operand_type, - node.location, - "`and` expects operands to be `bool`", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - - const end_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - try codegen.emitOpCode(self.node.location, .OP_POP); - - _ = try self.right.toByteCode(self.right, codegen, breaks); - - codegen.patchJump(end_jump); - }, - .Or => { - if (left_type.def_type != .Bool) { - codegen.reporter.reportErrorAt( - .logical_operand_type, - node.location, - "`and` expects operands to be `bool`", - ); - } - - _ = try self.left.toByteCode(self.left, codegen, breaks); - - const else_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - const end_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP); - - codegen.patchJump(else_jump); - try codegen.emitOpCode(self.node.location, .OP_POP); - - _ = try self.right.toByteCode(self.right, codegen, breaks); - - codegen.patchJump(end_jump); - }, - else => unreachable, - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Binary\", \"left\": "); - - try self.left.toJson(self.left, out); - try out.print(", \"operator\": \"{}\", \"right\": ", .{self.operator}); - try self.right.toJson(self.right, out); - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try self.left.render(self.left, out, depth); - try out.writeAll(switch (self.operator) { - .QuestionQuestion => " ?? ", - .Ampersand => " & ", - .Bor => " \\ ", - .Xor => " ^ ", - .ShiftLeft => " << ", - .ShiftRight => " >> ", - .Greater => " > ", - .Less => " < ", - .GreaterEqual => " >= ", - .LessEqual => " <= ", - .BangEqual => " != ", - .EqualEqual => " == ", - .Plus => " + ", - .Minus => " - ", - .Star => " * ", - .Slash => " / ", - .Percent => " % ", - .And => " and ", - .Or => " or ", - else => unreachable, - }); - try self.right.render(self.right, out, depth); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Binary) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const SubscriptNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Subscript, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - subscripted: *ParseNode, - index: *ParseNode, - value: ?*ParseNode, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.subscripted.isConstant(self.subscripted) and self.index.isConstant(self.index) and self.value == null; - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - const subscriptable = (try self.subscripted.toValue(self.subscripted, gc)).obj(); - const index = try self.index.toValue(self.index, gc); - - switch (subscriptable.obj_type) { - .List => { - const list: *ObjList = ObjList.cast(subscriptable).?; - - const list_index_i: ?i32 = if (index.isInteger()) index.integer() else null; - - if (list_index_i == null or list_index_i.? < 0) { - return VM.Error.OutOfBound; - } - - const list_index: usize = @intCast(list_index_i.?); - - if (list_index < list.items.items.len) { - return list.items.items[list_index]; - } else { - return VM.Error.OutOfBound; - } - }, - .Map => { - const map: *ObjMap = ObjMap.cast(subscriptable).?; - - if (map.map.get(index)) |value| { - return value; - } else { - return Value.Null; - } - }, - .String => { - const str: *ObjString = ObjString.cast(subscriptable).?; - - const str_index_i: ?i32 = if (index.isInteger()) index.integer() else null; - - if (str_index_i == null or str_index_i.? < 0) { - return VM.Error.OutOfBound; - } - - const str_index: usize = @intCast(str_index_i.?); - - if (str_index < str.string.len) { - return (try gc.copyString(&([_]u8{str.string[str_index]}))).toValue(); - } else { - return VM.Error.OutOfBound; - } - }, - else => unreachable, - } - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - _ = try self.subscripted.toByteCode(self.subscripted, codegen, breaks); - - if (self.subscripted.type_def == null or self.subscripted.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.subscripted.type_def.?.resolved_type.?.Placeholder); - } - - if (self.index.type_def == null or self.index.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.index.type_def.?.resolved_type.?.Placeholder); - } - - if (self.value != null and (self.value.?.type_def == null or self.value.?.type_def.?.def_type == .Placeholder)) { - codegen.reporter.reportPlaceholder(self.value.?.type_def.?.resolved_type.?.Placeholder); - } - - var get_code: OpCode = .OP_GET_LIST_SUBSCRIPT; - var set_code: OpCode = .OP_SET_LIST_SUBSCRIPT; - switch (self.subscripted.type_def.?.def_type) { - .String => { - if (self.index.type_def.?.def_type != .Integer) { - codegen.reporter.reportErrorAt( - .subscript_key_type, - self.index.location, - "Expected `int` index.", - ); - } - - get_code = .OP_GET_STRING_SUBSCRIPT; - - assert(self.value == null); - }, - .List => { - if (self.index.type_def.?.def_type != .Integer) { - codegen.reporter.reportErrorAt( - .subscript_key_type, - self.index.location, - "Expected `int` index.", - ); - } - - if (self.value) |value| { - if (!self.subscripted.type_def.?.resolved_type.?.List.item_type.eql(value.type_def.?)) { - codegen.reporter.reportTypeCheck( - .subscript_value_type, - self.subscripted.location, - self.subscripted.type_def.?.resolved_type.?.List.item_type, - value.location, - value.type_def.?, - "Bad value type", - ); - } - } - }, - .Map => { - if (!self.subscripted.type_def.?.resolved_type.?.Map.key_type.eql(self.index.type_def.?)) { - codegen.reporter.reportTypeCheck( - .subscript_key_type, - self.subscripted.location, - self.subscripted.type_def.?.resolved_type.?.Map.key_type, - self.index.location, - self.index.type_def.?, - "Bad key type", - ); - } - - if (self.value) |value| { - if (!self.subscripted.type_def.?.resolved_type.?.Map.value_type.eql(value.type_def.?)) { - codegen.reporter.reportTypeCheck( - .subscript_value_type, - self.subscripted.location, - self.subscripted.type_def.?.resolved_type.?.Map.value_type, - value.location, - value.type_def.?, - "Bad value type", - ); - } - } - - get_code = .OP_GET_MAP_SUBSCRIPT; - set_code = .OP_SET_MAP_SUBSCRIPT; - }, - else => codegen.reporter.reportErrorAt( - .subscriptable, - node.location, - "Not subscriptable.", - ), - } - - _ = try self.index.toByteCode(self.index, codegen, breaks); - - if (self.value) |value| { - _ = try value.toByteCode(value, codegen, breaks); - - try codegen.emitOpCode(self.node.location, set_code); - } else { - try codegen.emitOpCode(self.node.location, get_code); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Subscript\", \"subscripted\": "); - - try self.subscripted.toJson(self.subscripted, out); - - try out.writeAll(", \"index\": "); - - try self.index.toJson(self.index, out); - - try out.writeAll(", "); - - if (self.value) |value| { - try out.writeAll("\"value\": "); - try value.toJson(value, out); - try out.writeAll(", "); - } - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try self.subscripted.render(self.subscripted, out, depth); - try out.writeAll("["); - try self.index.render(self.index, out, depth); - try out.writeAll("]"); - if (self.value) |value| { - try out.writeAll(" = "); - try value.render(value, out, depth); - } - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Subscript) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const TryNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Try, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - body: *ParseNode, - clauses: std.AutoArrayHashMap(*ObjTypeDef, *ParseNode), - clause_identifiers: [][]const u8, - unconditional_clause: ?*ParseNode, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - codegen.current.?.try_should_handle = std.AutoHashMap(*ObjTypeDef, Token).init(codegen.gc.allocator); - defer { - codegen.current.?.try_should_handle.?.deinit(); - codegen.current.?.try_should_handle = null; - } - - // OP_TRY notifies runtime that we're handling error at offset - const try_jump = try codegen.emitJump(node.location, .OP_TRY); - - _ = try self.body.toByteCode(self.body, codegen, breaks); - - // Jump reached if no error was raised - const no_error_jump = try codegen.emitJump(self.body.end_location, .OP_JUMP); - - var exit_jumps = std.ArrayList(usize).init(codegen.gc.allocator); - defer exit_jumps.deinit(); - - codegen.patchTry(try_jump); - var has_unconditional = self.unconditional_clause != null; - for (self.clauses.keys()) |error_type| { - const clause = self.clauses.get(error_type).?; - - if (error_type.eql((try codegen.gc.type_registry.getTypeDef(.{ .def_type = .Any })))) { - has_unconditional = true; - } - - // We assume the error is on top of the stack - try codegen.emitOpCode(clause.location, .OP_COPY); // Copy error value since its argument to the catch clause - try codegen.emitConstant(clause.location, error_type.toValue()); - try codegen.emitOpCode(clause.location, .OP_IS); - // If error type does not match, jump to next catch clause - const next_clause_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - // Pop `is` result - try codegen.emitOpCode(clause.location, .OP_POP); - - // Clause block will pop error value since its declared as a local in it - // We don't catch things is the catch clause - const previous = codegen.current.?.try_should_handle; - codegen.current.?.try_should_handle = null; - _ = try clause.toByteCode(clause, codegen, breaks); - codegen.current.?.try_should_handle = previous; - - // After handling the error, jump over next clauses - try exit_jumps.append(try codegen.emitJump(self.node.location, .OP_JUMP)); - - codegen.patchJump(next_clause_jump); - // Pop `is` result - try codegen.emitOpCode(clause.location, .OP_POP); - } - - if (self.unconditional_clause) |unconditional_clause| { - // pop error because its not a local of this clause - try codegen.emitOpCode(unconditional_clause.location, .OP_POP); - // We don't catch things is the catch clause - const previous = codegen.current.?.try_should_handle; - codegen.current.?.try_should_handle = null; - _ = try unconditional_clause.toByteCode(unconditional_clause, codegen, breaks); - codegen.current.?.try_should_handle = previous; - - try exit_jumps.append(try codegen.emitJump(self.node.location, .OP_JUMP)); - } - - // Tell runtime we're not in a try block anymore - try codegen.emitOpCode(node.location, .OP_TRY_END); - // Uncaught error, throw the error again - try codegen.emitOpCode(node.location, .OP_THROW); - - // Patch exit jumps - for (exit_jumps.items) |exit_jump| { - codegen.patchJump(exit_jump); - } - - codegen.patchJump(no_error_jump); - - // OP_TRY_END notifies runtime that we're not in a try block anymore - try codegen.emitOpCode(node.location, .OP_TRY_END); - - // Did we handle all errors not specified in current function signature? - if (!has_unconditional) { - var it = codegen.current.?.try_should_handle.?.iterator(); - while (it.next()) |kv| { - if (self.clauses.get(kv.key_ptr.*) == null) { - const err_str = try kv.key_ptr.*.toStringAlloc(codegen.gc.allocator); - defer err_str.deinit(); - - codegen.reporter.reportWithOrigin( - .error_not_handled, - node.location, - kv.value_ptr.*, - "Error type `{s}` not handled", - .{err_str.items}, - "can occur here", - ); - } - } - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"Try\", ", .{}); - - try ParseNode.stringify(node, out); - - try out.writeAll(",\"body\": "); - - try self.body.toJson(self.body, out); - - try out.writeAll(",\"unconditional_clause\": "); - - if (self.unconditional_clause) |clause| { - try clause.toJson(clause, out); - } else { - try out.writeAll("null"); - } - - try out.writeAll(",\"clauses\": {"); - // TODO - try out.writeAll("}"); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("try {\n"); - try self.body.render(self.body, out, depth + 1); - try out.writeAll("}"); - - var it = self.clauses.iterator(); - var i: usize = 0; - while (it.next()) |kv| : (i += 1) { - try out.writeAll(" catch ("); - try kv.key_ptr.*.toStringUnqualified(out); - try out.print("{s}) {{\n", .{self.clause_identifiers[i]}); - try kv.value_ptr.*.render(kv.value_ptr.*, out, depth + 1); - try out.writeAll("}"); - } - - if (self.unconditional_clause) |unconditional_clause| { - try out.writeAll(" catch {\n"); - try unconditional_clause.render(unconditional_clause, out, depth + 1); - try out.writeAll("}"); - } - - try out.writeAll("\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Try) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const FunctionNode = struct { - const Self = @This(); - - var next_id: usize = 0; - - node: ParseNode = .{ - .node_type = .Function, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - id: usize, - static: bool = false, - body: ?*BlockNode = null, - arrow_expr: ?*ParseNode = null, - native: ?*ObjNative = null, - test_message: ?[]const u8 = null, - // If true this is the root of a script being imported - import_root: bool = false, - upvalue_binding: std.AutoArrayHashMap(u8, bool), - - // Useful when generating root script bootstrap code - main_slot: ?usize = null, - main_location: ?Token = null, - test_slots: ?[]usize = null, - test_locations: ?[]Token = null, - exported_count: ?usize = null, - - // Set when the function is first generated - // The JIT compiler can then reference it when creating its closure - function: ?*ObjFunction = null, - - pub fn nextId() usize { - Self.next_id += 1; - - return Self.next_id; - } - - fn constant(_: *anyopaque) bool { - // TODO: should be true but requires to codegen the node - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - const codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - const function_type = node.type_def.?.resolved_type.?.Function.function_type; - - if (node.synchronize(codegen)) { - return null; - } - - // If function is a test block and we're not testing/checking/etc. don't waste time generating the node - if (codegen.flavor == .Run and function_type == .Test) { - return null; - } - - var self = Self.cast(node).?; - - var enclosing = codegen.current; - codegen.current = try codegen.gc.allocator.create(Frame); - codegen.current.?.* = Frame{ - .enclosing = enclosing, - .function_node = self, - }; - - var function = try ObjFunction.init( - codegen.gc.allocator, - self, - node.type_def.?.resolved_type.?.Function.name, - ); - - function.type_def = node.type_def.?; - - // Check for any remaining placeholders in function signature - if (function.type_def.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(function.type_def.resolved_type.?.Placeholder); - } else { - const function_def = function.type_def.resolved_type.?.Function; - - if (function_def.return_type.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(function_def.return_type.resolved_type.?.Placeholder); - } - - if (function_def.yield_type.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(function_def.yield_type.resolved_type.?.Placeholder); - } - - var it = function_def.parameters.iterator(); - while (it.next()) |kv| { - if (kv.value_ptr.*.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(kv.value_ptr.*.resolved_type.?.Placeholder); - } - } - - if (function_def.error_types) |error_types| { - for (error_types) |error_type| { - if (error_type.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(error_type.resolved_type.?.Placeholder); - } - } - } - } - - // First chunk constant is the empty string - _ = try function.chunk.addConstant( - null, - Value.fromObj((try codegen.gc.copyString("")).toObj()), - ); - - codegen.current.?.function = try codegen.gc.allocateObject(ObjFunction, function); - - // Can't have both arrow expression and body - assert((self.arrow_expr != null and self.body == null) or (self.arrow_expr == null and self.body != null)); - - // Generate function's body bytecode - if (self.arrow_expr) |arrow_expr| { - _ = try arrow_expr.toByteCode(arrow_expr, codegen, breaks); - try codegen.emitOpCode(arrow_expr.location, .OP_RETURN); - codegen.current.?.return_emitted = true; - } else { - _ = try self.body.?.node.toByteCode(&self.body.?.node, codegen, breaks); - } - - if (function_type != .Extern) { - // If .Script, search for exported globals and return them in a map - if (function_type == .Script or function_type == .ScriptEntryPoint) { - // If top level, search `main` or `test` function(s) and call them - // Then put any exported globals on the stack - if (codegen.flavor != .Test and function_type == .ScriptEntryPoint) { - if (self.main_slot) |main_slot| { - try codegen.emitCodeArg(self.main_location.?, .OP_GET_GLOBAL, @intCast(main_slot)); - try codegen.emitCodeArg(self.main_location.?, .OP_GET_LOCAL, 0); // cli args are always local 0 - try codegen.emitCodeArgs(self.main_location.?, .OP_CALL, 1, 0); - } - } else if (codegen.flavor == .Test and self.test_slots != null) { - // Create an entry point wich runs all `test` - for (self.test_slots.?, 0..) |slot, index| { - try codegen.emitCodeArg(self.test_locations.?[index], .OP_GET_GLOBAL, @intCast(slot)); - try codegen.emitCodeArgs(self.test_locations.?[index], .OP_CALL, 0, 0); - } - } - - // If we're being imported, put all globals on the stack - if (self.import_root) { - if (self.exported_count orelse 0 > 16777215) { - codegen.reporter.reportErrorAt( - .export_count, - node.location, - "Can't export more than 16777215 values.", - ); - } - - var index: usize = 0; - while (index < self.exported_count orelse 0) : (index += 1) { - try codegen.emitCodeArg(node.location, .OP_GET_GLOBAL, @intCast(index)); - } - - try codegen.emitCodeArg(node.location, .OP_EXPORT, @intCast(self.exported_count orelse 0)); - } else { - try codegen.emitOpCode(node.location, .OP_VOID); - try codegen.emitOpCode(node.location, .OP_RETURN); - codegen.current.?.return_emitted = true; - } - } else if (function_type == .Repl and self.body != null and self.body.?.statements.items.len > 0 and self.body.?.statements.getLast().node_type == .Expression) { - // Repl and last expression is a lone statement, remove OP_POP, add OP_RETURN - assert(VM.getCode(codegen.current.?.function.?.chunk.code.pop()) == .OP_POP); - _ = codegen.current.?.function.?.chunk.lines.pop(); - - try codegen.emitReturn(node.location); - } else if (codegen.current.?.function.?.type_def.resolved_type.?.Function.return_type.def_type == .Void and !codegen.current.?.return_emitted) { - // TODO: detect if some branches of the function body miss a return statement - try codegen.emitReturn(node.location); - } else if (!codegen.current.?.return_emitted) { - codegen.reporter.reportErrorAt( - .missing_return, - node.location, - "Missing return statement", - ); - } - } - - var frame = codegen.current.?; - var current_function: *ObjFunction = frame.function.?; - current_function.upvalue_count = @intCast(self.upvalue_binding.count()); - - if (BuildOptions.debug) { - try disassembleChunk(¤t_function.chunk, current_function.name.string); - std.debug.print("\n\n", .{}); - } - - codegen.current = frame.enclosing; - - if (function_type != .ScriptEntryPoint and function_type != .Repl) { - // `extern` functions don't have upvalues - if (function_type == .Extern) { - try codegen.emitCodeArg(node.location, .OP_CONSTANT, try codegen.makeConstant(self.native.?.toValue())); - } else { - try codegen.emitCodeArg(node.location, .OP_CLOSURE, try codegen.makeConstant(current_function.toValue())); - - var it = self.upvalue_binding.iterator(); - while (it.next()) |kv| { - try codegen.emit(node.location, if (kv.value_ptr.*) 1 else 0); - try codegen.emit(node.location, kv.key_ptr.*); - } - } - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - self.function = current_function; - - return current_function; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print( - "{{\"node\": \"Function\", \"type\": \"{}\", \"name\": \"{s}\", ", - .{ - self.node.type_def.?.resolved_type.?.Function.function_type, - self.node.type_def.?.resolved_type.?.Function.name.string, - }, - ); - - if (self.body) |body| { - try out.writeAll("\"body\": "); - - try body.toNode().toJson(body.toNode(), out); - } else if (self.arrow_expr) |expr| { - try out.writeAll("\"arrow_expr\": "); - - try expr.toJson(expr, out); - } - - try out.writeAll(", "); - - if (self.native) |native| { - try out.writeAll("\"native\": \""); - - try valueToString(out, native.toValue()); - - try out.writeAll("\","); - } - - if (self.test_message) |test_message| { - try out.print("\"test_message\": \"{s}\", ", .{test_message}); - } - - const function_def = node.type_def.?.resolved_type.?.Function; - if (function_def.error_types != null and function_def.error_types.?.len > 0) { - try out.writeAll("\"error_types\":["); - for (function_def.error_types.?, 0..) |error_type, i| { - try out.writeAll("\""); - try error_type.toString(out); - try out.writeAll("\""); - if (i < function_def.error_types.?.len - 1) { - try out.writeAll(","); - } - } - try out.writeAll("],"); - } - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - pub fn init(parser: *Parser, function_type: FunctionType, script_name: []const u8, name: ?[]const u8) !Self { - var self = Self{ - .id = Self.nextId(), - .body = try parser.gc.allocator.create(BlockNode), - .upvalue_binding = std.AutoArrayHashMap(u8, bool).init(parser.gc.allocator), - }; - - self.body.?.* = BlockNode.init(parser.gc.allocator); - - const function_name: []const u8 = switch (function_type) { - .EntryPoint => "main", - .ScriptEntryPoint, .Script => name orelse script_name, - .Repl => "REPL", - else => name orelse "???", - }; - - const function_def = ObjFunction.FunctionDef{ - .id = ObjFunction.FunctionDef.nextId(), - .name = try parser.gc.copyString(function_name), - .script_name = try parser.gc.copyString(script_name), - .return_type = try parser.gc.type_registry.getTypeDef(.{ .def_type = .Void }), - .yield_type = try parser.gc.type_registry.getTypeDef(.{ .def_type = .Void }), - .parameters = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), - .defaults = std.AutoArrayHashMap(*ObjString, Value).init(parser.gc.allocator), - .function_type = function_type, - .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), - }; - - const type_def = ObjTypeDef.TypeUnion{ .Function = function_def }; - - self.node.type_def = try parser.gc.type_registry.getTypeDef( - .{ - .def_type = .Function, - .resolved_type = type_def, - }, - ); - - return self; - } - - pub fn deinit(self: Self) void { - self.body.deinit(); - self.default_arguments.deinit(); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - const function_type = node.type_def.?.resolved_type.?.Function.function_type; - - if (self.test_message) |test_message| { - try out.print("\ntest \"{s}\"", .{test_message}); - } else if (self.arrow_expr == null and function_type != .ScriptEntryPoint) { - // No need to print return type for arrow function - try out.writeAll("\n"); - try out.writeByteNTimes(' ', depth * 4); - if (self.static) { - try out.writeAll("static "); - } - try node.type_def.?.toStringUnqualified(out); - } - - if (self.arrow_expr) |arrow_expr| { - try out.writeAll(" -> "); - try arrow_expr.render(arrow_expr, out, depth); - } else if (self.body) |body| { - if (function_type != .ScriptEntryPoint) { - try out.writeAll(" {\n"); - try body.node.render(&body.node, out, depth + 1); - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("}"); - } else { - try body.node.render(&body.node, out, depth); - } - } - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Function) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const YieldNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Yield, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - expression: *ParseNode, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; // self - - const current_function_typedef = codegen.current.?.function_node.node.type_def.?.resolved_type.?.Function; - const current_function_type = current_function_typedef.function_type; - switch (current_function_type) { - .Script, - .ScriptEntryPoint, - .Repl, - .EntryPoint, - .Test, - .Extern, - => codegen.reporter.reportErrorAt(.yield_not_allowed, node.location, "Can't yield here"), - else => {}, - } - - if (node.type_def == null) { - codegen.reporter.reportErrorAt(.unknown, node.location, "Unknown type."); - } else if (node.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(node.type_def.?.resolved_type.?.Placeholder); - } else if (!codegen.current.?.function.?.type_def.resolved_type.?.Function.yield_type.eql(node.type_def.?)) { - codegen.reporter.reportTypeCheck( - .yield_type, - codegen.current.?.function_node.node.location, - codegen.current.?.function.?.type_def.resolved_type.?.Function.yield_type, - node.location, - node.type_def.?, - "Bad yield value", - ); - } - - _ = try self.expression.toByteCode(self.expression, codegen, breaks); - - try codegen.emitOpCode(node.location, .OP_YIELD); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; // self - - try out.writeAll("{\"node\": \"Yield\", \"expression\": "); - - try self.expression.toJson(self.expression, out); - - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll("yield "); - try self.expression.render(self.expression, out, depth); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Yield) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ResolveNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Resolve, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - fiber: *ParseNode, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; // self - - if (self.fiber.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.fiber.type_def.?.resolved_type.?.Placeholder); - - return null; - } - - if (self.fiber.type_def.?.def_type != .Fiber) { - codegen.reporter.reportErrorAt(.fiber, self.fiber.location, "Not a fiber"); - } - - _ = try self.fiber.toByteCode(self.fiber, codegen, breaks); - - try codegen.emitOpCode(node.location, .OP_RESOLVE); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; // self - - try out.writeAll("{\"node\": \"Resolve\", \"fiber\": "); - - try self.fiber.toJson(self.fiber, out); - - try out.writeAll(","); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll("resolve "); - try self.fiber.render(self.fiber, out, depth); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Resolve) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ResumeNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Resume, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - fiber: *ParseNode, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; // self - - if (self.fiber.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.fiber.type_def.?.resolved_type.?.Placeholder); - - return null; - } - - if (self.fiber.type_def.?.def_type != .Fiber) { - codegen.reporter.reportErrorAt(.fiber, self.fiber.location, "Not a fiber"); - } - - _ = try self.fiber.toByteCode(self.fiber, codegen, breaks); - - try codegen.emitOpCode(node.location, .OP_RESUME); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; // self - - try out.writeAll("{\"node\": \"Resume\", \"fiber\": "); - - try self.fiber.toJson(self.fiber, out); - - try out.writeAll(","); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll("resume "); - try self.fiber.render(self.fiber, out, depth); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Resume) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const AsyncCallNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .AsyncCall, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - call: *ParseNode, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; // self - - // Push fiber type as constant (we only need it if the fiber is printed out) - // Should not interfere with local counts since OP_FIBER will consume it right away - try codegen.emitConstant( - node.location, - node.type_def.?.toValue(), - ); - - _ = try self.call.toByteCode(self.call, codegen, breaks); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; // self - - try out.writeAll("{\"node\": \"AsyncCall\", \"call\": "); - - try self.call.toJson(self.call, out); - - try out.writeAll(","); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeAll("&"); - try self.call.render(self.call, out, depth); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .AsyncCall) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const CallNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Call, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - async_call: bool = false, - callee: *ParseNode, - callable_type: ?*ObjTypeDef, - arguments: std.AutoArrayHashMap(*ObjString, *ParseNode), - catch_default: ?*ParseNode = null, - trailing_comma: bool, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - if (self.callee.type_def == null or self.callee.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.callee.type_def.?.resolved_type.?.Placeholder); - } - - // This is not a call but an Enum(value) - if (self.callee.type_def.?.def_type == .Enum) { - if (self.async_call) { - codegen.reporter.reportErrorAt( - .fiber_call_not_allowed, - self.callee.end_location, - "Can't be wrapped in a fiber", - ); - } - - if (self.catch_default != null) { - codegen.reporter.reportErrorAt( - .no_error, - self.callee.end_location, - "Doesn't raise any error", - ); - } - - if (self.arguments.count() > 1) { - codegen.reporter.reportErrorAt( - .enum_argument, - self.callee.end_location, - "Enum instanciation expect only value", - ); - } else if (self.arguments.count() == 0) { - codegen.reporter.reportErrorAt( - .enum_argument, - self.callee.end_location, - "Enum instanciation expect value", - ); - - return null; - } - - const value = self.arguments.get(self.arguments.keys()[0]).?; - - if (value.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(value.type_def.?.resolved_type.?.Placeholder); - } - - _ = try self.callee.toByteCode(self.callee, codegen, breaks); - _ = try value.toByteCode(value, codegen, breaks); - try codegen.emitOpCode(value.location, .OP_GET_ENUM_CASE_FROM_VALUE); - - return null; - } - - // Find out if call is invoke or regular call - var invoked = false; - var invoked_on: ?ObjTypeDef.Type = null; - - if (self.callee.node_type == .Dot) { - const dot = DotNode.cast(self.callee).?; - const field_accessed = dot.callee.type_def; - - if (field_accessed == null or field_accessed.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(field_accessed.?.resolved_type.?.Placeholder); - } - - invoked = field_accessed.?.def_type != .Object; - invoked_on = field_accessed.?.def_type; - } - - if (!invoked and invoked_on == null) { - _ = try self.callee.toByteCode(self.callee, codegen, breaks); - } - - const callee_type = switch (self.callee.node_type) { - .Dot => DotNode.cast(self.callee).?.member_type_def, - else => self.callee.type_def, - }; - - if (callee_type == null) { - codegen.reporter.reportErrorAt( - .undefined, - self.callee.location, - "Callee is not defined", - ); - } else if (callee_type != null and callee_type.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(callee_type.?.resolved_type.?.Placeholder); - - // We know nothing about the function being called, no need to go any further - return null; - } else if (callee_type.?.def_type != .Function) { - codegen.reporter.reportErrorAt( - .callable, - self.node.location, - "Can't be called", - ); - - return null; - } else if (callee_type.?.optional) { - codegen.reporter.reportErrorAt( - .callable, - self.node.location, - "Function maybe null and can't be called", - ); - } - - const function_type = callee_type.?; - const yield_type = function_type.resolved_type.?.Function.yield_type; - - // Function being called and current function should have matching yield type unless the current function is an entrypoint - if (!self.async_call) { - const current_function_typedef = codegen.current.?.function_node.node.type_def.?.resolved_type.?.Function; - const current_function_type = current_function_typedef.function_type; - const current_function_yield_type = current_function_typedef.yield_type; - switch (current_function_type) { - // Event though a function can call a yieldable function without wraping it in a fiber, the function itself could be called in a fiber - .Function, .Method, .Anonymous => { - if (!current_function_yield_type.eql(yield_type)) { - codegen.reporter.reportTypeCheck( - .yield_type, - codegen.current.?.function_node.node.location, - current_function_yield_type, - node.location, - yield_type, - "Bad function yield type", - ); - } - }, - else => {}, - } - } - - // Arguments - const args: std.AutoArrayHashMap(*ObjString, *ObjTypeDef) = function_type.resolved_type.?.Function.parameters; - const defaults = function_type.resolved_type.?.Function.defaults; - const arg_keys = args.keys(); - const arg_count = arg_keys.len; - - var missing_arguments = std.AutoArrayHashMap(*ObjString, usize).init(codegen.gc.allocator); - defer missing_arguments.deinit(); - for (arg_keys, 0..) |arg_name, pindex| { - try missing_arguments.put(arg_name, pindex); - } - - if (self.arguments.count() > args.count()) { - codegen.reporter.reportErrorAt( - .call_arguments, - node.location, - "Too many arguments.", - ); - } - - // First push on the stack arguments has they are parsed - var needs_reorder = false; - for (self.arguments.keys(), 0..) |arg_key, index| { - const argument = self.arguments.get(arg_key).?; - const actual_arg_key = if (index == 0 and std.mem.eql(u8, arg_key.string, "$")) arg_keys[0] else arg_key; - const def_arg_type = args.get(actual_arg_key); - - const ref_index = args.getIndex(actual_arg_key); - if (index != ref_index) { - needs_reorder = true; - } - - // Type check the argument - if (def_arg_type) |arg_type| { - if (argument.type_def == null or argument.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(argument.type_def.?.resolved_type.?.Placeholder); - } else if (!arg_type.eql(argument.type_def.?)) { - codegen.reporter.reportTypeCheck( - .call_argument_type, - self.callee.location, - arg_type, - argument.location, - argument.type_def.?, - "Bad argument type", - ); - } - - _ = missing_arguments.orderedRemove(actual_arg_key); - } else { - codegen.reporter.reportErrorFmt( - .call_arguments, - argument.location, - "Argument `{s}` does not exists.", - .{arg_key.string}, - ); - } - - _ = try argument.toByteCode(argument, codegen, breaks); - } - - // Argument order reference - var arguments_order_ref = std.ArrayList(*ObjString).init(codegen.gc.allocator); - defer arguments_order_ref.deinit(); - try arguments_order_ref.appendSlice(self.arguments.keys()); - - // Push default arguments - if (missing_arguments.count() > 0) { - var tmp_missing_arguments = try missing_arguments.clone(); - defer tmp_missing_arguments.deinit(); - const missing_keys = tmp_missing_arguments.keys(); - for (missing_keys) |missing_key| { - if (defaults.get(missing_key)) |default| { - // TODO: like ObjTypeDef, avoid generating constants multiple time for the same value - try codegen.emitConstant(node.location, default); - try codegen.emitOpCode(node.location, .OP_CLONE); - - try arguments_order_ref.append(missing_key); - _ = missing_arguments.orderedRemove(missing_key); - needs_reorder = true; - } - } - } - - if (missing_arguments.count() > 0) { - var missing = std.ArrayList(u8).init(codegen.gc.allocator); - const missing_writer = missing.writer(); - for (missing_arguments.keys(), 0..) |key, i| { - try missing_writer.print( - "{s}{s}", - .{ - key.string, - if (i < missing_arguments.keys().len - 1) - ", " - else - "", - }, - ); - } - defer missing.deinit(); - codegen.reporter.reportErrorFmt( - .call_arguments, - node.location, - "Missing argument(s): {s}", - .{missing.items}, - ); - } - - // Reorder arguments - if (needs_reorder) { - // Until ordered - while (true) { - var ordered = true; - - for (arguments_order_ref.items, 0..) |arg_key, index| { - const actual_arg_key = if (index == 0 and std.mem.eql(u8, arg_key.string, "$")) args.keys()[0] else arg_key; - const correct_index = args.getIndex(actual_arg_key).?; - - if (correct_index != index) { - ordered = false; - - // TODO: both OP_SWAP args could fit in a 32 bit instruction - try codegen.emitCodeArg(node.location, .OP_SWAP, @intCast(arg_count - index - 1)); - // to where it should be - try codegen.emit(node.location, @intCast(arg_count - correct_index - 1)); - - // Switch it in the reference - var temp = arguments_order_ref.items[index]; - arguments_order_ref.items[index] = arguments_order_ref.items[correct_index]; - arguments_order_ref.items[correct_index] = temp; - - // Stop (so we can take the swap into account) and try again - break; - } - } - - if (ordered) break; - } - } - - // Catch clause - const error_types = function_type.resolved_type.?.Function.error_types; - if (self.catch_default) |catch_default| { - if (error_types == null or error_types.?.len == 0) { - codegen.reporter.reportErrorAt( - .no_error, - node.location, - "Function doesn't raise any error", - ); - } else if (error_types != null) { - if (catch_default.type_def == null or catch_default.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(catch_default.type_def.?.resolved_type.?.Placeholder); - } else { - // Expression - if (!node.type_def.?.eql(catch_default.type_def.?) and !(try node.type_def.?.cloneOptional(&codegen.gc.type_registry)).eql(catch_default.type_def.?)) { - codegen.reporter.reportTypeCheck( - .inline_catch_type, - self.callee.location, - node.type_def.?, - catch_default.location, - catch_default.type_def.?, - "Bad inline catch value type", - ); - } - } - - _ = try catch_default.toByteCode(catch_default, codegen, breaks); - } - } else if (error_types) |errors| { - if (codegen.current.?.enclosing != null and codegen.current.?.function.?.type_def.resolved_type.?.Function.function_type != .Test) { - var handles_any = false; - var not_handled = std.ArrayList(*ObjTypeDef).init(codegen.gc.allocator); - defer not_handled.deinit(); - for (errors) |error_type| { - if (error_type.def_type == .Void) { - continue; - } - - var handled = false; - - if (codegen.current.?.function.?.type_def.resolved_type.?.Function.error_types) |handled_types| { - for (handled_types) |handled_type| { - if (error_type.eql(handled_type)) { - handled = true; - break; - } - - if (handled_type.def_type == .Any) { - handles_any = true; - break; - } - } - } - - if (!handled) { - if (codegen.current.?.try_should_handle != null) { - try codegen.current.?.try_should_handle.?.put(error_type, self.callee.location); - } else { - try not_handled.append(error_type); - } - } - - if (handles_any) { - not_handled.clearAndFree(); - break; - } - } - - for (not_handled.items) |error_type| { - const error_str = try error_type.toStringAlloc(codegen.gc.allocator); - defer error_str.deinit(); - - codegen.reporter.reportErrorFmt( - .error_not_handled, - node.location, - "Error `{s}` is not handled", - .{error_str.items}, - ); - } - } - } - - // This is an async call, create a fiber - if (self.async_call) { - if (!invoked) { - // zig fmt: off - const call_arg_count: u8 = if (!invoked) @as(u8, @intCast( arg_count)) - else - if (invoked_on != null and invoked_on.? != .ObjectInstance and invoked_on.? != .ProtocolInstance) @as(u8, @intCast( arg_count)) + 1 - else @as(u8, @intCast( arg_count)); - // zig fmt: on - - try codegen.emitCodeArgs( - self.node.location, - .OP_FIBER, - call_arg_count, - if (self.catch_default != null) 1 else 0, - ); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } else { - if (invoked) { - try codegen.emitCodeArg( - self.node.location, - .OP_INVOKE_FIBER, - try codegen.identifierConstant(DotNode.cast(self.callee).?.identifier.lexeme), - ); - } - - try codegen.emitTwo( - self.node.location, - if (invoked_on != null and invoked_on.? != .ObjectInstance and invoked_on.? != .ProtocolInstance) @as(u8, @intCast(arg_count)) + 1 else @as(u8, @intCast(self.arguments.count())), - if (self.catch_default != null) 1 else 0, - ); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - } - - // Normal call/invoke - if (invoked) { - // TODO: can it be invoked without callee being a DotNode? - try codegen.emitCodeArg( - self.node.location, - switch (DotNode.cast(self.callee).?.callee.type_def.?.def_type) { - .ObjectInstance, .ProtocolInstance => .OP_INSTANCE_INVOKE, - .String => .OP_STRING_INVOKE, - .Pattern => .OP_PATTERN_INVOKE, - .Fiber => .OP_FIBER_INVOKE, - .List => .OP_LIST_INVOKE, - .Map => .OP_MAP_INVOKE, - else => unexpected: { - assert(codegen.reporter.had_error); - break :unexpected .OP_INSTANCE_INVOKE; - }, - }, - try codegen.identifierConstant(DotNode.cast(self.callee).?.identifier.lexeme), - ); - } - - if (!invoked) { - try codegen.emitCodeArgs( - self.node.location, - .OP_CALL, - @intCast(arguments_order_ref.items.len), - if (self.catch_default != null) 1 else 0, - ); - } else { - try codegen.emitTwo( - self.node.location, - if (invoked_on != null and invoked_on.? != .ObjectInstance and invoked_on.? != .ProtocolInstance) - @as(u8, @intCast(arg_count)) + 1 - else - @as(u8, @intCast(arg_count)), - if (self.catch_default != null) 1 else 0, - ); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Call\""); - - var invoked = false; - var invoked_on = false; - if (self.callee.node_type == .Dot) { - const dot = DotNode.cast(self.callee).?; - const field_accessed = dot.callee.type_def; - - invoked = field_accessed.?.def_type != .Object; - invoked_on = true; - } - - if (!invoked and !invoked_on) { - try out.writeAll(", \"callee\": "); - try self.callee.toJson(self.callee, out); - } - - // FIXME - // try out.writeAll(", \"resolved_generics\": ["); - // for (self.resolved_generics, 0..) |generic, i| { - // try out.writeAll("\""); - // try generic.toString(out); - // try out.writeAll("\""); - - // if (i < self.resolved_generics.len - 1) { - // try out.writeAll(","); - // } - // } - - try out.writeAll(", \"arguments\": ["); - - for (self.arguments.keys(), 0..) |key, i| { - const argument = self.arguments.get(key).?; - - try out.print("{{\"name\": \"{s}\", \"value\": ", .{key.string}); - - try argument.toJson(argument, out); - - try out.writeAll("}"); - - if (i < self.arguments.keys().len - 1) { - try out.writeAll(","); - } - } - - try out.writeAll("], "); - - if (self.catch_default) |default| { - try out.writeAll("\"catch_default\": "); - - try default.toJson(default, out); - - try out.writeAll(","); - } - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - // Find out if call is invoke or regular call - var invoked = false; - var invoked_on: ?ObjTypeDef.Type = null; - - if (self.callee.node_type == .Dot) { - const dot = DotNode.cast(self.callee).?; - const field_accessed = dot.callee.type_def; - - invoked = field_accessed.?.def_type != .Object; - invoked_on = field_accessed.?.def_type; - } - - if (!invoked and invoked_on == null) { - try self.callee.render(self.callee, out, depth); - } - - try out.writeAll("("); - var it = self.arguments.iterator(); - var i: usize = 0; - while (it.next()) |kv| : (i += 1) { - // If trailing comma, means we want all element on its own line - if (self.trailing_comma) { - try out.writeAll("\n"); - try out.writeByteNTimes(' ', (depth + 1) * 4); - } - - if (i > 0) { - try out.print("{s}: ", .{kv.key_ptr.*.string}); - } - - try kv.value_ptr.*.render(kv.value_ptr.*, out, depth + 1); - - if (i < self.arguments.count() - 1 or self.trailing_comma) { - try out.writeAll(","); - if (!self.trailing_comma) { - try out.writeAll(" "); - } - } - } - if (self.trailing_comma) { - try out.writeAll("\n"); - try out.writeByteNTimes(' ', depth * 4); - } - try out.writeAll(")"); - - if (self.catch_default) |catch_default| { - try out.writeAll(" catch "); - try catch_default.render(catch_default, out, depth); - } - } - - pub fn init(allocator: Allocator, callee: *ParseNode) Self { - return Self{ - .callee = callee, - .arguments = std.ArrayList(ParsedArg).init(allocator), - }; - } - - pub fn deinit(self: Self) void { - self.callee.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Call) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const FunDeclarationNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .FunDeclaration, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - function: *FunctionNode, - slot: usize, - slot_type: SlotType, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - // Give the docblock to the function itself - self.function.node.docblock = self.function.node.docblock orelse node.docblock; - - _ = try self.function.node.toByteCode(&self.function.node, codegen, breaks); - - if (self.slot_type == .Global) { - try codegen.emitCodeArg(self.node.location, .OP_DEFINE_GLOBAL, @intCast(self.slot)); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"FunDeclaration\",\"slot_type\": \"{}\",\"function\": ", .{self.slot_type}); - - try self.function.node.toJson(&self.function.node, out); - - try out.writeAll(","); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - try self.function.node.render(&self.function.node, out, depth); - if (self.function.native != null or self.function.arrow_expr != null) { - try out.writeAll(";"); - } - try out.writeAll("\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .FunDeclaration) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const VarDeclarationNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .VarDeclaration, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - name: Token, - value: ?*ParseNode = null, - type_def: *ObjTypeDef, - constant: bool, - slot: usize, - slot_type: SlotType, - expression: bool, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - if (self.value) |value| { - _ = try value.toByteCode(value, codegen, breaks); - - if (value.type_def == null or value.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(value.type_def.?.resolved_type.?.Placeholder); - } else if (self.type_def.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.type_def.resolved_type.?.Placeholder); - } else if (!(try self.type_def.toInstance(codegen.gc.allocator, &codegen.gc.type_registry)).eql(value.type_def.?) and !(try (try self.type_def.toInstance(codegen.gc.allocator, &codegen.gc.type_registry)).cloneNonOptional(&codegen.gc.type_registry)).eql(value.type_def.?)) { - codegen.reporter.reportTypeCheck( - .assignment_value_type, - node.location, - try self.type_def.toInstance(codegen.gc.allocator, &codegen.gc.type_registry), - value.location, - value.type_def.?, - "Wrong variable type", - ); - } - } else { - try codegen.emitOpCode(self.node.location, .OP_NULL); - } - - if (self.slot_type == .Global) { - try codegen.emitCodeArg(self.node.location, .OP_DEFINE_GLOBAL, @intCast(self.slot)); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print( - "{{\"node\": \"VarDeclaration\", \"name\": \"{s}\", \"constant\": {}, \"var_type\": \"", - .{ - self.name.lexeme, - self.constant, - }, - ); - - try self.type_def.toString(out); - - try out.print(" @{}\", ", .{@intFromPtr(self.type_def)}); - - if (self.value) |value| { - try out.writeAll("\"value\": "); - - try value.toJson(value, out); - - try out.writeAll(", "); - } - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - // $test#X is a test block, only render its value - if (std.mem.startsWith(u8, self.name.lexeme, "$test")) { - std.debug.assert(self.value != null); - - try self.value.?.render(self.value.?, out, depth); - - return; - } - - if (!self.expression) { - try out.writeByteNTimes(' ', depth * 4); - } - - if (self.constant) { - try out.writeAll("const "); - } - - try self.type_def.toStringUnqualified(out); - try out.print(" {s}", .{self.name.lexeme}); - - if (self.value) |value| { - try out.writeAll(" = "); - try value.render(value, out, depth); - } - - if (!self.expression) { - try out.writeAll(";\n"); - } - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .VarDeclaration) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const EnumNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Enum, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - slot: usize, - cases: std.ArrayList(*ParseNode), - picked: std.ArrayList(bool), - case_type_picked: bool, - - fn constant(_: *anyopaque) bool { - return false; - } - - pub fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - if (node.type_def.?.resolved_type.?.Enum.value) |enum_| { - return enum_.toValue(); - } - - var enum_ = try gc.allocateObject( - ObjEnum, - ObjEnum.init(gc.allocator, node.type_def.?), - ); - - for (self.cases.items) |case| { - try enum_.cases.append(try case.toValue(case, gc)); - } - - node.type_def.?.resolved_type.?.Enum.value = enum_; - - return enum_.toValue(); - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - const enum_type = node.type_def.?.resolved_type.?.Enum.enum_type; - - if (enum_type.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(enum_type.resolved_type.?.Placeholder); - - return null; - } - - switch (enum_type.def_type) { - .String, .Integer, .Float => {}, - else => { - codegen.reporter.reportErrorAt(.syntax, node.location, "Type not allowed as enum value"); - return null; - }, - } - - for (self.cases.items) |case| { - if (case.type_def == null or case.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(case.type_def.?.resolved_type.?.Placeholder); - } else if (!((try enum_type.toInstance(codegen.gc.allocator, &codegen.gc.type_registry))).eql(case.type_def.?)) { - codegen.reporter.reportTypeCheck( - .enum_case_type, - node.location, - (try enum_type.toInstance(codegen.gc.allocator, &codegen.gc.type_registry)), - case.location, - case.type_def.?, - "Bad enum case type", - ); - } - } - - // Since an enum contains only constant values we can make the constant right away - try codegen.emitCodeArg( - self.node.location, - .OP_CONSTANT, - try codegen.makeConstant( - try val(self.toNode(), codegen.gc), - ), - ); - try codegen.emitCodeArg(self.node.location, .OP_DEFINE_GLOBAL, @intCast(self.slot)); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Enum\", \"cases\": ["); - - for (self.cases.items, 0..) |case, i| { - try case.toJson(case, out); - if (i < self.cases.items.len - 1) { - try out.writeAll(","); - } - } - - try out.writeAll("], "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - const enum_def = node.type_def.?.resolved_type.?.Enum; - - try out.writeByteNTimes(' ', depth * 4); - - try out.writeAll("enum"); - if (self.case_type_picked) { - try out.writeAll("("); - try enum_def.enum_type.toStringUnqualified(out); - try out.writeAll(")"); - } - try out.print(" {s} {{\n", .{enum_def.name.string}); - for (enum_def.cases.items, 0..) |case, i| { - try out.writeByteNTimes(' ', (depth + 1) * 4); - try out.writeAll(case); - - if (self.picked.items[i]) { - try out.writeAll(" = "); - try self.cases.items[i].render(self.cases.items[i], out, depth + 1); - } - - try out.writeAll(",\n"); - } - - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("}\n"); - } - - pub fn init(allocator: Allocator) Self { - return Self{ - .cases = std.ArrayList(*ParseNode).init(allocator), - .picked = std.ArrayList(bool).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.cases.deinit(); - self.picked.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Enum) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ThrowNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Throw, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - error_value: *ParseNode, - unconditional: bool, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - const codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - if (self.unconditional) { - codegen.current.?.return_emitted = true; - } - - assert(self.error_value.type_def != null); - if (self.error_value.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.error_value.type_def.?.resolved_type.?.Placeholder); - } else { - const current_error_types = codegen.current.?.function.?.type_def.resolved_type.?.Function.error_types; - - var found_match = false; - if (current_error_types != null) { - for (current_error_types.?) |error_type| { - if (error_type.eql(self.error_value.type_def.?)) { - found_match = true; - break; - } - } - } - - if (!found_match) { - if (codegen.current.?.try_should_handle != null) { - // In a try catch remember to check that we handle that error when finishing parsing the try-catch - try codegen.current.?.try_should_handle.?.put(self.error_value.type_def.?, node.location); - } else { - // Not in a try-catch and function signature does not expect this error type - const error_str = try self.error_value.type_def.?.toStringAlloc(codegen.gc.allocator); - defer error_str.deinit(); - - codegen.reporter.reportErrorFmt( - .unexpected_error_type, - node.location, - "Error type `{s}` not expected", - .{error_str.items}, - ); - } - } - } - - _ = try self.error_value.toByteCode(self.error_value, codegen, breaks); - - try codegen.emitOpCode(self.node.location, .OP_THROW); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Throw\", \"error_value\": "); - - try self.error_value.toJson(self.error_value, out); - - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("throw "); - try self.error_value.render(self.error_value, out, depth); - try out.writeAll(";\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Throw) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const BreakNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Break, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - assert(breaks != null); - - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - // Close scope(s), then jump - try node.endScope(codegen); - try breaks.?.append(try codegen.emitJump(node.location, .OP_JUMP)); - - // TODO: not sur if this makes sense here - try node.patchOptJumps(codegen); - - return null; - } - - fn stringify(_: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - try out.writeAll("{\"node\": \"Break\" }"); - } - - fn render(_: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("break;\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Break) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ContinueNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Continue, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - assert(breaks != null); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - // Close scope(s), then jump - try node.endScope(codegen); - try breaks.?.append(try codegen.emitJump(node.location, .OP_LOOP)); - - // TODO: not sur if this makes sense here - try node.patchOptJumps(codegen); - - return null; - } - - fn stringify(_: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - try out.writeAll("{\"node\": \"Continue\" }"); - } - - fn render(_: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("continue;\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Continue) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const IfNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .If, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - condition: *ParseNode, - unwrapped_identifier: ?Token, - casted_type: ?*ObjTypeDef, - body: *ParseNode, - else_branch: ?*ParseNode = null, - is_statement: bool, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return !self.is_statement and self.condition.isConstant(self.condition) and self.body.isConstant(self.body) and self.else_branch.?.isConstant(self.else_branch.?); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.isConstant(node)) { - const self = Self.cast(node).?; - assert(!self.is_statement); - - if ((try self.condition.toValue(self.condition, gc)).boolean()) { - return self.body.toValue(self.body, gc); - } else { - return self.else_branch.?.toValue(self.else_branch.?, gc); - } - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - const codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - if (self.condition.type_def == null or self.condition.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.condition.type_def.?.resolved_type.?.Placeholder); - } - - if (!self.is_statement) { - if (self.body.type_def == null or self.body.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.body.type_def.?.resolved_type.?.Placeholder); - } - - if (self.else_branch.?.type_def == null or self.else_branch.?.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.else_branch.?.type_def.?.resolved_type.?.Placeholder); - } - - // Both should have same type - if (!node.type_def.?.eql(self.body.type_def.?)) { - codegen.reporter.reportTypeCheck( - .inline_if_body_type, - node.location, - node.type_def.?, - self.body.location, - self.body.type_def.?, - "Inline if body type not matching", - ); - } - - if (!node.type_def.?.eql(self.else_branch.?.type_def.?)) { - codegen.reporter.reportTypeCheck( - .inline_if_else_type, - node.location, - node.type_def.?, - self.else_branch.?.location, - self.else_branch.?.type_def.?, - "Inline if else type not matching", - ); - } - } - - if (self.unwrapped_identifier != null) { - if (!self.condition.type_def.?.optional) { - codegen.reporter.reportErrorAt( - .optional, - self.condition.location, - "Expected optional", - ); - } - } else if (self.casted_type == null) { - if (self.condition.type_def.?.def_type != .Bool) { - codegen.reporter.reportErrorAt( - .if_condition_type, - self.condition.location, - "`if` condition must be bool", - ); - } - } - - // If condition is a constant expression, no need to generate branches - if (self.condition.isConstant(self.condition) and self.unwrapped_identifier == null and self.casted_type == null) { - const condition = try self.condition.toValue(self.condition, codegen.gc); - - if (condition.boolean()) { - _ = try self.body.toByteCode(self.body, codegen, breaks); - } else if (self.else_branch) |else_branch| { - _ = try else_branch.toByteCode(else_branch, codegen, breaks); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - _ = try self.condition.toByteCode(self.condition, codegen, breaks); - if (self.unwrapped_identifier != null) { - try codegen.emitOpCode(self.condition.location, .OP_COPY); - try codegen.emitOpCode(self.condition.location, .OP_NULL); - try codegen.emitOpCode(self.condition.location, .OP_EQUAL); - try codegen.emitOpCode(self.condition.location, .OP_NOT); - } else if (self.casted_type) |casted_type| { - try codegen.emitOpCode(self.condition.location, .OP_COPY); - try codegen.emitConstant(self.condition.location, casted_type.toValue()); - try codegen.emitOpCode(self.condition.location, .OP_IS); - } - - const else_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - try codegen.emitOpCode(self.node.location, .OP_POP); - - _ = try self.body.toByteCode(self.body, codegen, breaks); - - const out_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP); - - codegen.patchJump(else_jump); - if (self.unwrapped_identifier != null or self.casted_type != null) { - // Since we did not enter the if block, we did not pop the unwrapped local - try codegen.emitOpCode(self.node.location, .OP_POP); - } - try codegen.emitOpCode(self.node.location, .OP_POP); - - if (self.else_branch) |else_branch| { - _ = try else_branch.toByteCode(else_branch, codegen, breaks); - } - - codegen.patchJump(out_jump); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"If\", \"condition\": "); - - try self.condition.toJson(self.condition, out); - - try out.writeAll(", \"body\": "); - - try self.body.toJson(self.body, out); - - if (self.else_branch) |else_branch| { - try out.writeAll(", \"else\": "); - try else_branch.toJson(else_branch, out); - } - - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.writeAll("if ("); - try self.condition.render(self.condition, out, depth); - if (self.unwrapped_identifier) |unwrapped_identifier| { - try out.print(" -> {s}", .{unwrapped_identifier.lexeme}); - } else if (self.casted_type) |casted_type| { - try out.writeAll(" -> "); - try casted_type.toStringUnqualified(out); - } - - try out.writeAll(") {\n"); - try self.body.render(self.body, out, depth + 1); - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("}"); - - if (self.else_branch) |else_branch| { - try out.writeAll(" else "); - - if (else_branch.node_type != .If) { - try out.writeAll("{\n"); - try else_branch.render(else_branch, out, depth + 1); - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("}"); - } else { - try else_branch.render(else_branch, out, depth); - } - } - - try out.writeAll("\n\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .If) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ReturnNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Return, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - value: ?*ParseNode, - unconditional: bool, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - if (self.unconditional) { - codegen.current.?.return_emitted = true; - } - - if (self.value) |value| { - if (value.type_def == null) { - codegen.reporter.reportErrorAt( - .undefined, - value.location, - "Unknown type.", - ); - } else if (value.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(value.type_def.?.resolved_type.?.Placeholder); - } else if (!codegen.current.?.function.?.type_def.resolved_type.?.Function.return_type.eql(value.type_def.?)) { - codegen.reporter.reportTypeCheck( - .return_type, - codegen.current.?.function_node.node.location, - codegen.current.?.function.?.type_def.resolved_type.?.Function.return_type, - value.location, - value.type_def.?, - "Return value", - ); - } - - _ = try value.toByteCode(value, codegen, breaks); - } else { - try codegen.emitOpCode(self.node.location, .OP_VOID); - } - - try codegen.emitOpCode(self.node.location, .OP_RETURN); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Return\", "); - - if (self.value) |value| { - try out.writeAll("\"value\": "); - try value.toJson(value, out); - try out.writeAll(", "); - } - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.writeAll("return"); - if (self.value) |value| { - try out.writeAll(" "); - try value.render(value, out, depth); - } - - try out.writeAll(";\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Return) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ForNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .For, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - init_declarations: std.ArrayList(*VarDeclarationNode), - condition: *ParseNode, - post_loop: std.ArrayList(*ParseNode), - body: *ParseNode, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - if (self.condition.isConstant(self.condition) and !(try self.condition.toValue(self.condition, codegen.gc)).boolean()) { - try node.patchOptJumps(codegen); - - return null; - } - - for (self.init_declarations.items) |var_declaration| { - _ = try var_declaration.node.toByteCode(&var_declaration.node, codegen, _breaks); - } - - const loop_start: usize = codegen.currentCode(); - - if (self.condition.type_def == null or self.condition.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.condition.type_def.?.resolved_type.?.Placeholder); - } - - if (self.condition.type_def.?.def_type != .Bool) { - codegen.reporter.reportErrorAt( - .for_condition_type, - self.condition.location, - "`for` condition must be bool", - ); - } - - _ = try self.condition.toByteCode(self.condition, codegen, _breaks); - - const exit_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - try codegen.emitOpCode(self.node.location, .OP_POP); // Pop condition - - // Jump over expressions which will be executed at end of loop - // TODO: since we don't generate as we parse, we can get rid of this jump and just generate the post_loop later - var body_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP); - - const expr_loop: usize = codegen.currentCode(); - for (self.post_loop.items) |expr| { - if (expr.type_def == null or expr.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(expr.type_def.?.resolved_type.?.Placeholder); - } - - _ = try expr.toByteCode(expr, codegen, _breaks); - try codegen.emitOpCode(expr.location, .OP_POP); - } - - try codegen.emitLoop(self.node.location, loop_start); - - codegen.patchJump(body_jump); - - var breaks: std.ArrayList(usize) = std.ArrayList(usize).init(codegen.gc.allocator); - defer breaks.deinit(); - - _ = try self.body.toByteCode(self.body, codegen, &breaks); - - try codegen.emitLoop(self.node.location, expr_loop); - - codegen.patchJump(exit_jump); - - try codegen.emitOpCode(self.node.location, .OP_POP); // Pop condition - - // Patch breaks - for (breaks.items) |jump| { - try codegen.patchJumpOrLoop(jump, loop_start); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"For\", \"init_declarations\": ["); - - for (self.init_declarations.items, 0..) |var_declaration, i| { - try var_declaration.node.toJson(&var_declaration.node, out); - - if (i < self.init_declarations.items.len - 1) { - try out.writeAll(","); - } - } - - try out.writeAll("], \"condition\": "); - - try self.condition.toJson(self.condition, out); - - try out.writeAll(", \"post_loop\": ["); - - for (self.post_loop.items, 0..) |expression, i| { - try expression.toJson(expression, out); - - if (i < self.post_loop.items.len - 1) { - try out.writeAll(", "); - } - } - - try out.writeAll("], \"body\": "); - - try self.body.toJson(self.body, out); - - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.writeAll("for ("); - for (self.init_declarations.items, 0..) |decl, i| { - try decl.node.render(&decl.node, out, depth); - if (i < self.init_declarations.items.len - 1) { - try out.writeAll(", "); - } - } - try out.writeAll("; "); - - try self.condition.render(self.condition, out, depth); - try out.writeAll("; "); - - for (self.post_loop.items, 0..) |expr, i| { - try expr.render(expr, out, depth); - - if (i < self.post_loop.items.len - 1) { - try out.writeAll(", "); - } - } - - try out.writeAll(") {\n"); - - try self.body.render(self.body, out, depth + 1); - - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("}\n"); - } - - pub fn init(allocator: Allocator) Self { - return Self{ - .init_declarations = std.ArrayList(*VarDeclarationNode).init(allocator), - .post_loop = std.ArrayList(*ParseNode).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.init_declarations.deinit(); - self.post_loop.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .For) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ForEachNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .ForEach, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - key_omitted: bool, - key: *VarDeclarationNode, - value: *VarDeclarationNode, - iterable: *ParseNode, - block: *ParseNode, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - // Type checking - if (self.iterable.type_def == null or self.iterable.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.iterable.type_def.?.resolved_type.?.Placeholder); - } else { - if (!self.key_omitted) { - if (self.key.type_def.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.key.type_def.resolved_type.?.Placeholder); - } - - switch (self.iterable.type_def.?.def_type) { - .String, .List => { - if (self.key.type_def.def_type != .Integer) { - codegen.reporter.reportErrorAt( - .foreach_key_type, - self.key.node.location, - "Expected `int`.", - ); - } - }, - .Map => { - if (!self.iterable.type_def.?.resolved_type.?.Map.key_type.strictEql(self.key.type_def)) { - codegen.reporter.reportTypeCheck( - .foreach_key_type, - self.iterable.location, - self.iterable.type_def.?.resolved_type.?.Map.key_type, - self.key.node.location, - self.key.type_def, - "Bad key type", - ); - } - }, - .Enum => codegen.reporter.reportErrorAt( - .foreach_key_type, - self.key.node.location, - "No key available when iterating over enum.", - ), - else => codegen.reporter.reportErrorAt( - .foreach_iterable, - self.iterable.location, - "Not iterable.", - ), - } - } else { - // Key was omitted, put the correct type in the key var declation to avoid raising errors - switch (self.iterable.type_def.?.def_type) { - .Map => self.key.type_def = self.iterable.type_def.?.resolved_type.?.Map.key_type, - .String, .List => self.key.type_def = try codegen.gc.type_registry.getTypeDef(.{ .def_type = .Integer }), - else => {}, - } - } - - if (self.value.type_def.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.value.type_def.resolved_type.?.Placeholder); - } - - switch (self.iterable.type_def.?.def_type) { - .Map => { - if (!self.iterable.type_def.?.resolved_type.?.Map.value_type.strictEql(self.value.type_def)) { - codegen.reporter.reportTypeCheck( - .foreach_value_type, - self.iterable.location, - self.iterable.type_def.?.resolved_type.?.Map.value_type, - self.value.node.location, - self.value.type_def, - "Bad value type", - ); - } - }, - .List => { - if (!self.iterable.type_def.?.resolved_type.?.List.item_type.strictEql(self.value.type_def)) { - codegen.reporter.reportTypeCheck( - .foreach_value_type, - self.iterable.location, - self.iterable.type_def.?.resolved_type.?.List.item_type, - self.value.node.location, - self.value.type_def, - "Bad value type", - ); - } - }, - .String => { - if (self.value.type_def.def_type != .String) { - codegen.reporter.reportErrorAt( - .foreach_value_type, - self.value.node.location, - "Expected `str`.", - ); - } - }, - .Enum => { - const iterable_type = try self.iterable.type_def.?.toInstance(codegen.gc.allocator, &codegen.gc.type_registry); - if (!iterable_type.strictEql(self.value.type_def)) { - codegen.reporter.reportTypeCheck( - .foreach_value_type, - self.iterable.location, - iterable_type, - self.value.node.location, - self.value.type_def, - "Bad value type", - ); - } - }, - .Fiber => { - const iterable_type = try self.iterable.type_def.?.resolved_type.?.Fiber.yield_type.toInstance( - codegen.gc.allocator, - &codegen.gc.type_registry, - ); - if (!iterable_type.strictEql(self.value.type_def)) { - codegen.reporter.reportTypeCheck( - .foreach_value_type, - self.iterable.location, - iterable_type, - self.value.node.location, - self.value.type_def, - "Bad value type", - ); - } - }, - else => codegen.reporter.reportErrorAt( - .foreach_iterable, - self.iterable.location, - "Not iterable.", - ), - } - } - - // If iterable constant and empty, skip the node - if (self.iterable.isConstant(self.iterable)) { - const iterable = (try self.iterable.toValue(self.iterable, codegen.gc)).obj(); - - if (switch (iterable.obj_type) { - .List => ObjList.cast(iterable).?.items.items.len == 0, - .Map => ObjMap.cast(iterable).?.map.count() == 0, - .String => ObjString.cast(iterable).?.string.len == 0, - .Enum => ObjEnum.cast(iterable).?.cases.items.len == 0, - else => codegen.reporter.had_error, - }) { - try node.patchOptJumps(codegen); - return null; - } - } - - _ = try self.key.node.toByteCode(&self.key.node, codegen, _breaks); - _ = try self.value.node.toByteCode(&self.value.node, codegen, _breaks); - - _ = try self.iterable.toByteCode(self.iterable, codegen, _breaks); - - const loop_start: usize = codegen.currentCode(); - - // Calls `next` and update key and value locals - try codegen.emitOpCode( - self.node.location, - switch (self.iterable.type_def.?.def_type) { - .String => .OP_STRING_FOREACH, - .List => .OP_LIST_FOREACH, - .Enum => .OP_ENUM_FOREACH, - .Map => .OP_MAP_FOREACH, - .Fiber => .OP_FIBER_FOREACH, - else => unexpected: { - assert(codegen.reporter.had_error); - break :unexpected .OP_STRING_FOREACH; - }, - }, - ); - - // If next key is null, exit loop - try codegen.emitCodeArg( - self.node.location, - .OP_GET_LOCAL, - @as( - u24, - @intCast( - switch (self.iterable.type_def.?.def_type) { - .String, .List, .Map => self.key.slot, - else => self.value.slot, - }, - ), - ), - ); - try codegen.emitOpCode(self.node.location, .OP_NULL); - try codegen.emitOpCode(self.node.location, .OP_EQUAL); - try codegen.emitOpCode(self.node.location, .OP_NOT); - const exit_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - try codegen.emitOpCode(self.node.location, .OP_POP); // Pop condition result - - var breaks: std.ArrayList(usize) = std.ArrayList(usize).init(codegen.gc.allocator); - defer breaks.deinit(); - - _ = try self.block.toByteCode(self.block, codegen, &breaks); - - try codegen.emitLoop(self.node.location, loop_start); - - // Patch condition jump - codegen.patchJump(exit_jump); - - try codegen.emitOpCode(self.node.location, .OP_POP); // Pop condition result - - // Patch breaks - for (breaks.items) |jump| { - try codegen.patchJumpOrLoop(jump, loop_start); - } - - try node.patchOptJumps(codegen); - // Should have key, [value,] iterable to pop - assert(node.ends_scope != null and node.ends_scope.?.items.len == 3); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"ForEach\", "); - - if (!self.key_omitted) { - try out.writeAll("\"key\": "); - try self.key.node.toJson(&self.key.node, out); - try out.writeAll(", "); - } - - try out.writeAll("\"value\": "); - - try self.value.node.toJson(&self.value.node, out); - - try out.writeAll(", \"iterable\": "); - - try self.iterable.toJson(self.iterable, out); - - try out.writeAll(", \"block\": "); - - try self.block.toJson(self.block, out); - - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.writeAll("foreach ("); - if (!self.key_omitted) { - try self.key.node.render(&self.key.node, out, depth); - try out.writeAll(", "); - } - try self.value.node.render(&self.value.node, out, depth); - try out.writeAll(" in "); - try self.iterable.render(self.iterable, out, depth); - try out.writeAll(") {\n"); - try self.block.render(self.block, out, depth + 1); - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("}\n"); - } - - pub fn init(allocator: Allocator) Self { - return Self{ - .init_expression = std.ArrayList(*ParseNode).init(allocator), - .post_loop = std.ArrayList(*ParseNode).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.init_expressions.deinit(); - self.post_loop.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .ForEach) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const WhileNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .While, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - condition: *ParseNode, - block: *ParseNode, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - // If condition constant and false, skip the node - if (self.condition.isConstant(self.condition) and !(try self.condition.toValue(self.condition, codegen.gc)).boolean()) { - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - const loop_start: usize = codegen.currentCode(); - - if (self.condition.type_def == null or self.condition.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.condition.type_def.?.resolved_type.?.Placeholder); - } - - if (self.condition.type_def.?.def_type != .Bool) { - codegen.reporter.reportErrorAt( - .while_condition_type, - self.condition.location, - "`while` condition must be bool", - ); - } - - _ = try self.condition.toByteCode(self.condition, codegen, _breaks); - - const exit_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - try codegen.emitOpCode(self.node.location, .OP_POP); - - var breaks: std.ArrayList(usize) = std.ArrayList(usize).init(codegen.gc.allocator); - defer breaks.deinit(); - - _ = try self.block.toByteCode(self.block, codegen, &breaks); - - try codegen.emitLoop(self.node.location, loop_start); - codegen.patchJump(exit_jump); - - try codegen.emitOpCode(self.node.location, .OP_POP); // Pop condition (is not necessary if broke out of the loop) - - // Patch breaks - for (breaks.items) |jump| { - try codegen.patchJumpOrLoop(jump, loop_start); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"While\", \"condition\": "); - - try self.condition.toJson(self.condition, out); - - try out.writeAll(", \"block\": "); - - try self.block.toJson(self.block, out); - - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.writeAll("while ("); - try self.condition.render(self.condition, out, depth); - try out.writeAll(") {\n"); - try self.block.render(self.block, out, depth + 1); - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("}\n"); - } - - pub fn init(allocator: Allocator) Self { - return Self{ - .init_expression = std.ArrayList(*ParseNode).init(allocator), - .post_loop = std.ArrayList(*ParseNode).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.init_expressions.deinit(); - self.post_loop.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .While) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const DoUntilNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .DoUntil, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - condition: *ParseNode, - block: *ParseNode, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - const loop_start: usize = codegen.currentCode(); - - var breaks: std.ArrayList(usize) = std.ArrayList(usize).init(codegen.gc.allocator); - defer breaks.deinit(); - - _ = try self.block.toByteCode(self.block, codegen, &breaks); - - if (self.condition.type_def == null or self.condition.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(self.condition.type_def.?.resolved_type.?.Placeholder); - } - - if (self.condition.type_def.?.def_type != .Bool) { - codegen.reporter.reportErrorAt( - .do_condition_type, - self.condition.location, - "`do` condition must be bool", - ); - } - - _ = try self.condition.toByteCode(self.condition, codegen, &breaks); - - try codegen.emitOpCode(self.node.location, .OP_NOT); - const exit_jump: usize = try codegen.emitJump(self.node.location, .OP_JUMP_IF_FALSE); - try codegen.emitOpCode(self.node.location, .OP_POP); - - try codegen.emitLoop(self.node.location, loop_start); - codegen.patchJump(exit_jump); - - try codegen.emitOpCode(self.node.location, .OP_POP); // Pop condition - - // Patch breaks - for (breaks.items) |jump| { - try codegen.patchJumpOrLoop(jump, loop_start); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"DoUntil\", \"condition\": "); - - try self.condition.toJson(self.condition, out); - - try out.writeAll(", \"block\": "); - - try self.block.toJson(self.block, out); - - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.writeAll("do {\n"); - try self.block.render(self.block, out, depth + 1); - try out.writeAll("} until ("); - try self.condition.render(self.condition, out, depth); - try out.writeAll(")\n"); - } - - pub fn init(allocator: Allocator) Self { - return Self{ - .init_expression = std.ArrayList(*ParseNode).init(allocator), - .post_loop = std.ArrayList(*ParseNode).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.init_expressions.deinit(); - self.post_loop.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .DoUntil) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const BlockNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Block, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - statements: std.ArrayList(*ParseNode), - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - for (self.statements.items) |statement| { - _ = try statement.toByteCode(statement, codegen, breaks); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Block\", \"statements\": ["); - - for (self.statements.items, 0..) |statement, i| { - try statement.toJson(statement, out); - - if (i < self.statements.items.len - 1) { - try out.writeAll(","); - } - } - - try out.writeAll("], "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - for (self.statements.items) |statement| { - try statement.render(statement, out, depth); - } - } - - pub fn init(allocator: Allocator) Self { - return Self{ - .statements = std.ArrayList(*ParseNode).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.statements.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Block) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const DotNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Dot, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - callee: *ParseNode, - identifier: Token, - member_type_def: ?*ObjTypeDef = null, - value: ?*ParseNode = null, - call: ?*CallNode = null, - enum_index: ?usize = null, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.callee.type_def.?.def_type == .Enum and self.callee.type_def.?.resolved_type.?.Enum.value != null; - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - if (self.callee.type_def.?.def_type != .Enum or self.callee.type_def.?.resolved_type.?.Enum.value == null) { - return GenError.NotConstant; - } - - return (try gc.allocateObject( - ObjEnumInstance, - ObjEnumInstance{ - .enum_ref = self.callee.type_def.?.resolved_type.?.Enum.value.?, - .case = @intCast(self.enum_index.?), - }, - )).toValue(); - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - _ = try self.callee.toByteCode(self.callee, codegen, breaks); - - const callee_type = self.callee.type_def.?; - - if (callee_type.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(callee_type.resolved_type.?.Placeholder); - } - - switch (callee_type.def_type) { - .ObjectInstance, - .Object, - .ProtocolInstance, - .Enum, - .EnumInstance, - .List, - .Map, - .String, - .Pattern, - .Fiber, - .ForeignContainer, - => {}, - else => codegen.reporter.reportErrorAt( - .field_access, - node.location, - "Doesn't have field access", - ), - } - - if (callee_type.optional) { - codegen.reporter.reportErrorAt( - .field_access, - node.location, - "Optional doesn't have field access", - ); - } - - var get_code: ?OpCode = switch (callee_type.def_type) { - .Object => .OP_GET_OBJECT_PROPERTY, - .ObjectInstance, .ProtocolInstance => .OP_GET_INSTANCE_PROPERTY, - .ForeignContainer => .OP_GET_FCONTAINER_INSTANCE_PROPERTY, - .List => .OP_GET_LIST_PROPERTY, - .Map => .OP_GET_MAP_PROPERTY, - .String => .OP_GET_STRING_PROPERTY, - .Pattern => .OP_GET_PATTERN_PROPERTY, - .Fiber => .OP_GET_FIBER_PROPERTY, - else => null, - }; - - switch (callee_type.def_type) { - .Fiber, .Pattern, .String => { - if (self.call) |call_node| { // Call - try codegen.emitOpCode(self.node.location, .OP_COPY); - _ = try call_node.node.toByteCode(&call_node.node, codegen, breaks); - } else { // Expression - assert(self.value == null); - try codegen.emitCodeArg( - self.node.location, - get_code.?, - try codegen.identifierConstant(self.identifier.lexeme), - ); - } - }, - .ForeignContainer, .ObjectInstance, .Object => { - if (self.value) |value| { - if (value.type_def == null or value.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(value.type_def.?.resolved_type.?.Placeholder); - } - - _ = try value.toByteCode(value, codegen, breaks); - - try codegen.emitCodeArg( - self.node.location, - switch (callee_type.def_type) { - .ObjectInstance => .OP_SET_INSTANCE_PROPERTY, - .ForeignContainer => .OP_SET_FCONTAINER_INSTANCE_PROPERTY, - else => .OP_SET_OBJECT_PROPERTY, - }, - try codegen.identifierConstant(self.identifier.lexeme), - ); - } else if (self.call) |call| { - if (callee_type.def_type == .ForeignContainer) { - codegen.reporter.reportErrorAt( - .callable, - self.callee.location, - "Not callable", - ); - } - - // Static call - if (callee_type.def_type == .Object) { - try codegen.emitCodeArg( - node.location, - get_code.?, - try codegen.identifierConstant(self.identifier.lexeme), - ); - } - - _ = try call.node.toByteCode(&call.node, codegen, breaks); - } else { - try codegen.emitCodeArg( - self.node.location, - get_code.?, - try codegen.identifierConstant(self.identifier.lexeme), - ); - } - }, - .ProtocolInstance => { - if (self.call) |call| { - _ = try call.node.toByteCode(&call.node, codegen, breaks); - } else { - assert(self.value == null); - try codegen.emitCodeArg( - self.node.location, - get_code.?, - try codegen.identifierConstant(self.identifier.lexeme), - ); - } - }, - .Enum => { - try codegen.emitCodeArg(self.node.location, .OP_GET_ENUM_CASE, @intCast(self.enum_index.?)); - }, - .EnumInstance => { - assert(std.mem.eql(u8, self.identifier.lexeme, "value")); - - try codegen.emitOpCode(self.node.location, .OP_GET_ENUM_CASE_VALUE); - }, - .List, .Map => { - if (self.call) |call| { - try codegen.emitOpCode(self.node.location, .OP_COPY); - - _ = try call.node.toByteCode(&call.node, codegen, breaks); - } else { - assert(self.value == null); - try codegen.emitCodeArg( - self.node.location, - get_code.?, - try codegen.identifierConstant(self.identifier.lexeme), - ); - } - }, - else => assert(codegen.reporter.had_error), - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"Dot\", \"callee\": "); - - try self.callee.toJson(self.callee, out); - - try out.print(", \"identifier\": \"{s}\", ", .{self.identifier.lexeme}); - - if (self.value) |value| { - try out.writeAll("\"value\": "); - try value.toJson(value, out); - try out.writeAll(", "); - } - - if (self.call) |call| { - try out.writeAll("\"call\": "); - try call.toNode().toJson(call.toNode(), out); - try out.writeAll(", "); - } - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try self.callee.render(self.callee, out, depth); - try out.print(".{s}", .{self.identifier.lexeme}); - - if (self.value) |value| { - try out.writeAll(" = "); - try value.render(value, out, depth); - } else if (self.call) |call| { - try call.node.render(&call.node, out, depth); - } - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .Dot) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ObjectInitNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .ObjectInit, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - object: ?*ParseNode, // Should be a NamedVariableNode or GenericResolve - properties: std.StringArrayHashMap(*ParseNode), - - fn checkOmittedProperty( - self: *Self, - codegenPtr: *anyopaque, - fields: std.StringArrayHashMap(*ObjTypeDef), - fields_defaults: ?std.StringArrayHashMap(void), - init_properties: std.StringHashMap(void), - ) anyerror!void { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var it = fields.iterator(); - while (it.next()) |kv| { - // If ommitted in initialization and doesn't have default value - if (init_properties.get(kv.key_ptr.*) == null and (fields_defaults == null or fields_defaults.?.get(kv.key_ptr.*) == null)) { - codegen.reporter.reportErrorFmt( - .property_not_initialized, - self.node.location, - "Property `{s}` was not initialized and has no default value", - .{kv.key_ptr.*}, - ); - } - } - } - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - if (self.object != null and self.object.?.type_def.?.def_type == .Object) { - _ = try self.object.?.toByteCode(self.object.?, codegen, breaks); - } else if (node.type_def.?.def_type == .ObjectInstance) { - try codegen.emitOpCode(node.location, .OP_NULL); - } - - try codegen.emitCodeArg( - node.location, - .OP_CONSTANT, - try codegen.makeConstant(node.type_def.?.toValue()), - ); - - if (node.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(node.type_def.?.resolved_type.?.Placeholder); - } else if (node.type_def.?.def_type != .ObjectInstance and node.type_def.?.def_type != .ForeignContainer) { - codegen.reporter.reportErrorAt( - .expected_object, - node.location, - "Expected object or foreign struct.", - ); - } - - try codegen.emitOpCode( - self.node.location, - if (node.type_def.?.def_type == .ObjectInstance) - .OP_INSTANCE - else - .OP_FCONTAINER_INSTANCE, - ); - - const fields = if (node.type_def.?.def_type == .ObjectInstance) - node.type_def.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields - else - node.type_def.?.resolved_type.?.ForeignContainer.buzz_type; - - const location = if (node.type_def.?.def_type == .ObjectInstance) - node.type_def.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.location - else - node.type_def.?.resolved_type.?.ForeignContainer.location; - - const fields_location = if (node.type_def.?.def_type == .ObjectInstance) - node.type_def.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields_locations - else - null; - - // To keep track of what's been initialized or not by this statement - var init_properties = std.StringHashMap(void).init(codegen.gc.allocator); - defer init_properties.deinit(); - - for (self.properties.keys()) |property_name| { - const property_name_constant: u24 = try codegen.identifierConstant(property_name); - const value = self.properties.get(property_name).?; - - if (fields.get(property_name)) |prop| { - try codegen.emitCodeArg(self.node.location, .OP_COPY, 0); // Will be popped by OP_SET_PROPERTY - - if (value.type_def == null or value.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(value.type_def.?.resolved_type.?.Placeholder); - } else if (!prop.eql(value.type_def.?)) { - if (BuildOptions.debug_placeholders) { - std.debug.print( - "prop {}({}), value {}({})\n", - .{ - @intFromPtr(prop.resolved_type.?.ObjectInstance), - prop.optional, - @intFromPtr(value.type_def.?.resolved_type.?.ObjectInstance), - value.type_def.?.optional, - }, - ); - } - codegen.reporter.reportTypeCheck( - .property_type, - if (fields_location) |floc| - floc.get(property_name) - else - location, - prop, - value.location, - value.type_def.?, - "Wrong property type", - ); - } - - _ = try value.toByteCode(value, codegen, breaks); - - try init_properties.put(property_name, {}); - - try codegen.emitCodeArg( - self.node.location, - if (node.type_def.?.def_type == .ObjectInstance) - .OP_SET_INSTANCE_PROPERTY - else - .OP_SET_FCONTAINER_INSTANCE_PROPERTY, - property_name_constant, - ); - try codegen.emitOpCode(self.node.location, .OP_POP); // Pop property value - } else { - codegen.reporter.reportWithOrigin( - .property_does_not_exists, - node.location, - location, - "Property `{s}` does not exists", - .{property_name}, - null, - ); - } - } - - // Did we initialized all properties without a default value? - // If union we're statisfied with only on field initialized - if (node.type_def.?.def_type != .ForeignContainer or node.type_def.?.resolved_type.?.ForeignContainer.zig_type != .Union or init_properties.count() == 0) { - try self.checkOmittedProperty( - codegen, - fields, - if (node.type_def.?.def_type == .ObjectInstance) - node.type_def.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.fields_defaults - else - null, - init_properties, - ); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"ObjectInit\", \"properties\": {"); - - var it = self.properties.iterator(); - var first = true; - while (it.next()) |entry| { - if (!first) { - try out.writeAll(","); - } - - first = false; - - try out.print("\"{s}\": ", .{entry.key_ptr.*}); - - try entry.value_ptr.*.toJson(entry.value_ptr.*, out); - } - - try out.writeAll("}, \"object\": "); - - if (self.object) |object| { - try object.toJson(object, out); - } else { - try out.writeAll("null"); - } - try out.writeAll(", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - if (self.object) |object| { - try object.render(object, out, depth); - } else { - try out.writeAll("."); - } - try out.writeAll("{"); - - if (self.properties.count() > 0) { - try out.writeAll("\n"); - } - - var it = self.properties.iterator(); - while (it.next()) |kv| { - try out.writeByteNTimes(' ', (depth + 1) * 4); - try out.print("{s} = ", .{kv.key_ptr.*}); - try kv.value_ptr.*.render(kv.value_ptr.*, out, depth + 1); - try out.writeAll(",\n"); - } - - if (self.properties.count() > 0) { - try out.writeByteNTimes(' ', depth * 4); - } - try out.writeAll("}"); - } - - pub fn init(allocator: Allocator, object: ?*ParseNode) Self { - return .{ - .object = object, - .properties = std.StringArrayHashMap(*ParseNode).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.properties.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.node_type != .ObjectInit) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ObjectDeclarationNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .ObjectDeclaration, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - slot: usize, - // All properties and methods with preserved order for buzz --fmt - fields: [][]const u8, - methods: std.StringHashMap(*ParseNode), - properties: std.StringHashMap(?*ParseNode), - properties_type: std.StringHashMap(*ObjTypeDef), - docblocks: std.StringHashMap(?Token), - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - const object_type = node.type_def.?; - const object_def = object_type.resolved_type.?.Object; - - // Check object conforms to declared protocols - var protocol_it = object_def.conforms_to.iterator(); - while (protocol_it.next()) |kv| { - const protocol_type_def = kv.key_ptr.*; - - if (protocol_type_def.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(protocol_type_def.resolved_type.?.Placeholder); - } else { - const protocol_def = protocol_type_def.resolved_type.?.Protocol; - - var method_it = protocol_def.methods.iterator(); - while (method_it.next()) |mkv| { - if (self.methods.get(mkv.key_ptr.*)) |method| { - if (method.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(method.type_def.?.resolved_type.?.Placeholder); - } else if (!mkv.value_ptr.*.eql(method.type_def.?)) { - codegen.reporter.reportTypeCheck( - .protocol_conforming, - protocol_def.location, - mkv.value_ptr.*, - method.location, - method.type_def.?, - "Method not conforming to protocol", - ); - } - } else { - codegen.reporter.reportWithOrigin( - .protocol_conforming, - self.node.location, - protocol_def.methods_locations.get(mkv.value_ptr.*.resolved_type.?.Function.name.string).?, - "Object declared as conforming to protocol `{s}` but doesn't implement method `{s}`", - .{ - protocol_def.name.string, - mkv.value_ptr.*.resolved_type.?.Function.name.string, - }, - null, - ); - } - } - } - } - - const name_constant = try codegen.makeConstant(object_def.name.toValue()); - const object_type_constant = try codegen.makeConstant(object_type.toValue()); - - // Put object on the stack and define global with it - try codegen.emitCodeArg(self.node.location, .OP_OBJECT, name_constant); - try codegen.emit(self.node.location, @intCast(object_type_constant)); - try codegen.emitCodeArg(self.node.location, .OP_DEFINE_GLOBAL, @intCast(self.slot)); - - // Put the object on the stack to set its fields - try codegen.emitCodeArg(self.node.location, .OP_GET_GLOBAL, @intCast(self.slot)); - - // Methods - var it = self.methods.iterator(); - while (it.next()) |kv| { - const member_name = kv.key_ptr.*; - const member = kv.value_ptr.*; - const member_name_constant: u24 = try codegen.identifierConstant(member_name); - - if (member.type_def == null or member.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(member.type_def.?.resolved_type.?.Placeholder); - } - - // Enforce "collect" method signature - if (std.mem.eql(u8, member_name, "collect")) { - const collect_def = member.type_def.?.resolved_type.?.Function; - - // zig fmt: off - if (collect_def.parameters.count() > 0 - or collect_def.return_type.def_type != .Void - or collect_def.yield_type.def_type != .Void - or collect_def.error_types != null) { - // zig fmt: on - const collect_def_str = member.type_def.?.toStringAlloc(codegen.gc.allocator) catch @panic("Out of memory"); - defer collect_def_str.deinit(); - codegen.reporter.reportErrorFmt( - .collect_signature, - member.location, - "Expected `collect` method to be `fun collect() > void` got {s}", - .{ - collect_def_str.items, - }, - ); - } - } - - // Enforce "toString" method signature - if (std.mem.eql(u8, member_name, "toString")) { - const tostring_def = member.type_def.?.resolved_type.?.Function; - - // zig fmt: off - if (tostring_def.parameters.count() > 0 - or tostring_def.return_type.def_type != .String - or tostring_def.yield_type.def_type != .Void - or tostring_def.error_types != null - or tostring_def.generic_types.count() > 0) { - // zig fmt: on - const tostring_def_str = member.type_def.?.toStringAlloc(codegen.gc.allocator) catch @panic("Out of memory"); - defer tostring_def_str.deinit(); - codegen.reporter.reportErrorFmt( - .tostring_signature, - member.location, - "Expected `toString` method to be `fun toString() > str` got {s}", - .{ - tostring_def_str.items, - }, - ); - } - } - - const is_static = object_def.static_fields.get(member_name) != null; - - _ = try member.toByteCode(member, codegen, breaks); - try codegen.emitCodeArg(self.node.location, if (is_static) .OP_PROPERTY else .OP_METHOD, member_name_constant); - } - - // Properties - var it2 = self.properties.iterator(); - while (it2.next()) |kv| { - const member_name = kv.key_ptr.*; - const member = kv.value_ptr.*; - const member_name_constant: u24 = try codegen.identifierConstant(member_name); - const is_static = object_def.static_fields.get(member_name) != null; - const property_type = object_def.fields.get(member_name) orelse object_def.static_fields.get(member_name); - - assert(property_type != null); - - // Create property default value - if (member) |default| { - if (default.type_def == null or default.type_def.?.def_type == .Placeholder) { - codegen.reporter.reportPlaceholder(default.type_def.?.resolved_type.?.Placeholder); - } else if (!property_type.?.eql(default.type_def.?)) { - codegen.reporter.reportTypeCheck( - .property_default_value, - object_def.location, - property_type.?, - default.location, - default.type_def.?, - "Wrong property default value type", - ); - } - - if (is_static) { - try codegen.emitOpCode(self.node.location, .OP_COPY); - } - - _ = try default.toByteCode(default, codegen, breaks); - - // Create property default value - if (is_static) { - try codegen.emitCodeArg(self.node.location, .OP_SET_OBJECT_PROPERTY, member_name_constant); - try codegen.emitOpCode(self.node.location, .OP_POP); - } else { - try codegen.emitCodeArg(self.node.location, .OP_PROPERTY, member_name_constant); - } - } - } - - // Pop object - try codegen.emitOpCode(self.node.location, .OP_POP); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.writeAll("{\"node\": \"ObjectDeclaration\", \"methods\": {"); - - var it = self.methods.iterator(); - var i: usize = 0; - while (it.next()) |kv| { - const member = kv.value_ptr.*; - - try out.print("\"{s}\": ", .{kv.key_ptr.*}); - - try member.toJson(member, out); - - if (i < self.methods.count() - 1) { - try out.writeAll(","); - } - - i += 1; - } - - try out.writeAll("}, \"members\": {"); - - var it2 = self.properties_type.iterator(); - i = 0; - while (it2.next()) |kv| { - try out.print( - "\"{s}\": {{\"type_def\": \"", - .{ - kv.key_ptr.*, - }, - ); - - try kv.value_ptr.*.toString(out); - - try out.print("\"", .{}); - - if (self.docblocks.get(kv.key_ptr.*).?) |docblock| { - var escaped = try escape(global_allocator, docblock.literal_string orelse ""); - defer escaped.deinit(); - try out.print(", \"docblock\": \"{s}\"}}", .{escaped.items}); - } else { - try out.print("}}", .{}); - } - - if (i < self.properties_type.count() - 1) { - try out.writeAll(","); - } - - i += 1; - } - - try out.writeAll("}, "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - const obj_def = node.type_def.?.resolved_type.?.Object; - - try out.writeByteNTimes(' ', depth * 4); - - try out.writeAll("\nobject"); - if (obj_def.conforms_to.count() > 0) { - try out.writeAll("("); - var it = obj_def.conforms_to.iterator(); - var i: usize = 0; - while (it.next()) |kv| : (i += 1) { - try kv.key_ptr.*.toStringUnqualified(out); - - if (i < obj_def.conforms_to.count() - 1) { - try out.writeAll(", "); - } - } - try out.writeAll(") "); - } - - try out.print(" {s} {{", .{obj_def.name.string}); - - if (self.fields.len > 0) { - try out.writeAll("\n"); - } - - for (self.fields) |field_name| { - const method = self.methods.get(field_name); - const property = obj_def.fields.get(field_name); - - try out.writeByteNTimes(' ', (depth + 1) * 4); - - if (method == null) { - try property.?.toStringUnqualified(out); - try out.print(" {s}", .{field_name}); - - if (self.properties.get(field_name)) |default_expr| { - if (default_expr) |expr| { - try out.writeAll(" = "); - try expr.render(expr, out, depth + 1); - } - } - - try out.writeAll(","); - } else { - try method.?.render(method.?, out, depth + 1); - } - - try out.writeAll("\n"); - } - - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("}\n"); - } - - pub fn init(allocator: Allocator) Self { - return Self{ - .properties = std.StringHashMap(*ParseNode).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.properties.deinit(); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.node_type != .ObjectDeclaration) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ProtocolDeclarationNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .ProtocolDeclaration, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - slot: usize, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - try codegen.emitConstant(node.location, node.type_def.?.toValue()); - try codegen.emitCodeArg(node.location, .OP_DEFINE_GLOBAL, @intCast(self.slot)); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - try out.writeAll("{\"node\": \"ProtocolDeclaration\", "); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - const protocol_def = node.type_def.?.resolved_type.?.Protocol; - - try out.writeByteNTimes(' ', depth * 4); - - try out.print("protocol {s} {{\n", .{protocol_def.name.string}); - var it = protocol_def.methods.iterator(); - while (it.next()) |kv| { - try out.writeByteNTimes(' ', (depth + 1) * 4); - try kv.value_ptr.*.toStringUnqualified(out); - try out.writeAll(";\n"); - } - try out.writeByteNTimes(' ', depth * 4); - try out.writeAll("}\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.node_type != .ProtocolDeclaration) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ExportNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Export, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - identifier: ?Token = null, - alias: ?Token = null, - - declaration: ?*ParseNode = null, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - const codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - if (self.declaration) |decl| { - _ = try decl.toByteCode(decl, codegen, breaks); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"Export\", ", .{}); - - if (self.identifier) |identifier| { - try out.print("\"identifier\": \"{s}\", ", .{identifier.lexeme}); - } - - if (self.alias) |alias| { - try out.print("\"alias\": \"{s}\", ", .{alias.lexeme}); - } - - if (self.declaration) |decl| { - try out.print("\"declaration\": ", .{}); - - try decl.toJson(@ptrCast(decl), out); - - try out.print(", ", .{}); - } - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - if (self.identifier) |identifier| { - try out.print("export {s}", .{identifier.lexeme}); - if (self.alias) |alias| { - try out.print(" as {s}", .{alias.lexeme}); - } - try out.writeAll(";\n"); - } else { - try out.print("export ", .{}); - try self.declaration.?.render(@ptrCast(self.declaration), out, depth); - } - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.node_type != .Export) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const TypeExpressionNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .TypeExpression, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - value: Value, - - fn constant(_: *anyopaque) bool { - return true; - } - - fn val(nodePtr: *anyopaque, _: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - return Self.cast(node).?.value; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - const codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - try codegen.emitConstant(self.node.location, self.value); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"TypeExpression\", \"value\": \"", .{}); - - try ObjTypeDef.cast(self.value.obj()).?.toString(out); - - try out.print("\", ", .{}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.print("<", .{}); - - try ObjTypeDef.cast(self.value.obj()).?.toString(out); - - try out.writeAll(">"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.node_type != .TypeExpression) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const TypeOfExpressionNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .TypeOfExpression, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - expression: *ParseNode, - - fn constant(nodePtr: *anyopaque) bool { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - return self.expression.isConstant(self.expression); - } - - fn val(nodePtr: *anyopaque, gc: *GarbageCollector) anyerror!Value { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - if (node.isConstant(node)) { - const self = Self.cast(node).?; - - const expr = try self.expression.toValue(self.expression, gc); - - return (try Value.typeOf(expr, gc)).toValue(); - } - - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - const codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - _ = try self.expression.toByteCode(self.expression, codegen, breaks); - - try codegen.emitOpCode(node.location, .OP_TYPEOF); - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"TypeOfExpression\", \"expression\": \"", .{}); - - try self.expression.toJson(self.expression, out); - - try out.print(", ", .{}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.print("<", .{}); - - try self.expression.render(self.expression, out, depth); - - try out.writeAll(">"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.node_type != .TypeOfExpression) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ZdefNode = struct { - const Self = @This(); - - pub const ZdefElement = struct { - fn_ptr: ?*anyopaque = null, - obj_native: ?*ObjNative = null, - // TODO: On the stack, do we free it at some point? - zdef: *const FFI.Zdef, - slot: usize, - }; - - node: ParseNode = .{ - .node_type = .Zdef, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - lib_name: Token, - source: Token, - elements: []ZdefElement, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, _: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - const self = Self.cast(node).?; - - for (self.elements) |*element| { - // Generate ObjNative wrapper of actual zdef - switch (element.zdef.type_def.def_type) { - .Function => { - if (element.obj_native == null) { - var timer = std.time.Timer.start() catch unreachable; - - element.obj_native = try codegen.mir_jit.?.compileZdef(element); - - codegen.mir_jit.?.jit_time += timer.read(); - - try codegen.emitConstant(node.location, element.obj_native.?.toValue()); - } - }, - .ForeignContainer => { - var timer = std.time.Timer.start() catch unreachable; - - try codegen.mir_jit.?.compileZdefContainer(element); - - codegen.mir_jit.?.jit_time += timer.read(); - - try codegen.emitConstant(node.location, element.zdef.type_def.toValue()); - }, - else => unreachable, - } - try codegen.emitCodeArg(node.location, .OP_DEFINE_GLOBAL, @intCast(element.slot)); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"Zdef\", \"lib_name\": {s}, ", .{self.lib_name.lexeme}); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.print( - "zdef({s}, {s}{s}", - .{ - if (std.mem.indexOf(u8, self.source.lexeme, "\n") != null) - "`" - else - "\"", - self.lib_name.lexeme, - self.source.lexeme, - }, - ); - - try out.print( - "{s});\n", - .{ - if (std.mem.indexOf(u8, self.source.lexeme, "\n") != null) - "`" - else - "\"", - }, - ); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.node_type != .Zdef) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -pub const ImportNode = struct { - const Self = @This(); - - node: ParseNode = .{ - .node_type = .Import, - .toJson = stringify, - .toByteCode = generate, - .toValue = val, - .isConstant = constant, - .render = render, - }, - - imported_symbols: ?std.StringHashMap(void) = null, - prefix: ?Token = null, - path: Token, - import: ?Parser.ScriptImport, - - fn constant(_: *anyopaque) bool { - return false; - } - - fn val(_: *anyopaque, _: *GarbageCollector) anyerror!Value { - return GenError.NotConstant; - } - - fn generate(nodePtr: *anyopaque, codegenPtr: *anyopaque, breaks: ?*std.ArrayList(usize)) anyerror!?*ObjFunction { - var codegen: *CodeGen = @ptrCast(@alignCast(codegenPtr)); - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.synchronize(codegen)) { - return null; - } - - var self = Self.cast(node).?; - - if (self.import) |import| { - try codegen.emitConstant( - node.location, - import.absolute_path.toValue(), - ); - _ = try import.function.toByteCode(import.function, codegen, breaks); - // FIXME: avoid generating the same import function more than once! - try codegen.emitOpCode(self.node.location, .OP_IMPORT); - } - - try node.patchOptJumps(codegen); - try node.endScope(codegen); - - return null; - } - - fn stringify(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer) RenderError!void { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - var self = Self.cast(node).?; - - try out.print("{{\"node\": \"Import\", \"path\": \"{s}\"", .{self.path.literal_string.?}); - - if (self.prefix) |prefix| { - try out.print(",\"prefix\": \"{s}\"", .{prefix.lexeme}); - } - - try out.writeAll(",\"imported_symbols\": ["); - if (self.imported_symbols) |imported_symbols| { - var key_it = imported_symbols.keyIterator(); - var total = imported_symbols.count(); - var count: usize = 0; - while (key_it.next()) |symbol| { - try out.print("\"{s}\"", .{symbol}); - - if (count < total - 1) { - try out.writeAll(","); - } - - count += 1; - } - } - try out.writeAll("]"); - - if (self.import) |import| { - try out.writeAll(",\"import\": "); - try import.function.toJson(import.function, out); - } - - try out.writeAll(","); - - try ParseNode.stringify(node, out); - - try out.writeAll("}"); - } - - fn render(nodePtr: *anyopaque, out: *const std.ArrayList(u8).Writer, depth: usize) RenderError!void { - const node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - const self = Self.cast(node).?; - - try out.writeByteNTimes(' ', depth * 4); - - try out.writeAll("import "); - if (self.imported_symbols) |imported_symbols| { - var it = imported_symbols.iterator(); - var i: usize = 0; - while (it.next()) |kv| : (i += 1) { - try out.writeAll(kv.key_ptr.*); - - if (i < imported_symbols.count() - 1) { - try out.writeAll(", "); - } - } - - try out.writeAll(" from "); - } - try out.print("{s}", .{self.path.lexeme}); - if (self.prefix) |prefix| { - try out.print(" as {s}", .{prefix.lexeme}); - } - - try out.writeAll(";\n"); - } - - pub fn toNode(self: *Self) *ParseNode { - return &self.node; - } - - pub fn cast(nodePtr: *anyopaque) ?*Self { - var node: *ParseNode = @ptrCast(@alignCast(nodePtr)); - - if (node.node_type != .Import) { - return null; - } - - return @fieldParentPtr(Self, "node", node); - } -}; - -fn escape(allocator: Allocator, from: []const u8) !std.ArrayList(u8) { - // Escape \ - var backslash = try std.ArrayList(u8).initCapacity( - allocator, - std.mem.replacementSize( - u8, - from, - "\\", - "\\\\", - ), - ); - defer backslash.deinit(); - backslash.expandToCapacity(); - - _ = std.mem.replace( - u8, - from, - "\\", - "\\\\", - backslash.items, - ); - - // Escape new lines - var newlines = try std.ArrayList(u8).initCapacity( - allocator, - std.mem.replacementSize( - u8, - backslash.items, - "\n", - "\\n", - ), - ); - defer newlines.deinit(); - newlines.expandToCapacity(); - - _ = std.mem.replace( - u8, - backslash.items, - "\n", - "\\n", - newlines.items, - ); - - // Escape " - var quotes = try std.ArrayList(u8).initCapacity( - allocator, - std.mem.replacementSize( - u8, - newlines.items, - "\"", - "\\\"", - ), - ); - defer quotes.deinit(); - quotes.expandToCapacity(); - - _ = std.mem.replace( - u8, - newlines.items, - "\"", - "\\\"", - quotes.items, - ); - - // Escape \r - var r = try std.ArrayList(u8).initCapacity( - allocator, - std.mem.replacementSize( - u8, - quotes.items, - "\r", - "\\r", - ), - ); - defer r.deinit(); - r.expandToCapacity(); - - _ = std.mem.replace( - u8, - quotes.items, - "\r", - "\\r", - r.items, - ); - - // Escape \t - var t = try std.ArrayList(u8).initCapacity( - allocator, - std.mem.replacementSize( - u8, - r.items, - "\t", - "\\t", - ), - ); - t.expandToCapacity(); - - _ = std.mem.replace( - u8, - r.items, - "\t", - "\\t", - t.items, - ); - - return t; -} diff --git a/src/obj.zig b/src/obj.zig index 0a36bc2b..c3724c4f 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -4,34 +4,25 @@ const assert = std.debug.assert; const mem = std.mem; const Allocator = mem.Allocator; const StringHashMap = std.StringHashMap; -const Chunk = @import("chunk.zig").Chunk; +const Chunk = @import("Chunk.zig"); const _vm = @import("vm.zig"); const VM = _vm.VM; const Fiber = _vm.Fiber; -const Parser = @import("parser.zig").Parser; +const Parser = @import("Parser.zig"); const _memory = @import("memory.zig"); const GarbageCollector = _memory.GarbageCollector; const TypeRegistry = _memory.TypeRegistry; -const _value = @import("value.zig"); -const Token = @import("token.zig").Token; +const Value = @import("value.zig").Value; +const Token = @import("Token.zig"); const BuildOptions = @import("build_options"); -const CodeGen = @import("codegen.zig").CodeGen; +const CodeGen = @import("Codegen.zig"); const buzz_api = @import("buzz_api.zig"); -const _node = @import("node.zig"); -const FunctionNode = _node.FunctionNode; const buzz_builtin = @import("builtin.zig"); const ZigType = @import("zigtypes.zig").Type; +const Ast = @import("Ast.zig"); pub const pcre = @import("pcre.zig"); -const Value = _value.Value; -const ValueType = _value.ValueType; -const floatToInteger = _value.floatToInteger; -const valueToString = _value.valueToString; -const valueEql = _value.valueEql; -const valueIs = _value.valueIs; -const valueTypeEql = _value.valueTypeEql; - pub const SerializeError = error{ CircularReference, NotSerializable, @@ -129,7 +120,7 @@ pub const Obj = struct { .EnumInstance => { const enum_instance = self.access(ObjEnumInstance, .EnumInstance, vm.gc).?; - return try enum_instance.enum_ref.cases.items[enum_instance.case].serialize(vm, seen); + return try enum_instance.enum_ref.cases[enum_instance.case].serialize(vm, seen); }, .List => { @@ -288,7 +279,7 @@ pub const Obj = struct { var it = container_def.fields.iterator(); while (it.next()) |kv| { - var dupped = try vm.gc.allocator.dupeZ(u8, kv.key_ptr.*); + const dupped = try vm.gc.allocator.dupeZ(u8, kv.key_ptr.*); defer vm.gc.allocator.free(dupped); try serialized_instance.set( @@ -306,7 +297,7 @@ pub const Obj = struct { } } - pub fn typeOf(self: *Self, gc: *GarbageCollector) anyerror!*ObjTypeDef { + pub fn typeOf(self: *Self, gc: *GarbageCollector) error{ OutOfMemory, NoSpaceLeft }!*ObjTypeDef { return switch (self.obj_type) { .String => try gc.type_registry.getTypeDef(.{ .def_type = .String }), .Pattern => try gc.type_registry.getTypeDef(.{ .def_type = .Pattern }), @@ -359,8 +350,7 @@ pub const Obj = struct { .UpValue => upvalue: { const upvalue: *ObjUpValue = ObjUpValue.cast(self).?; - break :upvalue valueIs( - Value.fromObj(type_def.toObj()), + break :upvalue Value.fromObj(type_def.toObj()).is( upvalue.closed orelse upvalue.location.*, ); }, @@ -369,8 +359,7 @@ pub const Obj = struct { .Map => ObjMap.cast(self).?.type_def.eql(type_def), .Bound => bound: { const bound: *ObjBoundMethod = ObjBoundMethod.cast(self).?; - break :bound valueIs( - Value.fromObj(type_def.toObj()), + break :bound Value.fromObj(type_def.toObj()).is( Value.fromObj(if (bound.closure) |cls| cls.function.toObj() else bound.native.?.toObj()), ); }, @@ -387,7 +376,7 @@ pub const Obj = struct { .Type => type_def.def_type == .Type, .UpValue => uv: { var upvalue: *ObjUpValue = ObjUpValue.cast(self).?; - break :uv valueTypeEql(upvalue.closed orelse upvalue.location.*, type_def); + break :uv (upvalue.closed orelse upvalue.location.*).typeEql(type_def); }, .EnumInstance => ei: { var instance: *ObjEnumInstance = ObjEnumInstance.cast(self).?; @@ -402,7 +391,7 @@ pub const Obj = struct { .Function => ObjFunction.cast(self).?.type_def.eql(type_def), .Closure => ObjClosure.cast(self).?.function.type_def.eql(type_def), .Bound => bound: { - var bound = ObjBoundMethod.cast(self).?; + const bound = ObjBoundMethod.cast(self).?; break :bound if (bound.closure) |cls| cls.function.type_def.eql(type_def) else unreachable; // TODO }, .List => ObjList.cast(self).?.type_def.eql(type_def), @@ -440,7 +429,7 @@ pub const Obj = struct { const self_upvalue: *ObjUpValue = ObjUpValue.cast(self).?; const other_upvalue: *ObjUpValue = ObjUpValue.cast(other).?; - return valueEql(self_upvalue.closed orelse self_upvalue.location.*, other_upvalue.closed orelse other_upvalue.location.*); + return (self_upvalue.closed orelse self_upvalue.location.*).eql(other_upvalue.closed orelse other_upvalue.location.*); }, .EnumInstance => { const self_enum_instance: *ObjEnumInstance = ObjEnumInstance.cast(self).?; @@ -566,8 +555,8 @@ pub const ObjFiber = struct { yield_type: *ObjTypeDef, pub fn mark(self: *SelfFiberDef, gc: *GarbageCollector) !void { - try gc.markObj(self.return_type.toObj()); - try gc.markObj(self.yield_type.toObj()); + try gc.markObj(@constCast(self.return_type.toObj())); + try gc.markObj(@constCast(self.yield_type.toObj())); } }; }; @@ -989,7 +978,7 @@ pub const ObjFunction = struct { upvalue_count: u8 = 0, // So we can JIT the function at runtime - node: *anyopaque, + node: Ast.Node.Index, // How many time the function was called call_count: u128 = 0, @@ -999,11 +988,11 @@ pub const ObjFunction = struct { // JIT compiled function callable by buzz VM native: ?*anyopaque = null, - pub fn init(allocator: Allocator, node: *FunctionNode, name: *ObjString) !Self { + pub fn init(allocator: Allocator, ast: Ast, node: Ast.Node.Index, name: *ObjString) !Self { return Self{ .name = name, .node = node, - .chunk = Chunk.init(allocator), + .chunk = Chunk.init(allocator, ast), }; } @@ -1013,7 +1002,7 @@ pub const ObjFunction = struct { pub fn mark(self: *Self, gc: *GarbageCollector) !void { try gc.markObj(self.name.toObj()); - try gc.markObj(self.type_def.toObj()); + try gc.markObj(@constCast(self.type_def.toObj())); if (BuildOptions.gc_debug) { std.debug.print("MARKING CONSTANTS OF FUNCTION @{} {s}\n", .{ @intFromPtr(self), self.name.string }); } @@ -1048,6 +1037,7 @@ pub const ObjFunction = struct { return_type: *ObjTypeDef, yield_type: *ObjTypeDef, error_types: ?[]const *ObjTypeDef = null, + // TODO: rename 'arguments' parameters: std.AutoArrayHashMap(*ObjString, *ObjTypeDef), // Storing here the defaults means they can only be non-Obj values defaults: std.AutoArrayHashMap(*ObjString, Value), @@ -1066,13 +1056,13 @@ pub const ObjFunction = struct { pub fn mark(self: *FunctionDefSelf, gc: *GarbageCollector) !void { try gc.markObj(self.name.toObj()); try gc.markObj(self.script_name.toObj()); - try gc.markObj(self.return_type.toObj()); - try gc.markObj(self.yield_type.toObj()); + try gc.markObj(@constCast(self.return_type.toObj())); + try gc.markObj(@constCast(self.yield_type.toObj())); var it = self.parameters.iterator(); while (it.next()) |parameter| { try gc.markObj(parameter.key_ptr.*.toObj()); - try gc.markObj(parameter.value_ptr.*.toObj()); + try gc.markObj(@constCast(parameter.value_ptr.*.toObj())); } var it2 = self.defaults.iterator(); @@ -1083,14 +1073,14 @@ pub const ObjFunction = struct { if (self.error_types) |error_types| { for (error_types) |error_item| { - try gc.markObj(error_item.toObj()); + try gc.markObj(@constCast(error_item.toObj())); } } var it3 = self.generic_types.iterator(); while (it3.next()) |kv| { try gc.markObj(kv.key_ptr.*.toObj()); - try gc.markObj(kv.value_ptr.*.toObj()); + try gc.markObj(@constCast(kv.value_ptr.*.toObj())); } } }; @@ -1129,7 +1119,7 @@ pub const ObjObjectInstance = struct { if (self.object) |object| { try gc.markObj(object.toObj()); } - try gc.markObj(self.type_def.toObj()); + try gc.markObj(@constCast(self.type_def.toObj())); var it = self.fields.iterator(); while (it.next()) |kv| { try gc.markObj(kv.key_ptr.*.toObj()); @@ -1231,13 +1221,13 @@ pub const ObjForeignContainer = struct { try gc.markObj(def.qualified_name.toObj()); var it = def.buzz_type.iterator(); while (it.next()) |kv| { - try gc.markObj(kv.value_ptr.*.toObj()); + try gc.markObj(@constCast(kv.value_ptr.*.toObj())); } } }; pub fn mark(self: *Self, gc: *GarbageCollector) !void { - try gc.markObj(self.type_def.toObj()); + try gc.markObj(@constCast(self.type_def.toObj())); } pub inline fn toObj(self: *Self) *Obj { @@ -1296,7 +1286,7 @@ pub const ObjObject = struct { } pub fn mark(self: *Self, gc: *GarbageCollector) !void { - try gc.markObj(self.type_def.toObj()); + try gc.markObj(@constCast(self.type_def.toObj())); try gc.markObj(self.name.toObj()); var it = self.methods.iterator(); while (it.next()) |kv| { @@ -1363,7 +1353,7 @@ pub const ObjObject = struct { var it = self.methods.iterator(); while (it.next()) |kv| { - try gc.markObj(kv.value_ptr.*.toObj()); + try gc.markObj(@constCast(kv.value_ptr.*.toObj())); } } }; @@ -1458,38 +1448,38 @@ pub const ObjObject = struct { var it = self.fields.iterator(); while (it.next()) |kv| { - try gc.markObj(kv.value_ptr.*.toObj()); + try gc.markObj(@constCast(kv.value_ptr.*.toObj())); } var it3 = self.static_fields.iterator(); while (it3.next()) |kv| { - try gc.markObj(kv.value_ptr.*.toObj()); + try gc.markObj(@constCast(kv.value_ptr.*.toObj())); } var it4 = self.methods.iterator(); while (it4.next()) |kv| { - try gc.markObj(kv.value_ptr.*.toObj()); + try gc.markObj(@constCast(kv.value_ptr.*.toObj())); } var it5 = self.placeholders.iterator(); while (it5.next()) |kv| { - try gc.markObj(kv.value_ptr.*.toObj()); + try gc.markObj(@constCast(kv.value_ptr.*.toObj())); } var it6 = self.static_placeholders.iterator(); while (it6.next()) |kv| { - try gc.markObj(kv.value_ptr.*.toObj()); + try gc.markObj(@constCast(kv.value_ptr.*.toObj())); } var it7 = self.conforms_to.iterator(); while (it7.next()) |kv| { - try gc.markObj(kv.key_ptr.*.toObj()); + try gc.markObj(@constCast(kv.key_ptr.*.toObj())); } var it8 = self.generic_types.iterator(); while (it8.next()) |kv| { try gc.markObj(kv.key_ptr.*.toObj()); - try gc.markObj(kv.value_ptr.*.toObj()); + try gc.markObj(@constCast(kv.value_ptr.*.toObj())); } } }; @@ -1520,7 +1510,7 @@ pub const ObjList = struct { for (self.items.items) |value| { try gc.markValue(value); } - try gc.markObj(self.type_def.toObj()); + try gc.markObj(@constCast(self.type_def.toObj())); var it = self.methods.iterator(); while (it.next()) |kv| { try gc.markObj(kv.key_ptr.*.toObj()); @@ -1646,10 +1636,10 @@ pub const ObjList = struct { } pub fn mark(self: *SelfListDef, gc: *GarbageCollector) !void { - try gc.markObj(self.item_type.toObj()); + try gc.markObj(@constCast(self.item_type.toObj())); var it = self.methods.iterator(); while (it.next()) |method| { - try gc.markObj(method.value_ptr.*.toObj()); + try gc.markObj(@constCast(method.value_ptr.*.toObj())); } } @@ -1669,7 +1659,7 @@ pub const ObjList = struct { // `value` arg is of item_type try parameters.put(try parser.gc.copyString("value"), self.item_type); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("append"), @@ -1681,9 +1671,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); + const native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); try self.methods.put("append", native_type); @@ -1694,7 +1684,7 @@ pub const ObjList = struct { // We omit first arg: it'll be OP_SWAPed in and we already parsed it // It's always the list. - var at_type = try parser.gc.type_registry.getTypeDef( + const at_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Integer, .optional = false, @@ -1703,7 +1693,7 @@ pub const ObjList = struct { try parameters.put(try parser.gc.copyString("at"), at_type); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("remove"), @@ -1719,9 +1709,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -1732,9 +1722,9 @@ pub const ObjList = struct { return native_type; } else if (mem.eql(u8, method, "len")) { - var parameters = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator); + const parameters = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("len"), @@ -1750,9 +1740,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -1779,7 +1769,7 @@ pub const ObjList = struct { ), ); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("next"), @@ -1797,9 +1787,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -1838,7 +1828,7 @@ pub const ObjList = struct { var defaults = std.AutoArrayHashMap(*ObjString, Value).init(parser.gc.allocator); try defaults.put(len_str, Value.Null); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("sub"), @@ -1850,9 +1840,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -1870,7 +1860,7 @@ pub const ObjList = struct { try parameters.put(try parser.gc.copyString("needle"), self.item_type); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("indexOf"), @@ -1887,9 +1877,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -1907,7 +1897,7 @@ pub const ObjList = struct { try parameters.put(try parser.gc.copyString("separator"), try parser.gc.type_registry.getTypeDef(.{ .def_type = .String })); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("join"), @@ -1921,9 +1911,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -1949,7 +1939,7 @@ pub const ObjList = struct { // `value` arg is of item_type try parameters.put(try parser.gc.copyString("value"), self.item_type); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("insert"), @@ -1961,15 +1951,15 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); + const native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); try self.methods.put("insert", native_type); return native_type; } else if (mem.eql(u8, method, "pop")) { - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("pop"), @@ -1981,9 +1971,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); + const native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); try self.methods.put("pop", native_type); @@ -1997,7 +1987,7 @@ pub const ObjList = struct { try callback_parameters.put(try parser.gc.copyString("index"), try parser.gc.type_registry.getTypeDef(.{ .def_type = .Integer })); try callback_parameters.put(try parser.gc.copyString("element"), self.item_type); - var callback_method_def = ObjFunction.FunctionDef{ + const callback_method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), // TODO: is this ok? .script_name = try parser.gc.copyString("builtin"), @@ -2009,9 +1999,9 @@ pub const ObjList = struct { .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), }; - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -2025,7 +2015,7 @@ pub const ObjList = struct { callback_type, ); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("forEach"), @@ -2037,9 +2027,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); + const native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); try self.methods.put("forEach", native_type); @@ -2083,9 +2073,9 @@ pub const ObjList = struct { callback_method_def.return_type = generic_type; - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -2099,9 +2089,9 @@ pub const ObjList = struct { callback_type, ); - var new_list_def = ObjList.ListDef.init(parser.gc.allocator, generic_type); + const new_list_def = ObjList.ListDef.init(parser.gc.allocator, generic_type); - var new_list_type = ObjTypeDef.TypeUnion{ .List = new_list_def }; + const new_list_type = ObjTypeDef.TypeUnion{ .List = new_list_def }; var method_def = ObjFunction.FunctionDef{ .id = map_origin, @@ -2123,9 +2113,9 @@ pub const ObjList = struct { generic_type, ); - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); + const native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); try self.methods.put("map", native_type); @@ -2145,7 +2135,7 @@ pub const ObjList = struct { self.item_type, ); - var callback_method_def = ObjFunction.FunctionDef{ + const callback_method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), // TODO: is this ok? .script_name = try parser.gc.copyString("builtin"), @@ -2157,9 +2147,9 @@ pub const ObjList = struct { .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), }; - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -2173,7 +2163,7 @@ pub const ObjList = struct { callback_type, ); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("filter"), @@ -2185,9 +2175,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); + const native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); try self.methods.put("filter", native_type); @@ -2223,7 +2213,7 @@ pub const ObjList = struct { generic_type, ); - var callback_method_def = ObjFunction.FunctionDef{ + const callback_method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), // TODO: is this ok? .script_name = try parser.gc.copyString("builtin"), @@ -2235,9 +2225,9 @@ pub const ObjList = struct { .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), }; - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -2258,7 +2248,7 @@ pub const ObjList = struct { var generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator); try generic_types.put(try parser.gc.copyString("T"), generic_type); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = reduce_origin, .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("reduce"), @@ -2270,9 +2260,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2291,7 +2281,7 @@ pub const ObjList = struct { try callback_parameters.put(try parser.gc.copyString("left"), self.item_type); try callback_parameters.put(try parser.gc.copyString("right"), self.item_type); - var callback_method_def = ObjFunction.FunctionDef{ + const callback_method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), // TODO: is this ok? .script_name = try parser.gc.copyString("builtin"), @@ -2303,9 +2293,9 @@ pub const ObjList = struct { .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), }; - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -2319,7 +2309,7 @@ pub const ObjList = struct { callback_type, ); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("sort"), @@ -2331,9 +2321,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2347,7 +2337,7 @@ pub const ObjList = struct { // We omit first arg: it'll be OP_SWAPed in and we already parsed it // It's always the list. - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("clone"), @@ -2359,9 +2349,9 @@ pub const ObjList = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2460,7 +2450,7 @@ pub const ObjMap = struct { try gc.markObj(kv.value_ptr.*.toObj()); } - try gc.markObj(self.type_def.toObj()); + try gc.markObj(@constCast(self.type_def.toObj())); } pub fn rawNext(self: *Self, key: ?Value) ?Value { @@ -2517,11 +2507,11 @@ pub const ObjMap = struct { } pub fn mark(self: *SelfMapDef, gc: *GarbageCollector) !void { - try gc.markObj(self.key_type.toObj()); - try gc.markObj(self.value_type.toObj()); + try gc.markObj(@constCast(self.key_type.toObj())); + try gc.markObj(@constCast(self.value_type.toObj())); var it = self.methods.iterator(); while (it.next()) |method| { - try gc.markObj(method.value_ptr.*.toObj()); + try gc.markObj(@constCast(method.value_ptr.*.toObj())); } } @@ -2533,7 +2523,7 @@ pub const ObjMap = struct { } if (mem.eql(u8, method, "size")) { - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("size"), @@ -2547,9 +2537,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2567,7 +2557,7 @@ pub const ObjMap = struct { try parameters.put(try parser.gc.copyString("at"), self.key_type); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("remove"), @@ -2583,9 +2573,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2596,16 +2586,16 @@ pub const ObjMap = struct { return native_type; } else if (mem.eql(u8, method, "keys")) { - var list_def: ObjList.ListDef = ObjList.ListDef.init( + const list_def = ObjList.ListDef.init( parser.gc.allocator, self.key_type, ); - var list_def_union: ObjTypeDef.TypeUnion = .{ + const list_def_union: ObjTypeDef.TypeUnion = .{ .List = list_def, }; - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("keys"), @@ -2621,9 +2611,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2634,16 +2624,16 @@ pub const ObjMap = struct { return native_type; } else if (mem.eql(u8, method, "values")) { - var list_def: ObjList.ListDef = ObjList.ListDef.init( + const list_def = ObjList.ListDef.init( parser.gc.allocator, self.value_type, ); - var list_def_union: ObjTypeDef.TypeUnion = .{ + const list_def_union: ObjTypeDef.TypeUnion = .{ .List = list_def, }; - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("values"), @@ -2659,9 +2649,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2680,7 +2670,7 @@ pub const ObjMap = struct { try callback_parameters.put(try parser.gc.copyString("left"), self.key_type); try callback_parameters.put(try parser.gc.copyString("right"), self.key_type); - var callback_method_def = ObjFunction.FunctionDef{ + const callback_method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), // TODO: is this ok? .script_name = try parser.gc.copyString("builtin"), @@ -2692,9 +2682,9 @@ pub const ObjMap = struct { .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), }; - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -2708,7 +2698,7 @@ pub const ObjMap = struct { callback_type, ); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("sort"), @@ -2720,9 +2710,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2743,7 +2733,7 @@ pub const ObjMap = struct { obj_map, ); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("diff"), @@ -2755,9 +2745,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2778,7 +2768,7 @@ pub const ObjMap = struct { obj_map, ); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("intersect"), @@ -2790,9 +2780,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -2817,7 +2807,7 @@ pub const ObjMap = struct { self.value_type, ); - var callback_method_def = ObjFunction.FunctionDef{ + const callback_method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), // TODO: is this ok? .script_name = try parser.gc.copyString("builtin"), @@ -2829,9 +2819,9 @@ pub const ObjMap = struct { .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), }; - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -2845,7 +2835,7 @@ pub const ObjMap = struct { callback_type, ); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("forEach"), @@ -2857,9 +2847,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); + const native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); try self.methods.put("forEach", native_type); @@ -2939,9 +2929,9 @@ pub const ObjMap = struct { &parser.gc.type_registry, ); - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -2955,13 +2945,13 @@ pub const ObjMap = struct { callback_type, ); - var new_map_def = ObjMap.MapDef.init( + const new_map_def = ObjMap.MapDef.init( parser.gc.allocator, generic_key_type, generic_value_type, ); - var new_map_type = ObjTypeDef.TypeUnion{ .Map = new_map_def }; + const new_map_type = ObjTypeDef.TypeUnion{ .Map = new_map_def }; var method_def = ObjFunction.FunctionDef{ .id = map_origin, @@ -2988,9 +2978,9 @@ pub const ObjMap = struct { generic_value_type, ); - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -3015,7 +3005,7 @@ pub const ObjMap = struct { self.value_type, ); - var callback_method_def = ObjFunction.FunctionDef{ + const callback_method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), // TODO: is this ok? .script_name = try parser.gc.copyString("builtin"), @@ -3027,9 +3017,9 @@ pub const ObjMap = struct { .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), }; - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -3043,7 +3033,7 @@ pub const ObjMap = struct { callback_type, ); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("filter"), @@ -3055,9 +3045,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); + const native_type = try parser.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type }); try self.methods.put("filter", native_type); @@ -3093,7 +3083,7 @@ pub const ObjMap = struct { generic_type, ); - var callback_method_def = ObjFunction.FunctionDef{ + const callback_method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), // TODO: is this ok? .script_name = try parser.gc.copyString("builtin"), @@ -3105,9 +3095,9 @@ pub const ObjMap = struct { .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator), }; - var callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; + const callback_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = callback_method_def }; - var callback_type = try parser.gc.type_registry.getTypeDef( + const callback_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = callback_resolved_type, @@ -3128,7 +3118,7 @@ pub const ObjMap = struct { var generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(parser.gc.allocator); try generic_types.put(try parser.gc.copyString("T"), generic_type); - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = reduce_origin, .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("reduce"), @@ -3140,9 +3130,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -3156,7 +3146,7 @@ pub const ObjMap = struct { // We omit first arg: it'll be OP_SWAPed in and we already parsed it // It's always the list. - var method_def = ObjFunction.FunctionDef{ + const method_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .script_name = try parser.gc.copyString("builtin"), .name = try parser.gc.copyString("clone"), @@ -3168,9 +3158,9 @@ pub const ObjMap = struct { .function_type = .Extern, }; - var resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; + const resolved_type: ObjTypeDef.TypeUnion = .{ .Function = method_def }; - var native_type = try parser.gc.type_registry.getTypeDef( + const native_type = try parser.gc.type_registry.getTypeDef( ObjTypeDef{ .def_type = .Function, .resolved_type = resolved_type, @@ -3197,20 +3187,22 @@ pub const ObjEnum = struct { type_def: *ObjTypeDef, name: *ObjString, - cases: std.ArrayList(Value), + cases: []Value, + allocator: Allocator, pub fn init(allocator: Allocator, def: *ObjTypeDef) Self { return Self{ + .allocator = allocator, .type_def = def, .name = def.resolved_type.?.Enum.name, - .cases = std.ArrayList(Value).init(allocator), + .cases = undefined, }; } pub fn mark(self: *Self, gc: *GarbageCollector) !void { try gc.markObj(self.name.toObj()); - try gc.markObj(self.type_def.toObj()); - for (self.cases.items) |case| { + try gc.markObj(@constCast(self.type_def.toObj())); + for (self.cases) |case| { try gc.markValue(case); } } @@ -3219,7 +3211,7 @@ pub const ObjEnum = struct { if (enum_case) |case| { assert(case.enum_ref == self); - if (case.case == self.cases.items.len - 1) { + if (case.case == self.cases.len - 1) { return null; } @@ -3236,7 +3228,7 @@ pub const ObjEnum = struct { } pub fn deinit(self: *Self) void { - self.cases.deinit(); + self.allocator.free(self.cases); } pub inline fn toObj(self: *Self) *Obj { @@ -3278,7 +3270,7 @@ pub const ObjEnum = struct { pub fn mark(self: *EnumDefSelf, gc: *GarbageCollector) !void { try gc.markObj(self.name.toObj()); try gc.markObj(self.qualified_name.toObj()); - try gc.markObj(self.enum_type.toObj()); + try gc.markObj(@constCast(self.enum_type.toObj())); } }; }; @@ -3308,7 +3300,7 @@ pub const ObjEnumInstance = struct { } pub fn value(self: *Self) Value { - return self.enum_ref.cases.items[self.case]; + return self.enum_ref.cases[self.case]; } }; @@ -3349,7 +3341,7 @@ pub const ObjBoundMethod = struct { pub const ObjTypeDef = struct { const Self = @This(); - pub const Type = enum { + pub const Type = enum(u8) { Any, Bool, Float, @@ -3419,9 +3411,9 @@ pub const ObjTypeDef = struct { pub fn mark(self: *Self, gc: *GarbageCollector) !void { if (self.resolved_type) |*resolved| { if (resolved.* == .ObjectInstance) { - try gc.markObj(resolved.ObjectInstance.toObj()); + try gc.markObj(@constCast(resolved.ObjectInstance.toObj())); } else if (resolved.* == .EnumInstance) { - try gc.markObj(resolved.EnumInstance.toObj()); + try gc.markObj(@constCast(resolved.EnumInstance.toObj())); } else if (resolved.* == .Object) { try resolved.Object.mark(gc); } else if (resolved.* == .Protocol) { @@ -3446,12 +3438,12 @@ pub const ObjTypeDef = struct { pub fn populateGenerics( self: *Self, - where: Token, + where: Ast.TokenIndex, origin: ?usize, generics: []*Self, type_registry: *TypeRegistry, visited: ?*std.AutoHashMap(*Self, void), - ) VM.Error!*Self { + ) !*Self { var visited_nodes = if (visited == null) std.AutoHashMap(*Self, void).init(type_registry.gc.allocator) else @@ -3599,7 +3591,7 @@ pub const ObjTypeDef = struct { const resolved_type_union = ObjTypeDef.TypeUnion{ .Object = resolved }; - var new_object = ObjTypeDef{ + const new_object = ObjTypeDef{ .def_type = .Object, .optional = self.optional, .resolved_type = resolved_type_union, @@ -3625,7 +3617,7 @@ pub const ObjTypeDef = struct { ); } - var new_list_def = ObjList.ListDef{ + const new_list_def = ObjList.ListDef{ .item_type = try (try old_list_def.item_type.populateGenerics( where, origin, @@ -3636,7 +3628,7 @@ pub const ObjTypeDef = struct { .methods = methods, }; - var new_resolved = ObjTypeDef.TypeUnion{ .List = new_list_def }; + const new_resolved = ObjTypeDef.TypeUnion{ .List = new_list_def }; const new_list = ObjTypeDef{ .def_type = .List, @@ -3664,7 +3656,7 @@ pub const ObjTypeDef = struct { ); } - var new_map_def = ObjMap.MapDef{ + const new_map_def = ObjMap.MapDef{ .key_type = try old_map_def.key_type.populateGenerics( where, origin, @@ -3726,7 +3718,7 @@ pub const ObjTypeDef = struct { } } - var new_fun_def = ObjFunction.FunctionDef{ + const new_fun_def = ObjFunction.FunctionDef{ .id = ObjFunction.FunctionDef.nextId(), .name = old_fun_def.name, .script_name = old_fun_def.script_name, @@ -3876,7 +3868,7 @@ pub const ObjTypeDef = struct { if (object_def.anonymous) { try writer.writeAll("obj{ "); var it = object_def.fields.iterator(); - var count = object_def.fields.count(); + const count = object_def.fields.count(); var i: usize = 0; while (it.next()) |kv| { try kv.value_ptr.*.toStringRaw(writer, qualified); @@ -3942,7 +3934,7 @@ pub const ObjTypeDef = struct { if (object_def.anonymous) { try writer.writeAll(".{ "); var it = object_def.fields.iterator(); - var count = object_def.fields.count(); + const count = object_def.fields.count(); var i: usize = 0; while (it.next()) |kv| { try kv.value_ptr.*.toStringRaw(writer, qualified); @@ -4106,7 +4098,7 @@ pub const ObjTypeDef = struct { } pub inline fn toObj(self: *Self) *Obj { - return &self.obj; + return @constCast(&self.obj); } pub inline fn toValue(self: *Self) Value { @@ -4126,7 +4118,7 @@ pub const ObjTypeDef = struct { var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ .Placeholder = PlaceholderDef.init( allocator, - self.resolved_type.?.Placeholder.where.clone(), + self.resolved_type.?.Placeholder.where, ), }; placeholder_resolved_type.Placeholder.name = self.resolved_type.?.Placeholder.name; @@ -4150,10 +4142,10 @@ pub const ObjTypeDef = struct { return self; } - var instance_type = try type_registry.getTypeDef( + const instance_type = try type_registry.getTypeDef( switch (self.def_type) { .Object => object: { - var resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ + const resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ .ObjectInstance = try self.cloneNonOptional(type_registry), }; @@ -4164,7 +4156,7 @@ pub const ObjTypeDef = struct { }; }, .Protocol => protocol: { - var resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ + const resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ .ProtocolInstance = try self.cloneNonOptional(type_registry), }; @@ -4175,7 +4167,7 @@ pub const ObjTypeDef = struct { }; }, .Enum => enum_instance: { - var resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ + const resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ .EnumInstance = try self.cloneNonOptional(type_registry), }; @@ -4189,7 +4181,7 @@ pub const ObjTypeDef = struct { var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ .Placeholder = PlaceholderDef.init( allocator, - self.resolved_type.?.Placeholder.where.clone(), + self.resolved_type.?.Placeholder.where, ), }; placeholder_resolved_type.Placeholder.name = self.resolved_type.?.Placeholder.name; @@ -4408,7 +4400,7 @@ pub fn objToString(writer: *const std.ArrayList(u8).Writer, obj: *Obj) (Allocato .UpValue => { const upvalue: *ObjUpValue = ObjUpValue.cast(obj).?; - try valueToString(writer, upvalue.closed orelse upvalue.location.*); + try (upvalue.closed orelse upvalue.location.*).toString(writer); }, .Closure => try writer.print("closure: 0x{x} `{s}`", .{ @intFromPtr(ObjClosure.cast(obj).?), @@ -4478,8 +4470,8 @@ pub fn objToString(writer: *const std.ArrayList(u8).Writer, obj: *Obj) (Allocato ObjEnum.cast(obj).?.name.string, }), .EnumInstance => enum_instance: { - var instance: *ObjEnumInstance = ObjEnumInstance.cast(obj).?; - var enum_: *ObjEnum = instance.enum_ref; + const instance: *ObjEnumInstance = ObjEnumInstance.cast(obj).?; + const enum_: *ObjEnum = instance.enum_ref; break :enum_instance try writer.print("{s}.{s}", .{ enum_.name.string, @@ -4490,28 +4482,28 @@ pub fn objToString(writer: *const std.ArrayList(u8).Writer, obj: *Obj) (Allocato const bound: *ObjBoundMethod = ObjBoundMethod.cast(obj).?; if (bound.closure) |closure| { - var closure_name: []const u8 = closure.function.name.string; + const closure_name: []const u8 = closure.function.name.string; try writer.writeAll("bound method: "); - try valueToString(writer, bound.receiver); + try (bound.receiver).toString(writer); try writer.print(" to {s}", .{closure_name}); } else { assert(bound.native != null); try writer.writeAll("bound method: "); - try valueToString(writer, bound.receiver); + try (bound.receiver).toString(writer); try writer.print(" to native 0x{}", .{@intFromPtr(bound.native.?)}); } }, .Native => { - var native: *ObjNative = ObjNative.cast(obj).?; + const native: *ObjNative = ObjNative.cast(obj).?; try writer.print("native: 0x{x}", .{@intFromPtr(native)}); }, .UserData => { - var userdata: *ObjUserData = ObjUserData.cast(obj).?; + const userdata: *ObjUserData = ObjUserData.cast(obj).?; try writer.print("userdata: 0x{x}", .{userdata.userdata}); }, @@ -4545,7 +4537,7 @@ pub const PlaceholderDef = struct { }; name: ?*ObjString = null, - where: Token, // Where the placeholder was created + where: Ast.TokenIndex, // Where the placeholder was created // When accessing/calling/subscrit/assign a placeholder we produce another. We keep them linked so we // can trace back the root of the unknown type. parent: ?*ObjTypeDef = null, @@ -4557,9 +4549,9 @@ pub const PlaceholderDef = struct { // If the placeholder is a function return, we need to remember eventual generic types defined in that call resolved_generics: ?[]*ObjTypeDef = null, - pub fn init(allocator: Allocator, where: Token) Self { + pub fn init(allocator: Allocator, where: Ast.TokenIndex) Self { return Self{ - .where = where.clone(), + .where = where, .children = std.ArrayList(*ObjTypeDef).init(allocator), }; } diff --git a/src/parser.zig b/src/parser.zig deleted file mode 100644 index 2b71cbb3..00000000 --- a/src/parser.zig +++ /dev/null @@ -1,6427 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const mem = std.mem; -const Allocator = mem.Allocator; -const assert = std.debug.assert; - -pub const pcre = @import("pcre.zig"); - -const _obj = @import("obj.zig"); -const _node = @import("node.zig"); -const _token = @import("token.zig"); -const _vm = @import("vm.zig"); -const RunFlavor = _vm.RunFlavor; -const _value = @import("value.zig"); -const _scanner = @import("scanner.zig"); -const _chunk = @import("chunk.zig"); -const BuildOptions = @import("build_options"); -const StringParser = @import("string_parser.zig").StringParser; -const GarbageCollector = @import("memory.zig").GarbageCollector; -const Reporter = @import("reporter.zig"); -const FFI = @import("ffi.zig"); - -const Value = _value.Value; -const ValueType = _value.ValueType; -const valueToHashable = _value.valueToHashable; -const hashableToValue = _value.hashableToValue; -const valueToString = _value.valueToString; -const valueEql = _value.valueEql; -const ObjType = _obj.ObjType; -const Obj = _obj.Obj; -const ObjNative = _obj.ObjNative; -const ObjString = _obj.ObjString; -const ObjUpValue = _obj.ObjUpValue; -const ObjClosure = _obj.ObjClosure; -const ObjFunction = _obj.ObjFunction; -const ObjFiber = _obj.ObjFiber; -const ObjObjectInstance = _obj.ObjObjectInstance; -const ObjObject = _obj.ObjObject; -const ObjectDef = _obj.ObjectDef; -const ObjList = _obj.ObjList; -const ObjMap = _obj.ObjMap; -const ObjEnum = _obj.ObjEnum; -const ObjEnumInstance = _obj.ObjEnumInstance; -const ObjTypeDef = _obj.ObjTypeDef; -const ObjPattern = _obj.ObjPattern; -const PlaceholderDef = _obj.PlaceholderDef; -const Token = _token.Token; -const TokenType = _token.TokenType; -const Scanner = _scanner.Scanner; -const NativeFn = _obj.NativeFn; -const FunctionType = _obj.ObjFunction.FunctionType; -const SlotType = _node.SlotType; -const ParseNodeType = _node.ParseNodeType; -const ParseNode = _node.ParseNode; -const NamedVariableNode = _node.NamedVariableNode; -const IntegerNode = _node.IntegerNode; -const FloatNode = _node.FloatNode; -const BooleanNode = _node.BooleanNode; -const StringLiteralNode = _node.StringLiteralNode; -const StringNode = _node.StringNode; -const NullNode = _node.NullNode; -const VoidNode = _node.VoidNode; -const PatternNode = _node.PatternNode; -const ListNode = _node.ListNode; -const MapNode = _node.MapNode; -const UnwrapNode = _node.UnwrapNode; -const ForceUnwrapNode = _node.ForceUnwrapNode; -const IsNode = _node.IsNode; -const UnaryNode = _node.UnaryNode; -const BinaryNode = _node.BinaryNode; -const SubscriptNode = _node.SubscriptNode; -const FunctionNode = _node.FunctionNode; -const CallNode = _node.CallNode; -const AsyncCallNode = _node.AsyncCallNode; -const ResumeNode = _node.ResumeNode; -const ResolveNode = _node.ResolveNode; -const YieldNode = _node.YieldNode; -const VarDeclarationNode = _node.VarDeclarationNode; -const ExpressionNode = _node.ExpressionNode; -const GroupingNode = _node.GroupingNode; -const EnumNode = _node.EnumNode; -const ThrowNode = _node.ThrowNode; -const BreakNode = _node.BreakNode; -const ContinueNode = _node.ContinueNode; -const IfNode = _node.IfNode; -const ReturnNode = _node.ReturnNode; -const ForNode = _node.ForNode; -const ForEachNode = _node.ForEachNode; -const WhileNode = _node.WhileNode; -const DoUntilNode = _node.DoUntilNode; -const BlockNode = _node.BlockNode; -const DotNode = _node.DotNode; -const AsNode = _node.AsNode; -const FunDeclarationNode = _node.FunDeclarationNode; -const ObjectInitNode = _node.ObjectInitNode; -const ObjectDeclarationNode = _node.ObjectDeclarationNode; -const ProtocolDeclarationNode = _node.ProtocolDeclarationNode; -const ExportNode = _node.ExportNode; -const ImportNode = _node.ImportNode; -const RangeNode = _node.RangeNode; -const TryNode = _node.TryNode; -const ZdefNode = _node.ZdefNode; -const TypeExpressionNode = _node.TypeExpressionNode; -const TypeOfExpressionNode = _node.TypeOfExpressionNode; -const GenericResolveNode = _node.GenericResolveNode; -const ParsedArg = _node.ParsedArg; -const OpCode = _chunk.OpCode; -const TypeRegistry = _obj.TypeRegistry; - -extern fn dlerror() [*:0]u8; - -pub fn default_buzz_prefix() []const u8 { - // todo: maybe it's better to have multiple search paths? - unreachable; -} - -var _buzz_path_buffer: [4096]u8 = undefined; -pub fn buzz_prefix() []const u8 { - if (std.os.getenv("BUZZ_PATH")) |buzz_path| return buzz_path; - const path = std.fs.selfExePath(&_buzz_path_buffer) catch return default_buzz_prefix(); - const path1 = std.fs.path.dirname(path) orelse default_buzz_prefix(); - const path2 = std.fs.path.dirname(path1) orelse default_buzz_prefix(); - return path2; -} - -var _buzz_path_buffer2: [4096]u8 = undefined; -/// the returned string can be used only until next call to this function -pub fn buzz_lib_path() []const u8 { - const path2 = buzz_prefix(); - const sep = std.fs.path.sep_str; - return std.fmt.bufPrint(&_buzz_path_buffer2, "{s}" ++ sep ++ "lib" ++ sep ++ "buzz", .{path2}) catch unreachable; -} - -pub const CompileError = error{ - Unrecoverable, - Recoverable, -}; - -pub const ParserState = struct { - const Self = @This(); - - current_token: ?Token = null, - previous_token: ?Token = null, - - // Most of the time 1 token in advance is enough, but in some special cases we want to be able to look - // some arbitrary number of token ahead - ahead: std.ArrayList(Token), - - pub fn init(allocator: Allocator) Self { - return .{ - .ahead = std.ArrayList(Token).init(allocator), - }; - } - - pub fn deinit(self: *Self) void { - self.ahead.deinit(); - } -}; - -pub const Local = struct { - name: *ObjString, - location: Token, - type_def: *ObjTypeDef, - depth: i32, - is_captured: bool, - constant: bool, - referenced: bool = false, - - pub fn isReferenced(self: Local) bool { - // zig fmt: off - return self.referenced - or self.type_def.def_type == .Void - or self.type_def.def_type == .Placeholder - or self.name.string[0] == '$' - or (self.name.string[0] == '_' and self.name.string.len == 1); - // zig fmt: on - } -}; - -pub const Global = struct { - prefix: ?[]const u8 = null, - name: *ObjString, // TODO: do i need to mark those? does it need to be an objstring? - location: Token, - type_def: *ObjTypeDef, - initialized: bool = false, - exported: bool = false, - export_alias: ?[]const u8 = null, - hidden: bool = false, - constant: bool, - referenced: bool = false, - // When resolving a placeholder, the start of the resolution is the global - // If `constant` is true, we can search for any `.Assignment` link and fail then. - - pub fn isReferenced(self: Global) bool { - const function_type = if (self.type_def.def_type == .Function) - self.type_def.resolved_type.?.Function.function_type - else - null; - - // zig fmt: off - return self.referenced - or self.type_def.def_type == .Void - or self.type_def.def_type == .Placeholder - or (function_type != null and (function_type == .Extern or function_type == .Abstract or function_type == .EntryPoint or function_type == .ScriptEntryPoint or function_type != .Repl)) - or self.name.string[0] == '$' - or (self.name.string[0] == '_' and self.name.string.len == 1) - or self.exported; - // zig fmt: on - } -}; - -pub const UpValue = struct { index: u8, is_local: bool }; - -pub const Frame = struct { - const Self = @This(); - - enclosing: ?*Frame = null, - locals: [255]Local, - local_count: u8 = 0, - upvalues: [255]UpValue, - upvalue_count: u8 = 0, - scope_depth: u32 = 0, - // If false, `return` was omitted or within a conditionned block (if, loop, etc.) - // We only count `return` emitted within the scope_depth 0 of the current function or unconditionned else statement - function_node: *FunctionNode, - function: ?*ObjFunction = null, - generics: ?*std.AutoArrayHashMap(*ObjString, *ObjTypeDef) = null, - constants: std.ArrayList(Value), - - in_try: bool = false, - - pub fn resolveGeneric(self: Self, name: *ObjString) ?*ObjTypeDef { - if (self.generics) |generics| { - if (generics.get(name)) |type_def| { - return type_def; - } - } - - return if (self.enclosing) |enclosing| - enclosing.resolveGeneric(name) - else - null; - } -}; - -pub const ObjectFrame = struct { - name: Token, - type_def: *ObjTypeDef, - generics: ?*std.AutoArrayHashMap(*ObjString, *ObjTypeDef) = null, -}; - -pub const Parser = struct { - const Self = @This(); - - pub const DeclarationTerminator = enum { - Comma, - OptComma, - Semicolon, - Nothing, - }; - - pub const LoopType = enum { - While, - Do, - For, - ForEach, - }; - - pub const LoopScope = struct { - loop_type: LoopType, - loop_body_scope: usize, - }; - - pub const Precedence = enum { - None, - Assignment, // =, -=, +=, *=, /= - IsAs, // is, as? - Or, // or - And, // and - Equality, // ==, != - Comparison, // >=, <=, >, < - Term, // +, - - NullCoalescing, // ??, typeof - Bitwise, // \, &, ^ - Shift, // >>, << - Factor, // /, *, % - Unary, // +, ++, -, --, ! - Call, // call(), dot.ref, sub[script], optUnwrap? - Primary, // literal, (grouped expression), identifier, [, alist], {, ...} - }; - - const ParseFn = *const fn (*Parser, bool) anyerror!*ParseNode; - const InfixParseFn = *const fn (*Parser, bool, *ParseNode) anyerror!*ParseNode; - - const ParseRule = struct { - prefix: ?ParseFn, - infix: ?InfixParseFn, - precedence: Precedence, - }; - - const search_paths = [_][]const u8{ - "./?.!", - "./?/main.!", - "./?/src/main.!", - "./?/src/?.!", - "/usr/share/buzz/?.!", - "/usr/share/buzz/?/main.!", - "/usr/share/buzz/?/src/main.!", - "/usr/local/share/buzz/?/src/?.!", - "/usr/local/share/buzz/?.!", - "/usr/local/share/buzz/?/main.!", - "/usr/local/share/buzz/?/src/main.!", - "/usr/local/share/buzz/?/src/?.!", - "$/?.!", - "$/?/main.!", - "$/?/src/?.!", - "$/?/src/main.!", - }; - - const lib_search_paths = [_][]const u8{ - "./lib?.!", - "./?/src/lib?.!", - "/usr/share/buzz/lib?.!", - "/usr/share/buzz/?/src/lib?.!", - "/usr/share/local/buzz/lib?.!", - "/usr/share/local/buzz/?/src/lib?.!", - "$/lib?.!", - "$/?/src/lib?.!", - }; - - const zdef_search_paths = [_][]const u8{ - "./?.!", - "/usr/lib/?.!", - "/usr/local/lib/?.!", - "./lib?.!", - "/usr/lib/lib?.!", - "/usr/local/lib/lib?.!", - }; - - const rules = [_]ParseRule{ - .{ .prefix = null, .infix = null, .precedence = .None }, // Pipe - .{ .prefix = list, .infix = subscript, .precedence = .Call }, // LeftBracket - .{ .prefix = null, .infix = null, .precedence = .None }, // RightBracket - .{ .prefix = grouping, .infix = call, .precedence = .Call }, // LeftParen - .{ .prefix = null, .infix = null, .precedence = .None }, // RightParen - .{ .prefix = map, .infix = objectInit, .precedence = .Primary }, // LeftBrace - .{ .prefix = null, .infix = null, .precedence = .None }, // RightBrace - .{ .prefix = anonymousObjectInit, .infix = dot, .precedence = .Call }, // Dot - .{ .prefix = null, .infix = null, .precedence = .None }, // Comma - .{ .prefix = null, .infix = null, .precedence = .None }, // Semicolon - .{ .prefix = null, .infix = binary, .precedence = .Comparison }, // Greater - .{ .prefix = typeExpression, .infix = binary, .precedence = .Comparison }, // Less - .{ .prefix = null, .infix = binary, .precedence = .Term }, // Plus - .{ .prefix = unary, .infix = binary, .precedence = .Term }, // Minus - .{ .prefix = null, .infix = binary, .precedence = .Factor }, // Star - .{ .prefix = null, .infix = binary, .precedence = .Factor }, // Slash - .{ .prefix = null, .infix = binary, .precedence = .Factor }, // Percent - .{ .prefix = null, .infix = unwrap, .precedence = .Call }, // Question - .{ .prefix = unary, .infix = forceUnwrap, .precedence = .Call }, // Bang - .{ .prefix = null, .infix = null, .precedence = .None }, // Colon - .{ .prefix = null, .infix = genericResolve, .precedence = .Call }, // DoubleColon - .{ .prefix = null, .infix = null, .precedence = .None }, // Equal - .{ .prefix = null, .infix = binary, .precedence = .Equality }, // EqualEqual - .{ .prefix = null, .infix = binary, .precedence = .Equality }, // BangEqual - .{ .prefix = null, .infix = null, .precedence = .None }, // BangGreater - .{ .prefix = null, .infix = binary, .precedence = .Comparison }, // GreaterEqual - .{ .prefix = null, .infix = binary, .precedence = .Comparison }, // LessEqual - .{ .prefix = null, .infix = binary, .precedence = .NullCoalescing }, // QuestionQuestion - .{ .prefix = null, .infix = null, .precedence = .None }, // Arrow - .{ .prefix = literal, .infix = null, .precedence = .None }, // True - .{ .prefix = literal, .infix = null, .precedence = .None }, // False - .{ .prefix = literal, .infix = null, .precedence = .None }, // Null - .{ .prefix = null, .infix = null, .precedence = .None }, // Str - .{ .prefix = null, .infix = null, .precedence = .None }, // Ud - .{ .prefix = null, .infix = null, .precedence = .None }, // Int - .{ .prefix = null, .infix = null, .precedence = .None }, // Float - .{ .prefix = null, .infix = null, .precedence = .None }, // Type - .{ .prefix = null, .infix = null, .precedence = .None }, // Bool - .{ .prefix = null, .infix = null, .precedence = .None }, // Function - .{ .prefix = null, .infix = binary, .precedence = .Shift }, // ShiftRight - .{ .prefix = null, .infix = binary, .precedence = .Shift }, // ShiftLeft - .{ .prefix = null, .infix = binary, .precedence = .Bitwise }, // Xor - .{ .prefix = null, .infix = binary, .precedence = .Bitwise }, // Bor - .{ .prefix = unary, .infix = null, .precedence = .Term }, // Bnot - .{ .prefix = null, .infix = or_, .precedence = .Or }, // Or - .{ .prefix = null, .infix = and_, .precedence = .And }, // And - .{ .prefix = null, .infix = null, .precedence = .None }, // Return - .{ .prefix = inlineIf, .infix = null, .precedence = .None }, // If - .{ .prefix = null, .infix = null, .precedence = .None }, // Else - .{ .prefix = null, .infix = null, .precedence = .None }, // Do - .{ .prefix = null, .infix = null, .precedence = .None }, // Until - .{ .prefix = null, .infix = null, .precedence = .None }, // While - .{ .prefix = null, .infix = null, .precedence = .None }, // For - .{ .prefix = null, .infix = null, .precedence = .None }, // ForEach - .{ .prefix = null, .infix = null, .precedence = .None }, // Switch - .{ .prefix = null, .infix = null, .precedence = .None }, // Break - .{ .prefix = null, .infix = null, .precedence = .None }, // Continue - .{ .prefix = null, .infix = null, .precedence = .None }, // Default - .{ .prefix = null, .infix = null, .precedence = .None }, // In - .{ .prefix = null, .infix = is, .precedence = .IsAs }, // Is - .{ .prefix = integer, .infix = null, .precedence = .None }, // Integer - .{ .prefix = float, .infix = null, .precedence = .None }, // FloatValue - .{ .prefix = string, .infix = null, .precedence = .None }, // String - .{ .prefix = variable, .infix = null, .precedence = .None }, // Identifier - .{ .prefix = fun, .infix = null, .precedence = .None }, // Fun - .{ .prefix = null, .infix = null, .precedence = .None }, // Object - .{ .prefix = null, .infix = null, .precedence = .None }, // Obj - .{ .prefix = null, .infix = null, .precedence = .None }, // Protocol - .{ .prefix = null, .infix = null, .precedence = .None }, // Enum - .{ .prefix = null, .infix = null, .precedence = .None }, // Throw - .{ .prefix = null, .infix = null, .precedence = .None }, // Try - .{ .prefix = null, .infix = null, .precedence = .None }, // Catch - .{ .prefix = null, .infix = null, .precedence = .None }, // Test - .{ .prefix = null, .infix = null, .precedence = .None }, // Import - .{ .prefix = null, .infix = null, .precedence = .None }, // Export - .{ .prefix = null, .infix = null, .precedence = .None }, // Const - .{ .prefix = null, .infix = null, .precedence = .None }, // Static - .{ .prefix = null, .infix = null, .precedence = .None }, // From - .{ .prefix = null, .infix = null, .precedence = .None }, // As - .{ .prefix = null, .infix = as, .precedence = .IsAs }, // AsBang - .{ .prefix = null, .infix = null, .precedence = .None }, // Extern - .{ .prefix = null, .infix = null, .precedence = .None }, // Eof - .{ .prefix = null, .infix = null, .precedence = .None }, // Error - .{ .prefix = literal, .infix = null, .precedence = .None }, // Void - .{ .prefix = null, .infix = null, .precedence = .None }, // Docblock - .{ .prefix = pattern, .infix = null, .precedence = .None }, // Pattern - .{ .prefix = null, .infix = null, .precedence = .None }, // pat - .{ .prefix = null, .infix = null, .precedence = .None }, // fib - .{ .prefix = asyncCall, .infix = binary, .precedence = .Term }, // & - .{ .prefix = resumeFiber, .infix = null, .precedence = .Primary }, // resume - .{ .prefix = resolveFiber, .infix = null, .precedence = .Primary }, // resolve - .{ .prefix = yield, .infix = null, .precedence = .Primary }, // yield - .{ .prefix = null, .infix = range, .precedence = .Primary }, // .. - .{ .prefix = null, .infix = null, .precedence = .None }, // any - .{ .prefix = null, .infix = null, .precedence = .None }, // zdef - .{ .prefix = typeOfExpression, .infix = null, .precedence = .Unary }, // typeof - .{ .prefix = null, .infix = null, .precedence = .None }, // var - }; - - pub const ScriptImport = struct { - function: *ParseNode, - globals: std.ArrayList(Global), - absolute_path: *ObjString, - }; - - pub var user_library_paths: ?[][]const u8 = null; - - gc: *GarbageCollector, - scanner: ?Scanner = null, - parser: ParserState, - script_name: []const u8 = undefined, - // If true the script is being imported - imported: bool = false, - // True when parsing a declaration inside an export statement - exporting: bool = false, - // Cached imported functions - imports: *std.StringHashMap(ScriptImport), - test_count: u64 = 0, - current: ?*Frame = null, - current_object: ?ObjectFrame = null, - globals: std.ArrayList(Global), - flavor: RunFlavor, - ffi: FFI, - reporter: Reporter, - - // Jump to patch at end of current expression with a optional unwrapping in the middle of it - opt_jumps: ?std.ArrayList(Precedence) = null, - - pub fn init( - gc: *GarbageCollector, - imports: *std.StringHashMap(ScriptImport), - imported: bool, - flavor: RunFlavor, - ) Self { - return .{ - .gc = gc, - .parser = ParserState.init(gc.allocator), - .imports = imports, - .imported = imported, - .globals = std.ArrayList(Global).init(gc.allocator), - .flavor = flavor, - .reporter = Reporter{ - .allocator = gc.allocator, - .error_prefix = "Syntax", - }, - .ffi = FFI.init(gc), - }; - } - - pub fn deinit(self: *Self) void { - self.parser.deinit(); - self.ffi.deinit(); - } - - pub fn parse(self: *Self, source: []const u8, file_name: []const u8) !?*ParseNode { - if (self.scanner != null) { - self.scanner = null; - } - - self.scanner = Scanner.init(self.gc.allocator, file_name, source); - - const function_type: FunctionType = if (!self.imported and self.flavor == .Repl) - .Repl - else if (self.imported) - .Script - else - .ScriptEntryPoint; - - var function_node = try self.gc.allocator.create(FunctionNode); - function_node.* = try FunctionNode.init( - self, - function_type, - file_name, - null, - ); - - self.script_name = file_name; - - try self.beginFrame(function_type, function_node, null); - - self.reporter.had_error = false; - self.reporter.panic_mode = false; - - try self.advance(); - - while (!(try self.match(.Eof))) { - if (function_type == .Repl) { - // When running in REPL, global scope is allowed to run statements since the global scope becomes procedural - if (self.declarationOrStatement(null) catch |err| { - if (BuildOptions.debug) { - std.debug.print("Parsing failed with error {}\n", .{err}); - } - return null; - }) |decl| { - try function_node.body.?.statements.append(decl); - } - } else { - if (self.declaration() catch |err| { - if (BuildOptions.debug) { - std.debug.print("Parsing failed with error {}\n", .{err}); - } - return null; - }) |decl| { - try function_node.body.?.statements.append(decl); - } - } - } - - // If top level, search `main` or `test` function(s) and call them - // Then put any exported globals on the stack - if (function_type == .ScriptEntryPoint) { - for (self.globals.items, 0..) |global, index| { - if (mem.eql(u8, global.name.string, "main") and !global.hidden and global.prefix == null) { - function_node.main_slot = index; - function_node.main_location = global.location; - break; - } - } - } - - var test_slots = std.ArrayList(usize).init(self.gc.allocator); - var test_locations = std.ArrayList(Token).init(self.gc.allocator); - // Create an entry point wich runs all `test` - for (self.globals.items, 0..) |global, index| { - if (mem.startsWith(u8, global.name.string, "$test") and !global.hidden and global.prefix == null) { - try test_slots.append(index); - try test_locations.append(global.location); - } - } - - function_node.test_slots = test_slots.items; - function_node.test_locations = test_locations.items; - - // If we're being imported, put all globals on the stack - if (self.imported) { - function_node.exported_count = self.globals.items.len; - } - - // Check there's no more root placeholders - for (self.globals.items) |global| { - if (global.type_def.def_type == .Placeholder) { - self.reporter.reportPlaceholder(global.type_def.resolved_type.?.Placeholder); - } - } - - return if (self.reporter.had_error) null else &self.endFrame().node; - } - - fn beginFrame(self: *Self, function_type: FunctionType, function_node: *FunctionNode, this: ?*ObjTypeDef) !void { - var enclosing = self.current; - self.current = try self.gc.allocator.create(Frame); - self.current.?.* = Frame{ - .locals = [_]Local{undefined} ** 255, - .upvalues = [_]UpValue{undefined} ** 255, - .enclosing = enclosing, - .function_node = function_node, - .constants = std.ArrayList(Value).init(self.gc.allocator), - }; - - if (function_type == .Extern) { - return; - } - - // First local is reserved for an eventual `this` or cli arguments - var local: *Local = &self.current.?.locals[self.current.?.local_count]; - self.current.?.local_count += 1; - local.depth = 0; - local.is_captured = false; - - switch (function_type) { - .Method => { - assert(this != null); - - local.type_def = this.?; - }, - .EntryPoint, .ScriptEntryPoint => { - // `args` is [str] - var list_def: ObjList.ListDef = ObjList.ListDef.init( - self.gc.allocator, - try self.gc.type_registry.getTypeDef(.{ .def_type = .String }), - ); - - var list_union: ObjTypeDef.TypeUnion = .{ .List = list_def }; - - local.type_def = try self.gc.type_registry.getTypeDef(ObjTypeDef{ .def_type = .List, .resolved_type = list_union }); - }, - else => { - // TODO: do we actually need to reserve that space since we statically know if we need it? - // nothing - local.type_def = try self.gc.type_registry.getTypeDef(ObjTypeDef{ - .def_type = .Void, - }); - }, - } - - const name: []const u8 = switch (function_type) { - .Method => "this", - .EntryPoint => "$args", - .ScriptEntryPoint => "$args", - else => "_", - }; - - local.name = try self.gc.copyString(name); - } - - fn endFrame(self: *Self) *FunctionNode { - var i: usize = 0; - while (i < self.current.?.local_count) : (i += 1) { - const local = self.current.?.locals[i]; - - // Check discarded locals - if (self.flavor != .Repl and !local.isReferenced()) { - const type_def_str = local.type_def.toStringAlloc(self.gc.allocator) catch unreachable; - defer type_def_str.deinit(); - - self.reporter.warnFmt( - .unused_argument, - local.location, - "Unused local of type `{s}`", - .{ - type_def_str.items, - }, - ); - } - } - - // If global scope, check unused globals - const function_type = self.current.?.function_node.node.type_def.?.resolved_type.?.Function.function_type; - if (function_type == .Script or function_type == .ScriptEntryPoint) { - for (self.globals.items) |global| { - if (!global.isReferenced()) { - const type_def_str = global.type_def.toStringAlloc(self.gc.allocator) catch unreachable; - defer type_def_str.deinit(); - - self.reporter.warnFmt( - .unused_argument, - global.location, - "Unused global of type `{s}`", - .{ - type_def_str.items, - }, - ); - } - } - } - - var current_node = self.current.?.function_node; - self.current = self.current.?.enclosing; - - return current_node; - } - - fn beginScope(self: *Self) void { - self.current.?.scope_depth += 1; - } - - fn endScope(self: *Self) !std.ArrayList(OpCode) { - var current = self.current.?; - var closing = std.ArrayList(OpCode).init(self.gc.allocator); - current.scope_depth -= 1; - - while (current.local_count > 0 and current.locals[current.local_count - 1].depth > current.scope_depth) { - const local = current.locals[current.local_count - 1]; - - if (local.is_captured) { - try closing.append(.OP_CLOSE_UPVALUE); - } else { - try closing.append(.OP_POP); - } - - // Check discarded locals - if (self.flavor != .Repl and !local.isReferenced()) { - const type_def_str = local.type_def.toStringAlloc(self.gc.allocator) catch unreachable; - defer type_def_str.deinit(); - - self.reporter.warnFmt( - .unused_argument, - local.location, - "Unused local of type `{s}`", - .{ - type_def_str.items, - }, - ); - } - - current.local_count -= 1; - } - - return closing; - } - - fn closeScope(self: *Self, upto_depth: usize) !std.ArrayList(OpCode) { - var current = self.current.?; - var closing = std.ArrayList(OpCode).init(self.gc.allocator); - - var local_count = current.local_count; - while (local_count > 0 and current.locals[local_count - 1].depth > upto_depth - 1) { - if (current.locals[local_count - 1].is_captured) { - try closing.append(.OP_CLOSE_UPVALUE); - } else { - try closing.append(.OP_POP); - } - - local_count -= 1; - } - - return closing; - } - - pub fn advance(self: *Self) !void { - self.parser.previous_token = self.parser.current_token; - - while (true) { - self.parser.current_token = if (self.parser.ahead.items.len > 0) - self.parser.ahead.orderedRemove(0) - else - try self.scanner.?.scanToken(); - if (self.parser.current_token.?.token_type != .Error) { - break; - } - - self.reportErrorAtCurrent( - .unknown, - self.parser.current_token.?.literal_string orelse "Unknown error.", - ); - } - } - - pub fn consume(self: *Self, token_type: TokenType, message: []const u8) !void { - if (self.parser.current_token.?.token_type == token_type) { - try self.advance(); - return; - } - - self.reportErrorAtCurrent(.syntax, message); - } - - // Check next token - fn check(self: *Self, token_type: TokenType) bool { - return self.parser.current_token.?.token_type == token_type; - } - - // Check `n` tokens ahead - fn checkAhead(self: *Self, token_type: TokenType, n: usize) !bool { - // Parse tokens if we didn't already look that far ahead - while (n + 1 > self.parser.ahead.items.len) { - while (true) { - const token = try self.scanner.?.scanToken(); - try self.parser.ahead.append(token); - - if (token.token_type == .Eof) { - return false; - } - - // If error, report it and keep checking ahead - if (token.token_type != .Error) { - break; - } - - self.reportErrorAtCurrent(.syntax, token.literal_string orelse "Unknown error."); - } - } - - return self.parser.ahead.items[n].token_type == token_type; - } - - // Check for a sequence ahead, a null in the sequence means continue until next token in the sequence is found - // Don't use null if you're not sure the following token **must** be found, otherwise it'll check the whole source - // Right now the null is only used to parse ahead a generic object type like `Person:: identifier` - fn checkSequenceAhead(self: *Self, sequence: []const ?TokenType, limit: usize) !bool { - assert(sequence.len > 0); - - if (!self.check(sequence[0].?)) { - return false; - } - - var i: usize = 0; - for (sequence[1..], 1..) |token_type, n| { - // Avoid going to far - if (i > limit) { - return false; - } - - if (token_type) |tt| { - if (!try self.checkAhead(tt, i)) { - return false; - } - - i += 1; - } else { - // Advance until next token - assert(n < sequence.len - 1 and sequence[n + 1] != null); // There must be at least one more token in the sequence - const next_token = sequence[n + 1].?; - - while (!try self.checkAhead(next_token, i)) : (i += 1) { - // Avoid looping forever if found EOF - if (self.parser.ahead.getLastOrNull()) |last| { - if (last.token_type == .Eof) { - std.debug.print("Reached EOF\n", .{}); - return false; - } - } - } - } - } - - return true; - } - - fn match(self: *Self, token_type: TokenType) !bool { - if (!self.check(token_type)) { - return false; - } - - try self.advance(); - - return true; - } - - fn resolvePlaceholderWithRelation( - self: *Self, - child: *ObjTypeDef, - resolved_type: *ObjTypeDef, - constant: bool, - relation: PlaceholderDef.PlaceholderRelation, - ) anyerror!void { - var child_placeholder: PlaceholderDef = child.resolved_type.?.Placeholder; - - if (BuildOptions.debug_placeholders) { - std.debug.print( - "Attempts to resolve @{} child placeholder @{} ({s}) with relation {}\n", - .{ - @intFromPtr(resolved_type), - @intFromPtr(child), - if (child_placeholder.name) |name| name.string else "unknown", - child_placeholder.parent_relation.?, - }, - ); - } - - switch (relation) { - .GenericResolve => { - try self.resolvePlaceholder( - child, - try resolved_type.populateGenerics( - self.parser.previous_token.?, - switch (resolved_type.def_type) { - .Function => resolved_type.resolved_type.?.Function.id, - .Object => resolved_type.resolved_type.?.Object.id, - else => null, - }, - child_placeholder.resolved_generics.?, - &self.gc.type_registry, - null, - ), - true, - ); - }, - .Optional => { - try self.resolvePlaceholder( - child, - try resolved_type.cloneOptional(&self.gc.type_registry), - false, - ); - }, - .Unwrap => { - try self.resolvePlaceholder( - child, - try resolved_type.cloneNonOptional(&self.gc.type_registry), - false, - ); - }, - .Instance => { - try self.resolvePlaceholder( - child, - try resolved_type.toInstance(self.gc.allocator, &self.gc.type_registry), - false, - ); - }, - .Parent => { - try self.resolvePlaceholder( - child, - try resolved_type.toParentType(self.gc.allocator, &self.gc.type_registry), - false, - ); - }, - .Call => { - // Can we call the parent? - if (resolved_type.def_type != .Function) { - self.reporter.reportErrorAt(.callable, child_placeholder.where, "Can't be called"); - return; - } - - try self.resolvePlaceholder( - child, - resolved_type.resolved_type.?.Function.return_type, - false, - ); - }, - .Yield => { - // Can we call the parent? - if (resolved_type.def_type != .Function) { - self.reporter.reportErrorAt(.callable, child_placeholder.where, "Can't be called"); - return; - } - - try self.resolvePlaceholder( - child, - resolved_type.resolved_type.?.Function.yield_type, - false, - ); - }, - .Subscript => { - if (resolved_type.def_type == .List) { - try self.resolvePlaceholder(child, resolved_type.resolved_type.?.List.item_type, false); - } else if (resolved_type.def_type == .Map) { - try self.resolvePlaceholder(child, try resolved_type.resolved_type.?.Map.value_type.cloneOptional(&self.gc.type_registry), false); - } else if (resolved_type.def_type == .String) { - try self.resolvePlaceholder(child, try self.gc.type_registry.getTypeDef(.{ .def_type = .String }), false); - } else { - self.reporter.reportErrorAt(.subscriptable, child_placeholder.where, "Can't be subscripted"); - return; - } - }, - .Key => { - if (resolved_type.def_type == .Map) { - try self.resolvePlaceholder(child, resolved_type.resolved_type.?.Map.key_type, false); - } else if (resolved_type.def_type == .List or resolved_type.def_type == .String) { - try self.resolvePlaceholder(child, try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }), false); - } else { - self.reporter.reportErrorAt(.map_key_type, child_placeholder.where, "Can't be a key"); - return; - } - }, - .FieldAccess => { - switch (resolved_type.def_type) { - .List => { - assert(child_placeholder.name != null); - - if (try ObjList.ListDef.member(resolved_type, self, child_placeholder.name.?.string)) |member| { - try self.resolvePlaceholder(child, member, false); - } - }, - .Map => { - assert(child_placeholder.name != null); - - if (try ObjMap.MapDef.member(resolved_type, self, child_placeholder.name.?.string)) |member| { - try self.resolvePlaceholder(child, member, false); - } - }, - .String => { - assert(child_placeholder.name != null); - - if (try ObjString.memberDef(self, child_placeholder.name.?.string)) |member| { - try self.resolvePlaceholder(child, member, false); - } - }, - .Pattern => { - assert(child_placeholder.name != null); - - if (try ObjPattern.memberDef(self, child_placeholder.name.?.string)) |member| { - try self.resolvePlaceholder(child, member, false); - } - }, - .Fiber => { - assert(child_placeholder.name != null); - - if (try ObjFiber.memberDef(self, child_placeholder.name.?.string)) |member| { - try self.resolvePlaceholder(child, member, false); - } - }, - .Object => { - // We can't create a field access placeholder without a name - assert(child_placeholder.name != null); - - var object_def: ObjObject.ObjectDef = resolved_type.resolved_type.?.Object; - - // Search for a field matching the placeholder - if (object_def.fields.get(child_placeholder.name.?.string)) |field| { - // TODO: remove? should only resolve with a field if field accessing an object instance? - try self.resolvePlaceholder(child, field, false); - } else if (object_def.methods.get(child_placeholder.name.?.string)) |method_def| { - try self.resolvePlaceholder(child, method_def, true); - } else if (object_def.static_fields.get(child_placeholder.name.?.string)) |static_def| { - try self.resolvePlaceholder(child, static_def, false); - } else { - self.reportErrorFmt( - .property_does_not_exists, - "`{s}` has no static field `{s}`", - .{ - object_def.name.string, - child_placeholder.name.?.string, - }, - ); - } - }, - .ObjectInstance => { - // We can't create a field access placeholder without a name - assert(child_placeholder.name != null); - - const object_def: ObjObject.ObjectDef = resolved_type.resolved_type.?.ObjectInstance.resolved_type.?.Object; - - // Search for a field matching the placeholder - if (object_def.fields.get(child_placeholder.name.?.string)) |field| { - try self.resolvePlaceholder(child, field, false); - } else if (object_def.methods.get(child_placeholder.name.?.string)) |method_def| { - try self.resolvePlaceholder(child, method_def, true); - } else { - self.reportErrorFmt( - .property_does_not_exists, - "`{s}` has no field `{s}`", - .{ - object_def.name.string, - child_placeholder.name.?.string, - }, - ); - } - }, - .ForeignContainer => { - // We can't create a field access placeholder without a name - assert(child_placeholder.name != null); - - const f_def = resolved_type.resolved_type.?.ForeignContainer; - - // Search for a field matching the placeholder - if (f_def.buzz_type.get(child_placeholder.name.?.string)) |field| { - try self.resolvePlaceholder(child, field, false); - } else { - self.reportErrorFmt( - .property_does_not_exists, - "`{s}` has no field `{s}`", - .{ - f_def.name.string, - child_placeholder.name.?.string, - }, - ); - } - }, - .ProtocolInstance => { - // We can't create a field access placeholder without a name - assert(child_placeholder.name != null); - - const protocol_def = resolved_type.resolved_type.?.ProtocolInstance.resolved_type.?.Protocol; - - // Search for a field matching the placeholder - if (protocol_def.methods.get(child_placeholder.name.?.string)) |method_def| { - try self.resolvePlaceholder(child, method_def, true); - } else { - self.reportErrorFmt( - .property_does_not_exists, - "`{s}` has no method `{s}`", - .{ - protocol_def.name.string, - child_placeholder.name.?.string, - }, - ); - } - }, - .Enum => { - // We can't create a field access placeholder without a name - assert(child_placeholder.name != null); - - var enum_def: ObjEnum.EnumDef = resolved_type.resolved_type.?.Enum; - - // Search for a case matching the placeholder - for (enum_def.cases.items) |case| { - if (mem.eql(u8, case, child_placeholder.name.?.string)) { - var enum_instance_def: ObjTypeDef.TypeUnion = .{ .EnumInstance = resolved_type }; - - try self.resolvePlaceholder(child, try self.gc.type_registry.getTypeDef(.{ - .def_type = .EnumInstance, - .resolved_type = enum_instance_def, - }), true); - break; - } - } - }, - .EnumInstance => { - assert(child_placeholder.name != null); - - if (std.mem.eql(u8, "value", child_placeholder.name.?.string)) { - try self.resolvePlaceholder( - child, - resolved_type.resolved_type.?.EnumInstance.resolved_type.?.Enum.enum_type, - false, - ); - } else { - self.reporter.reportErrorAt(.property_does_not_exists, child_placeholder.where, "Enum instance only has field `value`"); - return; - } - }, - else => { - self.reporter.reportErrorAt(.field_access, child_placeholder.where, "Doesn't support field access"); - return; - }, - } - }, - .Assignment => { - if (constant) { - self.reporter.reportErrorAt(.constant, child_placeholder.where, "Is constant."); - return; - } - - // Assignment relation from a once Placeholder and now Object/Enum is creating an instance - var child_type: *ObjTypeDef = try resolved_type.toInstance(self.gc.allocator, &self.gc.type_registry); - - // Is child type matching the parent? - try self.resolvePlaceholder(child, child_type, false); - }, - } - } - - // When we encounter the missing declaration we replace it with the resolved type. - // We then follow the chain of placeholders to see if their assumptions were correct. - // If not we raise a compile error. - pub fn resolvePlaceholder(self: *Self, placeholder: *ObjTypeDef, resolved_type: *ObjTypeDef, constant: bool) anyerror!void { - assert(placeholder.def_type == .Placeholder); - - if (BuildOptions.debug_placeholders) { - std.debug.print("Attempts to resolve @{} ({s}) with @{} a {}({})\n", .{ - @intFromPtr(placeholder), - if (placeholder.resolved_type.?.Placeholder.name) |name| name.string else "unknown", - @intFromPtr(resolved_type), - resolved_type.def_type, - resolved_type.optional, - }); - } - - // Both placeholders, we have to connect the child placeholder to a root placeholder so its not orphan - if (resolved_type.def_type == .Placeholder) { - if (BuildOptions.debug_placeholders) { - std.debug.print( - "Replaced linked placeholder @{} ({s}) with rooted placeholder @{} ({s})\n", - .{ - @intFromPtr(placeholder), - if (placeholder.resolved_type.?.Placeholder.name != null) placeholder.resolved_type.?.Placeholder.name.?.string else "unknown", - @intFromPtr(resolved_type), - if (resolved_type.resolved_type.?.Placeholder.name != null) resolved_type.resolved_type.?.Placeholder.name.?.string else "unknown", - }, - ); - } - - if (resolved_type.resolved_type.?.Placeholder.parent) |parent| { - if (parent.def_type == .Placeholder) { - try parent.resolved_type.?.Placeholder.children.append(placeholder); - } else { - // Parent already resolved, resolve this now orphan placeholder - try self.resolvePlaceholderWithRelation( - resolved_type, - parent, - constant, - resolved_type.resolved_type.?.Placeholder.parent_relation.?, - ); - } - } - - // Merge both placeholder children list - // TODO: do we need this? - // try resolved_type.resolved_type.?.Placeholder.children.appendSlice(placeholder.resolved_type.?.Placeholder.children.items); - - // Don't copy obj header or it will break the linked list of objects - const obj = placeholder.obj; - placeholder.* = resolved_type.*; - placeholder.obj = obj; - return; - } - - var placeholder_def: PlaceholderDef = placeholder.resolved_type.?.Placeholder; - - if (BuildOptions.debug_placeholders) { - std.debug.print( - "Resolved placeholder @{} {s}({}) with @{}.{}({})\n", - .{ - @intFromPtr(placeholder), - if (placeholder.resolved_type.?.Placeholder.name != null) placeholder.resolved_type.?.Placeholder.name.?.string else "unknown", - placeholder.optional, - @intFromPtr(resolved_type), - resolved_type.def_type, - resolved_type.optional, - }, - ); - } - - // Overwrite placeholder with resolved_type - // Don't copy obj header or it will break the linked list of objects - const obj = placeholder.obj; - placeholder.* = resolved_type.*; - placeholder.obj = obj; - // Put it in the registry so any cloneOptional/cloneNonOptional don't create new types - try self.gc.type_registry.setTypeDef(placeholder); - - // Now walk the chain of placeholders and see if they hold up - for (placeholder_def.children.items) |child| { - if (child.def_type == .Placeholder) { - try self.resolvePlaceholderWithRelation( - child, - placeholder, - constant, - child.resolved_type.?.Placeholder.parent_relation.?, - ); - } - } - - // TODO: should resolved_type be freed? - // TODO: does this work with vm.type_defs? (i guess not) - } - - // Skip tokens until we reach something that resembles a new statement - fn synchronize(self: *Self) !void { - self.reporter.panic_mode = false; - - while (self.parser.current_token.?.token_type != .Eof) : (try self.advance()) { - if (self.parser.previous_token.?.token_type == .Semicolon) { - return; - } - - switch (self.parser.current_token.?.token_type) { - .Object, - .Enum, - .Test, - .Fun, - .Const, - .If, - .While, - .Do, - .For, - .ForEach, - .Return, - .Switch, - .Throw, - .Break, - .Continue, - .Export, - .Import, - .Zdef, - => return, - else => {}, - } - } - } - - fn declaration(self: *Self) anyerror!?*ParseNode { - const global_scope = self.current.?.scope_depth == 0; - - var docblock: ?Token = if (global_scope and try self.match(.Docblock)) - self.parser.previous_token.? - else - null; - - if (try self.match(.Extern)) { - var node = try self.funDeclaration(); - node.docblock = docblock; - - return node; - } else if ((self.parser.previous_token == null or self.parser.previous_token.?.token_type != .Export) and try self.match(.Export)) { - var node = try self.exportStatement(); - node.docblock = docblock; - - return node; - } else { - const constant: bool = try self.match(.Const); - - var node = if (global_scope and !constant and try self.match(.Object)) - try self.objectDeclaration() - else if (global_scope and !constant and try self.match(.Protocol)) - try self.protocolDeclaration() - else if (global_scope and !constant and try self.match(.Enum)) - try self.enumDeclaration() - else if (!constant and try self.match(.Fun)) - try self.funDeclaration() - else if (!constant and try self.match(.Var)) - try self.varDeclaration( - false, - null, - .Semicolon, - false, - true, - ) - else if (try self.match(.Pat)) - try self.varDeclaration( - false, - try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Pattern }), - .Semicolon, - constant, - true, - ) - else if (try self.match(.Ud)) - try self.varDeclaration( - false, - try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .UserData }), - .Semicolon, - constant, - true, - ) - else if (try self.match(.Str)) - try self.varDeclaration( - false, - try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .String }), - .Semicolon, - constant, - true, - ) - else if (try self.match(.Int)) - try self.varDeclaration( - false, - try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Integer }), - .Semicolon, - constant, - true, - ) - else if (try self.match(.Float)) - try self.varDeclaration( - false, - try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Float }), - .Semicolon, - constant, - true, - ) - else if (try self.match(.Bool)) - try self.varDeclaration( - false, - try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Bool }), - .Semicolon, - constant, - true, - ) - else if (try self.match(.Type)) - try self.varDeclaration( - false, - try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Type }), - .Semicolon, - constant, - true, - ) - else if (try self.match(.Any)) - try self.varDeclaration( - false, - try self.gc.type_registry.getTypeDef(.{ .def_type = .Any }), - .Semicolon, - constant, - true, - ) - else if (self.parser.current_token != null and self.parser.current_token.?.token_type == .Identifier and self.parser.current_token.?.lexeme.len == 1 and self.parser.current_token.?.lexeme[0] == '_') - try self.varDeclaration( - false, - try self.gc.type_registry.getTypeDef(.{ .def_type = .Any }), - .Semicolon, - constant, - true, - ) - else if (try self.match(.Fib)) - try self.varDeclaration( - false, - try self.parseFiberType(null), - .Semicolon, - constant, - true, - ) - else if (try self.match(.Obj)) - try self.varDeclaration( - false, - try self.parseObjType(null), - .Semicolon, - constant, - true, - ) - else if (try self.match(.LeftBracket)) - try self.listDeclaration(constant) - else if (try self.match(.LeftBrace)) - try self.mapDeclaration(constant) - else if (!constant and try self.match(.Test)) - try self.testStatement() - else if (try self.match(.Function)) - try self.varDeclaration( - false, - try self.parseFunctionType(null), - .Semicolon, - constant, - true, - ) - else if (global_scope and try self.match(.Import)) - try self.importStatement() - else if (global_scope and try self.match(.Zdef)) - try self.zdefStatement() - else if (global_scope and !constant and try self.match(.Export)) - try self.exportStatement() - else if (self.check(.Identifier)) user_decl: { - // In the declaractive space, starting with an identifier is always a varDeclaration with a user type - if (global_scope and self.current.?.function_node.node.type_def.?.resolved_type.?.Function.function_type != .Repl) { - _ = try self.advance(); // consume first identifier - break :user_decl try self.userVarDeclaration(false, constant); - } else if ( // zig fmt: off - // As of now this is the only place where we need to check more than one token ahead - // Note that we would not have to do this if type were given **after** the identifier. But changing this is a pretty big left turn. - // `Type variable` - try self.checkSequenceAhead(&[_]?TokenType{ .Identifier, .Identifier }, 2) - // `prefix.Type variable` - or try self.checkSequenceAhead(&[_]?TokenType{ .Identifier, .Dot, .Identifier, .Identifier }, 4) - // `prefix.Type? variable` - or try self.checkSequenceAhead(&[_]?TokenType{ .Identifier, .Dot, .Identifier, .Question, .Identifier }, 4) - // `Type? variable` - or try self.checkSequenceAhead(&[_]?TokenType{ .Identifier, .Question, .Identifier }, 3) - // `Type::<...> variable` - or try self.checkSequenceAhead(&[_]?TokenType{ .Identifier, .DoubleColon, .Less, null, .Greater, .Identifier }, 255 * 2) - // - Type::<...>? variable - or try self.checkSequenceAhead(&[_]?TokenType{ .Identifier, .DoubleColon, .Less, null, .Greater, .Question, .Identifier }, 255 * 2) - // - prefix.Type::<...> variable - or try self.checkSequenceAhead(&[_]?TokenType{ .Identifier, .Dot, .Identifier, .DoubleColon, .Less, null, .Greater, .Identifier }, 255 * 2) - // - prefix.Type::<...>? variable - or try self.checkSequenceAhead(&[_]?TokenType{ .Identifier, .Dot, .Identifier, .DoubleColon, .Less, null, .Greater, .Question, .Identifier }, 255 * 2) - ) { - // zig fmt: on - _ = try self.advance(); // consume first identifier - break :user_decl try self.userVarDeclaration(false, constant); - } - - break :user_decl null; - } else if (global_scope and !constant and try self.match(.Export)) - try self.exportStatement() - else - null; - - if (node == null and constant) { - node = try self.varDeclaration( - false, - null, - .Semicolon, - true, - true, - ); - } - - if (node != null and docblock != null) { - node.?.docblock = docblock; - } - - if (self.reporter.panic_mode) { - try self.synchronize(); - } - - return node; - } - } - - fn declarationOrStatement(self: *Self, loop_scope: ?LoopScope) !?*ParseNode { - return try self.declaration() orelse try self.statement(false, loop_scope); - } - - // When a break statement, will return index of jump to patch - fn statement(self: *Self, hanging: bool, loop_scope: ?LoopScope) !?*ParseNode { - if (try self.match(.If)) { - assert(!hanging); - return try self.ifStatement(loop_scope); - } else if (try self.match(.For)) { - assert(!hanging); - return try self.forStatement(); - } else if (try self.match(.ForEach)) { - assert(!hanging); - return try self.forEachStatement(); - } else if (try self.match(.While)) { - assert(!hanging); - return try self.whileStatement(); - } else if (try self.match(.Do)) { - assert(!hanging); - return try self.doUntilStatement(); - } else if (try self.match(.Return)) { - assert(!hanging); - return try self.returnStatement(); - } else if (try self.match(.Try)) { - assert(!hanging); - return try self.tryStatement(); - } else if (try self.match(.Break)) { - assert(!hanging); - return try self.breakStatement(loop_scope); - } else if (try self.match(.Continue)) { - assert(!hanging); - return try self.continueStatement(loop_scope); - } else if (try self.match(.Import)) { - assert(!hanging); - return try self.importStatement(); - } else if (try self.match(.Throw)) { - const start_location = self.parser.previous_token.?; - // For now we don't care about the type. Later if we have `Error` type of data, we'll type check this - var error_value = try self.expression(false); - - try self.consume(.Semicolon, "Expected `;` after `throw` expression."); - - var node = try self.gc.allocator.create(ThrowNode); - node.* = .{ - .error_value = error_value, - .unconditional = self.current.?.scope_depth == 1, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } else { - return try self.expressionStatement(hanging); - } - - return null; - } - - fn objectDeclaration(self: *Self) !*ParseNode { - if (self.current.?.scope_depth > 0) { - self.reportError(.syntax, "Object must be defined at top-level."); - } - - const start_location = self.parser.previous_token.?; - - // Conforms to protocols? - var protocols = std.AutoHashMap(*ObjTypeDef, void).init(self.gc.allocator); - var protocol_count: usize = 0; - if (try self.match(.LeftParen)) { - while (!self.check(.RightParen) and !self.check(.Eof)) : (protocol_count += 1) { - if (protocol_count > 255) { - self.reportError(.protocols_count, "Can't conform to more than 255 protocols"); - } - - try self.consume(.Identifier, "Expected protocol identifier"); - - const protocol = try self.parseUserType(false); - - if (protocols.get(protocol) != null) { - self.reportErrorFmt(.already_conforming_protocol, "Already conforming to `{s}`.", .{protocol.resolved_type.?.Protocol.name.string}); - } - - try protocols.put(protocol, {}); - - if (!(try self.match(.Comma))) { - break; - } - } - - try self.consume(.RightParen, "Expected `)` after protocols list"); - } - - // Get object name - try self.consume(.Identifier, "Expected object name."); - const object_name: Token = self.parser.previous_token.?.clone(); - - // Qualified name to avoid cross script collision - const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); - defer self.gc.allocator.free(qualifier); - var qualified_object_name = std.ArrayList(u8).init(self.gc.allocator); - defer qualified_object_name.deinit(); - try qualified_object_name.writer().print("{s}.{s}", .{ qualifier, object_name.lexeme }); - - // Create a placeholder for self-reference which will be resolved at the end when declaring the object - var placeholder_index = try self.declarePlaceholder(object_name, null); - var object_placeholder = self.globals.items[placeholder_index].type_def; - - var object_def = ObjObject.ObjectDef.init( - self.gc.allocator, - object_name, - try self.gc.copyString(object_name.lexeme), - try self.gc.copyString(qualified_object_name.items), - false, - ); - - object_def.conforms_to.deinit(); - object_def.conforms_to = protocols; - - var resolved_type = ObjTypeDef.TypeUnion{ .Object = object_def }; - - // Create type - var object_type: ObjTypeDef = .{ - .def_type = .Object, - .resolved_type = resolved_type, - }; - - var object_frame: ObjectFrame = .{ - .name = object_name, - .type_def = object_placeholder, - .generics = &object_type.resolved_type.?.Object.generic_types, - }; - - self.current_object = object_frame; - - // Parse generic types - if (try self.match(.DoubleColon)) { - try self.consume(.Less, "Expected generic types list after `::`"); - var i: usize = 0; - while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { - try self.consume(.Identifier, "Expected generic type identifier"); - - const generic_identifier = try self.gc.copyString(self.parser.previous_token.?.lexeme); - if (object_type.resolved_type.?.Object.generic_types.get(generic_identifier) == null) { - const generic = ObjTypeDef.GenericDef{ - .origin = object_def.id, - .index = i, - }; - const resolved_generic_type = ObjTypeDef.TypeUnion{ .Generic = generic }; - try object_type.resolved_type.?.Object.generic_types.put( - generic_identifier, - try self.gc.type_registry.getTypeDef( - ObjTypeDef{ - .def_type = .Generic, - .resolved_type = resolved_generic_type, - }, - ), - ); - } else { - self.reportErrorFmt( - .generic_type, - "Generic type `{s}` already defined", - .{self.parser.previous_token.?.lexeme}, - ); - } - - if (!self.check(.Greater)) { - try self.consume(.Comma, "Expected `,` between generic types"); - } - } - - if (object_type.resolved_type.?.Object.generic_types.count() == 0) { - self.reportError( - .generic_type, - "Expected at least one generic type", - ); - } - - try self.consume(.Greater, "Expected `>` after generic types list"); - } - - self.beginScope(); - - // Body - try self.consume(.LeftBrace, "Expected `{` before object body."); - - var fields = std.StringArrayHashMap(void).init(self.gc.allocator); - defer fields.deinit(); - var field_order = std.ArrayList([]const u8).init(self.gc.allocator); - var methods = std.StringHashMap(*ParseNode).init(self.gc.allocator); - var properties = std.StringHashMap(?*ParseNode).init(self.gc.allocator); - var properties_type = std.StringHashMap(*ObjTypeDef).init(self.gc.allocator); - var docblocks = std.StringHashMap(?Token).init(self.gc.allocator); - while (!self.check(.RightBrace) and !self.check(.Eof)) { - const docblock: ?Token = if (try self.match(.Docblock)) self.parser.previous_token.? else null; - - const static: bool = try self.match(.Static); - - if (try self.match(.Fun)) { - const method_token = self.parser.current_token.?; - var method_node: *ParseNode = try self.method( - false, - if (static) object_placeholder else try object_placeholder.toInstance(self.gc.allocator, &self.gc.type_registry), - ); - - method_node.docblock = docblock; - - if (FunctionNode.cast(method_node)) |function_node| { - function_node.static = static; - } - - var method_name: []const u8 = method_node.type_def.?.resolved_type.?.Function.name.string; - - if (fields.get(method_name) != null) { - self.reportError(.property_already_exists, "A member with that name already exists."); - } - - // Does a placeholder exists for this name ? - if (static) { - if (object_type.resolved_type.?.Object.static_placeholders.get(method_name)) |placeholder| { - try self.resolvePlaceholder(placeholder, method_node.type_def.?, true); - - // Now we know the placeholder was a method - if (BuildOptions.debug_placeholders) { - std.debug.print( - "resolved static method for `{s}`\n", - .{ - method_name, - }, - ); - } - _ = object_type.resolved_type.?.Object.static_placeholders.remove(method_name); - } - } else { - if (object_type.resolved_type.?.Object.placeholders.get(method_name)) |placeholder| { - try self.resolvePlaceholder(placeholder, method_node.type_def.?, true); - - // Now we know the placeholder was a method - if (BuildOptions.debug_placeholders) { - std.debug.print( - "resolved method placeholder for `{s}`\n", - .{ - method_name, - }, - ); - } - _ = object_type.resolved_type.?.Object.placeholders.remove(method_name); - } - } - - if (static) { - try object_type.resolved_type.?.Object.static_fields.put(method_name, method_node.type_def.?); - - try object_type.resolved_type.?.Object.static_fields_locations.put(method_name, method_token); - } else { - try object_type.resolved_type.?.Object.methods.put( - method_name, - method_node.type_def.?, - ); - - try object_type.resolved_type.?.Object.methods_locations.put(method_name, method_token); - } - - try field_order.append(method_name); - try fields.put(method_name, {}); - try methods.put(method_name, method_node); - try properties_type.put(method_name, method_node.type_def.?); - try docblocks.put(method_name, docblock); - } else { - // TODO: constant object properties - // const constant = try self.match(.Const); - const property_type = try self.parseTypeDef(null, true); - - try self.consume(.Identifier, "Expected property name."); - const property_name = self.parser.previous_token.?.clone(); - - if (fields.get(property_name.lexeme) != null) { - self.reportError(.property_already_exists, "A member with that name already exists."); - } - - // Does a placeholder exists for this name ? - if (static) { - if (object_type.resolved_type.?.Object.static_placeholders.get(property_name.lexeme)) |placeholder| { - try self.resolvePlaceholder(placeholder, property_type, false); - - // Now we know the placeholder was a field - if (BuildOptions.debug_placeholders) { - std.debug.print( - "resolved static property placeholder for `{s}`\n", - .{ - property_name.lexeme, - }, - ); - } - _ = object_type.resolved_type.?.Object.static_placeholders.remove(property_name.lexeme); - } - } else { - if (object_type.resolved_type.?.Object.placeholders.get(property_name.lexeme)) |placeholder| { - try self.resolvePlaceholder(placeholder, property_type, false); - - // Now we know the placeholder was a field - if (BuildOptions.debug_placeholders) { - std.debug.print( - "resolved property placeholder for `{s}`\n", - .{ - property_name.lexeme, - }, - ); - } - _ = object_type.resolved_type.?.Object.placeholders.remove(property_name.lexeme); - } - } - - var default: ?*ParseNode = null; - - if (try self.match(.Equal)) { - default = try self.expression(false); - - if (!default.?.isConstant(default.?)) { - self.reporter.reportErrorAt(.constant_default, default.?.location, "Default value must be constant"); - } - } else if (property_type.optional) { - default = try self.nullLiteral(); - } - - if (static) { - if (!self.check(.RightBrace) or self.check(.Semicolon)) { - try self.consume(.Semicolon, "Expected `;` after static property definition."); - } - } else { - if (!self.check(.RightBrace) or self.check(.Comma)) { - try self.consume(.Comma, "Expected `,` after property definition."); - } - } - - if (static) { - try object_type.resolved_type.?.Object.static_fields.put( - property_name.lexeme, - property_type, - ); - - try object_type.resolved_type.?.Object.static_fields_locations.put(property_name.lexeme, property_name); - } else { - assert(!object_type.optional); - try object_type.resolved_type.?.Object.fields.put(property_name.lexeme, property_type); - try object_type.resolved_type.?.Object.fields_locations.put(property_name.lexeme, property_name); - - if (default != null) { - try object_type.resolved_type.?.Object.fields_defaults.put(property_name.lexeme, {}); - } - } - - try field_order.append(property_name.lexeme); - try fields.put(property_name.lexeme, {}); - try properties.put(property_name.lexeme, default); - try properties_type.put(property_name.lexeme, property_type); - try docblocks.put(property_name.lexeme, docblock); - } - } - - try self.consume(.RightBrace, "Expected `}` after object body."); - - var node = try self.gc.allocator.create(ObjectDeclarationNode); - node.node.ends_scope = try self.endScope(); - - const slot = try self.declareVariable( - &object_type, // Should resolve object_placeholder and be discarded - object_name, - true, // Object is always constant - true, - ); - - assert(!object_type.optional); - - self.markInitialized(); - - node.* = ObjectDeclarationNode{ - .slot = slot, - .methods = methods, - .properties = properties, - .docblocks = docblocks, - .properties_type = properties_type, - .fields = field_order.items, - }; - node.node.type_def = object_placeholder; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - assert(object_placeholder.resolved_type.?.Object.placeholders.count() == 0 or object_placeholder.resolved_type.?.Object.static_placeholders.count() == 0); - - self.current_object = null; - - return &node.node; - } - - fn method(self: *Self, abstract: bool, this: *ObjTypeDef) !*ParseNode { - try self.consume(.Identifier, "Expected method name."); - - return try self.function( - self.parser.previous_token.?.clone(), - if (abstract) .Abstract else .Method, - this, - ); - } - - fn protocolDeclaration(self: *Self) !*ParseNode { - if (self.current.?.scope_depth > 0) { - self.reportError(.syntax, "Protocol must be defined at top-level."); - } - - const start_location = self.parser.previous_token.?; - - // Get protocol name - try self.consume(.Identifier, "Expected protocol name."); - const protocol_name: Token = self.parser.previous_token.?.clone(); - - // Qualified name to avoid cross script collision - const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); - defer self.gc.allocator.free(qualifier); - var qualified_protocol_name = std.ArrayList(u8).init(self.gc.allocator); - defer qualified_protocol_name.deinit(); - try qualified_protocol_name.writer().print("{s}.{s}", .{ qualifier, protocol_name.lexeme }); - - // Create a placeholder for self-reference which will be resolved at the end when declaring the object - var placeholder_index = try self.declarePlaceholder(protocol_name, null); - var protocol_placeholder = self.globals.items[placeholder_index].type_def; - - var protocol_def = ObjObject.ProtocolDef.init( - self.gc.allocator, - protocol_name, - try self.gc.copyString(protocol_name.lexeme), - try self.gc.copyString(qualified_protocol_name.items), - ); - - var resolved_type = ObjTypeDef.TypeUnion{ .Protocol = protocol_def }; - - // Create type - var protocol_type: ObjTypeDef = .{ - .def_type = .Protocol, - .resolved_type = resolved_type, - }; - - self.beginScope(); - - // Body - try self.consume(.LeftBrace, "Expected `{` before protocol body."); - - var fields = std.StringHashMap(void).init(self.gc.allocator); - defer fields.deinit(); - var methods = std.StringHashMap(*ParseNode).init(self.gc.allocator); - var docblocks = std.StringHashMap(?Token).init(self.gc.allocator); - while (!self.check(.RightBrace) and !self.check(.Eof)) { - const docblock: ?Token = if (try self.match(.Docblock)) self.parser.previous_token.? else null; - - try self.consume(.Fun, "Expected method definition"); - - const method_name_token = self.parser.current_token.?; - var method_node: *ParseNode = try self.method( - true, - try protocol_placeholder.toInstance(self.gc.allocator, &self.gc.type_registry), - ); - - try self.consume(.Semicolon, "Expected `;` after method definition"); - - var method_name: []const u8 = method_node.type_def.?.resolved_type.?.Function.name.string; - - if (fields.get(method_name) != null) { - self.reportError(.property_already_exists, "A method with that name already exists."); - } - - try protocol_type.resolved_type.?.Protocol.methods.put( - method_name, - method_node.type_def.?, - ); - - try protocol_type.resolved_type.?.Protocol.methods_locations.put( - method_name, - method_name_token, - ); - - try fields.put(method_name, {}); - try methods.put(method_name, method_node); - try docblocks.put(method_name, docblock); - } - - try self.consume(.RightBrace, "Expected `}` after protocol body."); - - var node = try self.gc.allocator.create(ProtocolDeclarationNode); - node.node.ends_scope = try self.endScope(); - - const slot = try self.declareVariable( - &protocol_type, // Should resolve protocol_placeholder and be discarded - protocol_name, - true, // Protocol is always constant - true, - ); - - assert(!protocol_type.optional); - - self.markInitialized(); - - node.* = ProtocolDeclarationNode{ - .slot = slot, - }; - node.node.type_def = protocol_placeholder; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn expressionStatement(self: *Self, hanging: bool) !*ParseNode { - var node = try self.gc.allocator.create(ExpressionNode); - node.* = ExpressionNode{ - .expression = try self.expression(hanging), - }; - node.node.location = node.expression.location; - node.node.end_location = self.parser.previous_token.?; - - try self.consume(.Semicolon, "Expected `;` after expression."); - - return &node.node; - } - - fn breakStatement(self: *Self, loop_scope: ?LoopScope) !*ParseNode { - const start_location = self.parser.previous_token.?; - - if (loop_scope == null) { - self.reportError(.syntax, "break is not allowed here."); - } - - try self.consume(.Semicolon, "Expected `;` after `break`."); - - var node = try self.gc.allocator.create(BreakNode); - node.* = .{}; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.ends_scope = if (loop_scope != null) try self.closeScope(loop_scope.?.loop_body_scope) else null; - - return &node.node; - } - - fn continueStatement(self: *Self, loop_scope: ?LoopScope) !*ParseNode { - const start_location = self.parser.previous_token.?; - - if (loop_scope == null) { - self.reportError(.syntax, "continue is not allowed here."); - } - - try self.consume(.Semicolon, "Expected `;` after `continue`."); - - var node = try self.gc.allocator.create(ContinueNode); - node.* = .{}; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - // Continue only close body scope and not foreach/for loop variables - node.node.ends_scope = try self.closeScope(loop_scope.?.loop_body_scope); - - return &node.node; - } - - fn @"if"(self: *Self, is_statement: bool, loop_scope: ?LoopScope) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - try self.consume(.LeftParen, "Expected `(` after `if`."); - - self.beginScope(); - var condition: *ParseNode = try self.expression(false); - - var unwrapped_identifier: ?Token = null; - var casted_type: ?*ObjTypeDef = null; - if (try self.match(.Arrow)) { // if (opt -> unwrapped) - _ = try self.parseVariable( - false, - try condition.type_def.?.cloneNonOptional(&self.gc.type_registry), - true, - "Expected optional unwrap identifier", - ); - self.markInitialized(); - - unwrapped_identifier = self.parser.previous_token.?; - } else if (try self.match(.As)) { // if (expr as casted) - casted_type = try self.parseTypeDef(null, true); - - _ = try self.parseVariable( - false, - try casted_type.?.toInstance(self.gc.allocator, &self.gc.type_registry), - true, - "Expected casted identifier", - ); - self.markInitialized(); - } - - try self.consume(.RightParen, "Expected `)` after `if` condition."); - - const body = if (is_statement) stmt: { - try self.consume(.LeftBrace, "Expected `{` after `if` condition."); - break :stmt try self.block(loop_scope); - } else try self.expression(false); - - body.ends_scope = try self.endScope(); - - var else_branch: ?*ParseNode = null; - if (!is_statement or self.check(.Else)) { - try self.consume(.Else, "Expected `else` after inline `if` body."); - - if (try self.match(.If)) { - else_branch = try self.@"if"(is_statement, loop_scope); - } else if (is_statement) { - try self.consume(.LeftBrace, "Expected `{` after `else`."); - - self.beginScope(); - else_branch = try self.block(loop_scope); - else_branch.?.ends_scope = try self.endScope(); - } else { - else_branch = try self.expression(false); - } - } - - var node = try self.gc.allocator.create(IfNode); - node.* = IfNode{ - .condition = condition, - .unwrapped_identifier = unwrapped_identifier, - .casted_type = casted_type, - .body = body, - .else_branch = else_branch, - .is_statement = is_statement, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - if (!is_statement) { - if (body.type_def == null or body.type_def.?.def_type == .Void) { - node.node.type_def = else_branch.?.type_def; - } else { - node.node.type_def = body.type_def; - } - - const is_optional = node.node.type_def.?.optional or body.type_def.?.optional or else_branch.?.type_def.?.optional or body.type_def.?.def_type == .Void or else_branch.?.type_def.?.def_type == .Void; - if (is_optional and !node.node.type_def.?.optional) { - node.node.type_def = try node.node.type_def.?.cloneOptional(&self.gc.type_registry); - } - } - - return &node.node; - } - - fn ifStatement(self: *Self, loop_scope: ?LoopScope) anyerror!*ParseNode { - return try self.@"if"(true, loop_scope); - } - - fn forStatement(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - try self.consume(.LeftParen, "Expected `(` after `for`."); - - self.beginScope(); - - // Should be either VarDeclaration or expression - var init_declarations = std.ArrayList(*VarDeclarationNode).init(self.gc.allocator); - while (!self.check(.Semicolon) and !self.check(.Eof)) { - try init_declarations.append( - VarDeclarationNode.cast( - try self.varDeclaration( - false, - try self.parseTypeDef(null, true), - .Nothing, - false, - true, - ), - ).?, - ); - - self.markInitialized(); - - if (!self.check(.Semicolon)) { - try self.consume(.Comma, "Expected `,` after for loop variable"); - } - } - - try self.consume(.Semicolon, "Expected `;` after for loop variables."); - - var condition = try self.expression(false); - - try self.consume(.Semicolon, "Expected `;` after for loop condition."); - - var post_loop = std.ArrayList(*ParseNode).init(self.gc.allocator); - while (!self.check(.RightParen) and !self.check(.Eof)) { - try post_loop.append(try self.expression(false)); - - if (!self.check(.RightParen)) { - try self.consume(.Comma, "Expected `,` after for loop expression"); - } - } - - try self.consume(.RightParen, "Expected `)` after `for` expressions."); - - try self.consume(.LeftBrace, "Expected `{` after `for` definition."); - - self.beginScope(); - var body = try self.block(LoopScope{ - .loop_type = .For, - .loop_body_scope = self.current.?.scope_depth, - }); - body.ends_scope = try self.endScope(); - - var node = try self.gc.allocator.create(ForNode); - node.* = ForNode{ - .init_declarations = init_declarations, - .condition = condition, - .post_loop = post_loop, - .body = body, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.ends_scope = try self.endScope(); - - return &node.node; - } - - fn forEachStatement(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - try self.consume(.LeftParen, "Expected `(` after `foreach`."); - - self.beginScope(); - - var key: ?*ParseNode = try self.varDeclaration( - false, - try self.parseTypeDef(null, true), - .Nothing, - false, - false, - ); - - var value: ?*ParseNode = if (try self.match(.Comma)) - try self.varDeclaration( - false, - try self.parseTypeDef(null, true), - .Nothing, - false, - false, - ) - else - null; - - // If key is omitted, prepare the slot anyway - const key_omitted = value == null; - if (key_omitted) { - value = key; - - key = try self.implicitVarDeclaration( - Token.identifier("$key"), - try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), - false, - ); - - // Switch slots so that key is before value - const key_slot = VarDeclarationNode.cast(value.?).?.slot; - const value_slot = VarDeclarationNode.cast(key.?).?.slot; - VarDeclarationNode.cast(value.?).?.slot = VarDeclarationNode.cast(key.?).?.slot; - VarDeclarationNode.cast(key.?).?.slot = key_slot; - - const value_local = self.current.?.locals[key_slot]; - self.current.?.locals[key_slot] = self.current.?.locals[value_slot]; - self.current.?.locals[value_slot] = value_local; - } - - try self.consume(.In, "Expected `in` after `foreach` variables."); - - // Local not usable by user but needed so that locals are correct - const iterable_slot = try self.addLocal( - Token.identifier("$iterable"), - undefined, - true, - ); - - const iterable = try self.expression(false); - - self.current.?.locals[iterable_slot].type_def = iterable.type_def.?; - - self.markInitialized(); - - try self.consume(.RightParen, "Expected `)` after `foreach`."); - - try self.consume(.LeftBrace, "Expected `{` after `foreach` definition."); - - self.beginScope(); - var body = try self.block(LoopScope{ - .loop_type = .ForEach, - .loop_body_scope = self.current.?.scope_depth, - }); - body.ends_scope = try self.endScope(); - - var node = try self.gc.allocator.create(ForEachNode); - node.* = ForEachNode{ - .key = VarDeclarationNode.cast(key.?).?, - .value = VarDeclarationNode.cast(value.?).?, - .iterable = iterable, - .block = body, - .key_omitted = key_omitted, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.ends_scope = try self.endScope(); - - return &node.node; - } - - fn whileStatement(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - try self.consume(.LeftParen, "Expected `(` after `while`."); - - var condition = try self.expression(false); - - try self.consume(.RightParen, "Expected `)` after `while` condition."); - - try self.consume(.LeftBrace, "Expected `{` after `if` condition."); - - self.beginScope(); - var body = try self.block(LoopScope{ - .loop_type = .While, - .loop_body_scope = self.current.?.scope_depth, - }); - body.ends_scope = try self.endScope(); - - var node = try self.gc.allocator.create(WhileNode); - node.* = WhileNode{ - .condition = condition, - .block = body, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn doUntilStatement(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - try self.consume(.LeftBrace, "Expected `{` after `do`."); - - self.beginScope(); - var body = try self.block(LoopScope{ - .loop_type = .Do, - .loop_body_scope = self.current.?.scope_depth, - }); - body.ends_scope = try self.endScope(); - - try self.consume(.Until, "Expected `until` after `do` block."); - - try self.consume(.LeftParen, "Expected `(` after `until`."); - - var condition = try self.expression(false); - - try self.consume(.RightParen, "Expected `)` after `until` condition."); - - var node = try self.gc.allocator.create(DoUntilNode); - node.* = DoUntilNode{ - .condition = condition, - .block = body, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn returnStatement(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - if (self.current.?.scope_depth == 0) { - self.reportError(.syntax, "Can't use `return` at top-level."); - } - - var value: ?*ParseNode = null; - if (!try self.match(.Semicolon)) { - value = try self.expression(false); - - try self.consume(.Semicolon, "Expected `;` after return value."); - } - - var node = try self.gc.allocator.create(ReturnNode); - node.* = ReturnNode{ - .value = value, - .unconditional = self.current.?.scope_depth == 1, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn tryStatement(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - if (self.current.?.in_try) { - self.reportError(.nested_try, "Nested `try` statement are not allowed"); - } - - self.current.?.in_try = true; - - try self.consume(.LeftBrace, "Expected `{` after `try`"); - - self.beginScope(); - var body = try self.block(null); - body.ends_scope = try self.endScope(); - - var clause_identifiers = std.ArrayList([]const u8).init(self.gc.allocator); - var clauses = std.AutoArrayHashMap(*ObjTypeDef, *ParseNode).init(self.gc.allocator); - var unconditional_clause: ?*ParseNode = null; - // either catch with no type of catch any - while (try self.match(.Catch)) { - if (try self.match(.LeftParen)) { - if (unconditional_clause != null) { - self.reportError(.syntax, "Catch clause not allowed after unconditional catch"); - } - - self.beginScope(); - - const type_def = try self.parseTypeDef(null, true); - - _ = try self.parseVariable( - false, - type_def, - true, // function arguments are constant - "Expected error identifier", - ); - try clause_identifiers.append(self.parser.previous_token.?.lexeme); - self.markInitialized(); - - try self.consume(.RightParen, "Expected `)` after error identifier"); - try self.consume(.LeftBrace, "Expected `{`"); - - var catch_block = try self.block(null); - catch_block.ends_scope = try self.endScope(); - - try clauses.put(type_def, catch_block); - } else if (unconditional_clause == null) { - try self.consume(.LeftBrace, "Expected `{` after `catch`"); - - self.beginScope(); - unconditional_clause = try self.block(null); - unconditional_clause.?.ends_scope = try self.endScope(); - } else { - self.reportError(.syntax, "Expected `(` after `catch`"); - } - } - - self.current.?.in_try = false; - - var node = try self.gc.allocator.create(TryNode); - node.* = TryNode{ - .body = body, - .clauses = clauses, - .clause_identifiers = clause_identifiers.items, - .unconditional_clause = unconditional_clause, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - // Same as varDeclaration but does not parse anything (useful when we need to declare a variable that need to exists but is not exposed to the user) - fn implicitVarDeclaration(self: *Self, name: Token, parsed_type: *ObjTypeDef, constant: bool) !*ParseNode { - const var_type = try parsed_type.toInstance(self.gc.allocator, &self.gc.type_registry); - const slot = try self.declareVariable(var_type, name, constant, true); - - var node = try self.gc.allocator.create(VarDeclarationNode); - node.* = VarDeclarationNode{ - .name = name, - .value = null, - .type_def = var_type, - .constant = constant, - .slot = slot, - .slot_type = if (self.current.?.scope_depth > 0) .Local else .Global, - .expression = false, - }; - node.node.location = self.parser.previous_token.?; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = node.type_def; - - self.markInitialized(); - - return &node.node; - } - - fn varDeclaration( - self: *Self, - identifier_consumed: bool, - parsed_type: ?*ObjTypeDef, - terminator: DeclarationTerminator, - constant: bool, - should_assign: bool, - ) !*ParseNode { - var var_type = if (parsed_type) |ptype| - try ptype.toInstance(self.gc.allocator, &self.gc.type_registry) - else - try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); - - const slot: usize = try self.parseVariable( - identifier_consumed, - var_type, - constant, - "Expected variable name.", - ); - - const name = self.parser.previous_token.?; - const start_location = name; - - var value: ?*ParseNode = null; - - if (should_assign) { - if (try self.match(.Equal)) { - value = try self.expression(false); - } else if (parsed_type == null or !parsed_type.?.optional) { - self.reporter.reportErrorAt( - .syntax, - self.parser.previous_token.?, - "Expected variable initial value", - ); - } - } - - if (var_type.def_type == .Placeholder and value != null and value.?.type_def != null and value.?.type_def.?.def_type == .Placeholder) { - try PlaceholderDef.link(var_type, value.?.type_def.?, .Assignment); - } - - if (parsed_type == null and value != null and value.?.type_def != null) { - var_type = value.?.type_def.?; - - if (self.current.?.scope_depth == 0) { - self.globals.items[slot].type_def = var_type; - } else { - self.current.?.locals[slot].type_def = var_type; - } - } else if (parsed_type == null) { - self.reporter.reportErrorAt( - .inferred_type, - start_location, - "Could not infer variable type.", - ); - } - - var node = try self.gc.allocator.create(VarDeclarationNode); - node.* = VarDeclarationNode{ - .name = name, - .value = value, - .type_def = var_type, - .constant = constant, - .slot = slot, - .slot_type = if (self.current.?.scope_depth > 0) .Local else .Global, - .expression = terminator == .Nothing, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = node.type_def; - - switch (terminator) { - .OptComma => _ = try self.match(.Comma), - .Comma => try self.consume(.Comma, "Expected `,` after variable declaration."), - .Semicolon => try self.consume(.Semicolon, "Expected `;` after variable declaration."), - .Nothing => {}, - } - - self.markInitialized(); - - return &node.node; - } - - // FIXME: this is almost the same as parseUserType! - fn userVarDeclaration(self: *Self, _: bool, constant: bool) !*ParseNode { - var var_type: ?*ObjTypeDef = null; - - const inferred_declaration = self.check(.Equal) and constant; - - // If next token is `=`, means the identifier wasn't a user type but the variable name - // and the type needs to be inferred - if (!inferred_declaration) { - var user_type_name: Token = self.parser.previous_token.?.clone(); - - // Is it a generic type defined in enclosing functions or object? - if (self.resolveGeneric(try self.gc.copyString(self.parser.previous_token.?.lexeme))) |generic_type| { - var_type = generic_type; - } else if (self.current.?.generics != null) { - // Is it generic type defined in a function signature being parsed? - if (self.current.?.generics.?.get(try self.gc.copyString(self.parser.previous_token.?.lexeme))) |generic_type| { - var_type = generic_type; - } - } - - // Search for a global with that name - if (var_type == null) { - if (try self.resolveGlobal(null, user_type_name)) |slot| { - var_type = self.globals.items[slot].type_def; - } - } - - // If none found, create a placeholder - if (var_type == null) { - var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - // TODO: token is wrong but what else can we put here? - .Placeholder = PlaceholderDef.init(self.gc.allocator, user_type_name), - }; - - placeholder_resolved_type.Placeholder.name = try self.gc.copyString( - user_type_name.lexeme, - ); - - var_type = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = placeholder_resolved_type, - }, - ); - - _ = try self.declarePlaceholder(user_type_name, var_type); - } - - // Concrete generic types list - if (try self.match(.DoubleColon)) { - try self.consume(.Less, "Expected generic types list after `::`"); - - var resolved_generics = std.ArrayList(*ObjTypeDef).init(self.gc.allocator); - var i: usize = 0; - while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { - try resolved_generics.append( - try self.parseTypeDef( - if (self.current.?.generics) |generics| - generics.* - else - null, - true, - ), - ); - - if (!self.check(.Greater)) { - try self.consume(.Comma, "Expected `,` between generic types"); - } - } - - try self.consume(.Greater, "Expected `>` after generic types list"); - - resolved_generics.shrinkAndFree(resolved_generics.items.len); - - if (resolved_generics.items.len == 0) { - self.reportErrorAtCurrent(.generic_type, "Expected at least one type"); - } - - // Shouldn't we populate only in codegen? - var_type = try var_type.?.populateGenerics( - self.parser.previous_token.?, - var_type.?.resolved_type.?.Object.id, - resolved_generics.items, - &self.gc.type_registry, - null, - ); - } - - if (try self.match(.Question)) { - var_type = try var_type.?.cloneOptional(&self.gc.type_registry); - } - } - - return try self.varDeclaration( - inferred_declaration, - var_type, - .Semicolon, - constant, - true, - ); - } - - fn zdefStatement(self: *Self) anyerror!*ParseNode { - if (!BuildOptions.jit) { - self.reportError(.zdef, "zdef can't be used, this instance of buzz was built with JIT compiler disabled"); - } - - const start_location = self.parser.previous_token.?; - - try self.consume(.LeftParen, "Expected `(` after `zdef`."); - // TODO: this identifier can have `_` in it - try self.consume(.String, "Expected identifier after `zdef(`."); - const lib_name = self.parser.previous_token.?; - try self.consume(.Comma, "Expected `,` after lib name."); - try self.consume(.String, "Expected zdef declaration."); - const source = self.parser.previous_token.?; - try self.consume(.RightParen, "Expected `)` to close zdef"); - try self.consume(.Semicolon, "Expected `;`"); - - const zdefs = try self.ffi.parse( - self, - source, - false, - ) orelse &[_]*FFI.Zdef{}; - - var elements = std.ArrayList(ZdefNode.ZdefElement).init(self.gc.allocator); - for (zdefs) |zdef| { - var fn_ptr: ?*anyopaque = null; - var slot: usize = undefined; - - assert(self.current.?.scope_depth == 0); - slot = try self.declareVariable( - zdef.type_def, - Token.identifier(zdef.name), - true, - true, - ); - self.markInitialized(); - - // If zig_type is struct, we just push the objtypedef itself on the stack - // Otherwise we try to build a wrapper around the imported function - if (zdef.zig_type == .Fn) { - // Load the lib - const paths = try self.searchZdefLibPaths(lib_name.literal_string.?); - defer { - for (paths.items) |path| { - self.gc.allocator.free(path); - } - paths.deinit(); - } - - var lib: ?std.DynLib = null; - for (paths.items) |path| { - lib = std.DynLib.open(path) catch null; - if (lib != null) { - break; - } - } - - if (lib) |*dlib| { - // Convert symbol names to zig slices - const symbol = try self.gc.allocator.dupeZ(u8, zdef.name); - defer self.gc.allocator.free(symbol); - - // Lookup symbol - const opaque_symbol_method = dlib.lookup(*anyopaque, symbol); - - if (opaque_symbol_method == null) { - self.reportErrorFmt( - .symbol_not_found, - "Could not find symbol `{s}` in lib `{s}`", - .{ - symbol, - lib_name.literal_string.?, - }, - ); - } - - fn_ptr = opaque_symbol_method; - } else { - var search_report = std.ArrayList(u8).init(self.gc.allocator); - defer search_report.deinit(); - var writer = search_report.writer(); - - for (paths.items) |path| { - try writer.print(" no file `{s}`\n", .{path}); - } - - self.reportErrorFmt( - .library_not_found, - "External library `{s}` not found: {s}{s}\n", - .{ - lib_name.literal_string.?, - if (builtin.link_libc) - std.mem.sliceTo(dlerror(), 0) - else - "", - search_report.items, - }, - ); - } - } - - try elements.append( - .{ - .fn_ptr = fn_ptr, - .slot = slot, - .zdef = zdef, - }, - ); - } - - elements.shrinkAndFree(elements.items.len); - - var node = try self.gc.allocator.create(ZdefNode); - node.* = ZdefNode{ - .lib_name = lib_name, - .source = source, - .elements = elements.items, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = null; - - return &node.node; - } - - fn importStatement(self: *Self) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var imported_symbols = std.StringHashMap(void).init(self.gc.allocator); - - while ((try self.match(.Identifier)) and !self.check(.Eof)) { - try imported_symbols.put(self.parser.previous_token.?.lexeme, {}); - - if (!self.check(.From) or self.check(.Comma)) { // Allow trailing comma - try self.consume(.Comma, "Expected `,` after identifier."); - } - } - - var prefix: ?Token = null; - if (imported_symbols.count() > 0) { - try self.consume(.From, "Expected `from` after import identifier list."); - } - - try self.consume(.String, "Expected import path."); - - const path = self.parser.previous_token.?; - if (path.lexeme.len <= 1 or path.literal_string.?.len <= 0) { - self.reporter.reportErrorAt( - .empty_import, - path, - "Import path can't be empty", - ); - } - const file_name: []const u8 = if (path.lexeme.len <= 1 or path.literal_string.?.len <= 0) invalid: { - self.reporter.reportErrorAt( - .empty_import, - path, - "Import path can't be empty", - ); - - break :invalid "invalid"; - } else path.lexeme[1..(path.lexeme.len - 1)]; - - if (imported_symbols.count() == 0 and try self.match(.As)) { - try self.consume(.Identifier, "Expected identifier after `as`."); - prefix = self.parser.previous_token.?; - } - - try self.consume(.Semicolon, "Expected `;` after import."); - - var import = if (!self.reporter.had_error) - try self.importScript( - file_name, - if (prefix) |pr| pr.lexeme else null, - &imported_symbols, - ) - else - null; - - if (imported_symbols.count() > 0) { - var it = imported_symbols.iterator(); - while (it.next()) |kv| { - self.reportErrorFmt(.unknown_import, "Unknown import `{s}`.", .{kv.key_ptr.*}); - } - } - - var node = try self.gc.allocator.create(ImportNode); - node.* = ImportNode{ - .imported_symbols = if (imported_symbols.count() > 0) imported_symbols else null, - .prefix = prefix, - .path = path, - .import = import, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn exportStatement(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - // zig fmt: off - if (self.check(.Identifier) - and ( - try self.checkAhead(.Semicolon, 0) - or try self.checkAhead(.As, 0) - or try self.checkAhead(.Dot, 0))) { - // zig fmt: on - try self.consume(.Identifier, "Expected identifier after `export`."); - var identifier = self.parser.previous_token.?; - - // Search for a global with that name - if (try self.resolveGlobal(null, identifier)) |slot| { - const global: *Global = &self.globals.items[slot]; - global.referenced = true; - var alias: ?Token = null; - - global.exported = true; - if (global.prefix != null or self.check(.As)) { - try self.consume(.As, "Expected `as` after prefixed global."); - try self.consume(.Identifier, "Expected identifier after `as`."); - - global.export_alias = self.parser.previous_token.?.lexeme; - alias = self.parser.previous_token.?; - } - - try self.consume(.Semicolon, "Expected `;` after export."); - - var node = try self.gc.allocator.create(ExportNode); - node.* = ExportNode{ - .identifier = identifier, - .alias = alias, - }; - node.node.location = self.parser.previous_token.?; - node.node.type_def = global.type_def; - - return &node.node; - } - } else { - self.exporting = true; - if (try self.declaration()) |decl| { - self.globals.items[self.globals.items.len - 1].referenced = true; - - self.exporting = false; - var node = try self.gc.allocator.create(ExportNode); - node.* = ExportNode{ - .declaration = decl, - }; - node.node.location = self.parser.previous_token.?; - node.node.type_def = decl.type_def; - - return &node.node; - } - self.exporting = false; - } - - self.reportError(.syntax, "Expected identifier or declaration."); - - var node = try self.gc.allocator.create(ExportNode); - node.* = ExportNode{ - .identifier = Token.identifier("error"), - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn funDeclaration(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - var function_type: FunctionType = .Function; - - if (self.parser.previous_token.?.token_type == .Extern) { - try self.consume(.Fun, "Expected `fun` after `extern`."); - - function_type = .Extern; - } - - try self.consume(.Identifier, "Expected function name."); - var name_token: Token = self.parser.previous_token.?; - - const is_main = std.mem.eql(u8, name_token.lexeme, "main") and self.current.?.function_node.node.type_def != null and self.current.?.function_node.node.type_def.?.resolved_type.?.Function.function_type == .ScriptEntryPoint; - - if (is_main) { - if (function_type == .Extern) { - self.reportError(.extern_main, "`main` can't be `extern`."); - } - - function_type = .EntryPoint; - } - - var function_node: *ParseNode = try self.function(name_token, function_type, null); - - if (function_node.type_def.?.resolved_type.?.Function.lambda) { - try self.consume(.Semicolon, "Expected `;` after lambda function"); - } - - if (function_type == .Extern) { - try self.consume(.Semicolon, "Expected `;` after `extern` function declaration."); - } - - // Enforce main signature - if (is_main) { - const fun_def = function_node.type_def.?.resolved_type.?.Function; - - var signature_valid = true; - if (fun_def.parameters.count() != 1 or (fun_def.return_type.def_type != .Integer and fun_def.return_type.def_type != .Void)) { - signature_valid = false; - } else { - const first_param = fun_def.parameters.get(fun_def.parameters.keys()[0]); - if (first_param == null or - !(try self.parseTypeDefFrom("[str]")).eql(first_param.?)) - { - signature_valid = false; - } - } - - if (!signature_valid) { - const main_def_str = function_node.type_def.?.toStringAlloc(self.gc.allocator) catch @panic("Out of memory"); - defer main_def_str.deinit(); - self.reporter.reportErrorFmt( - .main_signature, - function_node.location, - "Expected `main` signature to be `fun main([str] args) > void|int` got {s}", - .{ - main_def_str.items, - }, - ); - } - } - - const slot: usize = try self.declareVariable( - function_node.type_def.?, - name_token, - true, - true, - ); - - self.markInitialized(); - - var node = try self.gc.allocator.create(FunDeclarationNode); - node.* = FunDeclarationNode{ - .slot = slot, - .slot_type = if (self.current.?.scope_depth == 0) .Global else .Local, - .function = FunctionNode.cast(function_node).?, - }; - node.node.type_def = node.function.node.type_def; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn parseFiberType(self: *Self, generic_types: ?std.AutoArrayHashMap(*ObjString, *ObjTypeDef)) !*ObjTypeDef { - try self.consume(.Less, "Expected `<` after `fib`"); - const return_type = try self.parseTypeDef(generic_types, true); - try self.consume(.Comma, "Expected `,` after fiber return type"); - const yield_type = try self.parseTypeDef(generic_types, true); - - if (!yield_type.optional and yield_type.def_type != .Void) { - self.reportError(.yield_type, "Expected optional type or void"); - } - - try self.consume(.Greater, "Expected `>` after fiber yield type"); - - const fiber_def = ObjFiber.FiberDef{ - .return_type = return_type, - .yield_type = yield_type, - }; - - const resolved_type = ObjTypeDef.TypeUnion{ - .Fiber = fiber_def, - }; - - return try self.gc.type_registry.getTypeDef(ObjTypeDef{ - .optional = try self.match(.Question), - .def_type = .Fiber, - .resolved_type = resolved_type, - }); - } - - fn parseListType(self: *Self, generic_types: ?std.AutoArrayHashMap(*ObjString, *ObjTypeDef)) !*ObjTypeDef { - var list_item_type: *ObjTypeDef = try self.parseTypeDef(generic_types, true); - - try self.consume(.RightBracket, "Expected `]` after list type."); - - var list_def = ObjList.ListDef.init(self.gc.allocator, list_item_type); - var resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ - .List = list_def, - }; - - return try self.gc.type_registry.getTypeDef( - .{ - .optional = try self.match(.Question), - .def_type = .List, - .resolved_type = resolved_type, - }, - ); - } - - fn listDeclaration(self: *Self, constant: bool) !*ParseNode { - if (self.check(.Less) and (self.current.?.scope_depth > 0 or self.current.?.function_node.node.type_def.?.resolved_type.?.Function.function_type == .Repl)) { - // Its a list expression - return try self.expressionStatement(true); - } - - return try self.varDeclaration( - false, - try self.parseListType(null), - .Semicolon, - constant, - true, - ); - } - - fn parseMapType(self: *Self, generic_types: ?std.AutoArrayHashMap(*ObjString, *ObjTypeDef)) !*ObjTypeDef { - var key_type: *ObjTypeDef = try self.parseTypeDef(generic_types, true); - - try self.consume(.Comma, "Expected `,` after key type."); - - var value_type: *ObjTypeDef = try self.parseTypeDef(generic_types, true); - - try self.consume(.RightBrace, "Expected `}` after value type."); - - var map_def = ObjMap.MapDef.init(self.gc.allocator, key_type, value_type); - var resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ - .Map = map_def, - }; - - return try self.gc.type_registry.getTypeDef( - .{ - .optional = try self.match(.Question), - .def_type = .Map, - .resolved_type = resolved_type, - }, - ); - } - - fn mapDeclaration(self: *Self, constant: bool) !*ParseNode { - if (self.check(.Less) and (self.current.?.scope_depth > 0 or self.current.?.function_node.node.type_def.?.resolved_type.?.Function.function_type == .Repl)) { - // Its a map expression - return try self.expressionStatement(true); - } - - return try self.varDeclaration( - false, - try self.parseMapType(null), - .Semicolon, - constant, - true, - ); - } - - fn enumDeclaration(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - if (self.current.?.scope_depth > 0) { - self.reportError(.syntax, "Enum must be defined at top-level."); - } - - var enum_case_type: *ObjTypeDef = undefined; - var case_type_picked: bool = false; - if (try self.match(.LeftParen)) { - enum_case_type = try self.parseTypeDef(null, true); - try self.consume(.RightParen, "Expected `)` after enum type."); - - case_type_picked = true; - } else { - enum_case_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }); - } - - enum_case_type = try enum_case_type.toInstance(self.gc.allocator, &self.gc.type_registry); - - try self.consume(.Identifier, "Expected enum name."); - var enum_name: Token = self.parser.previous_token.?.clone(); - - // Qualified name to avoid cross script collision - const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); - defer self.gc.allocator.free(qualifier); - var qualified_name = std.ArrayList(u8).init(self.gc.allocator); - defer qualified_name.deinit(); - try qualified_name.writer().print("{s}.{s}", .{ qualifier, enum_name.lexeme }); - - var enum_def: ObjEnum.EnumDef = ObjEnum.EnumDef.init( - self.gc.allocator, - try self.gc.copyString(enum_name.lexeme), - try self.gc.copyString(qualified_name.items), - enum_case_type, - ); - - var enum_resolved: ObjTypeDef.TypeUnion = .{ .Enum = enum_def }; - - var enum_type: *ObjTypeDef = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Enum, - .resolved_type = enum_resolved, - }, - ); - - const slot: usize = try self.declareVariable( - enum_type, - enum_name, - true, - true, - ); - self.markInitialized(); - - try self.consume(.LeftBrace, "Expected `{` before enum body."); - - var cases = std.ArrayList(*ParseNode).init(self.gc.allocator); - var picked = std.ArrayList(bool).init(self.gc.allocator); - var case_index: i32 = 0; - while (!self.check(.RightBrace) and !self.check(.Eof)) : (case_index += 1) { - if (case_index > 255) { - self.reportError(.enum_cases_count, "Too many enum cases."); - } - - try self.consume(.Identifier, "Expected case name."); - const case_name: []const u8 = self.parser.previous_token.?.lexeme; - - if (case_type_picked and (enum_case_type.def_type != .String or self.check(.Equal))) { - try self.consume(.Equal, "Expected `=` after case name."); - - try cases.append(try self.expression(false)); - try picked.append(true); - } else { - if (enum_case_type.def_type == .Integer) { - var constant_node = try self.gc.allocator.create(IntegerNode); - constant_node.* = IntegerNode{ - .integer_constant = case_index, - }; - constant_node.node.type_def = try self.gc.type_registry.getTypeDef(.{ - .def_type = .Integer, - }); - constant_node.node.location = self.parser.previous_token.?; - - try cases.append(&constant_node.node); - } else { - var constant_node = try self.gc.allocator.create(StringLiteralNode); - constant_node.* = StringLiteralNode{ - .constant = try self.gc.copyString(case_name), - }; - constant_node.node.type_def = try self.gc.type_registry.getTypeDef(.{ - .def_type = .String, - }); - constant_node.node.location = self.parser.previous_token.?; - - try cases.append(&constant_node.node); - } - - try picked.append(false); - } - - try enum_type.resolved_type.?.Enum.cases.append(case_name); - - // TODO: how to not force a comma at last case? - try self.consume(.Comma, "Expected `,` after case definition."); - } - - try self.consume(.RightBrace, "Expected `}` after enum body."); - - if (case_index == 0) { - self.reportError(.enum_cases_count, "Enum must have at least one case."); - } - - var node = try self.gc.allocator.create(EnumNode); - node.* = EnumNode{ - .slot = slot, - .cases = cases, - .picked = picked, - .case_type_picked = case_type_picked, - }; - node.node.type_def = enum_type; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - enum_type.resolved_type.?.Enum.value = ObjEnum.cast((try EnumNode.val(&node.node, self.gc)).obj()).?; - - return &node.node; - } - - fn anonymousObjectInit(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - try self.consume(.LeftBrace, "Expected `{` after `.`"); - - var node = try self.gc.allocator.create(ObjectInitNode); - node.* = ObjectInitNode.init(self.gc.allocator, null); - node.node.location = start_location; - - const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); - defer self.gc.allocator.free(qualifier); - var qualified_name = std.ArrayList(u8).init(self.gc.allocator); - defer qualified_name.deinit(); - try qualified_name.writer().print("{s}.anonymous", .{qualifier}); - - var object_def = ObjObject.ObjectDef.init( - self.gc.allocator, - start_location, - try self.gc.copyString("anonymous"), - try self.gc.copyString(qualified_name.items), - true, - ); - - var resolved_type = ObjTypeDef.TypeUnion{ .Object = object_def }; - - // We build the object type has we parse its instanciation - var object_type: ObjTypeDef = .{ - .def_type = .Object, - .resolved_type = resolved_type, - }; - - // Anonymous object can only have properties without default values (no methods, no static fields) - // They can't self reference since their anonymous - var fields = std.StringHashMap(void).init(self.gc.allocator); - defer fields.deinit(); - - while (!self.check(.RightBrace) and !self.check(.Eof)) { - try self.consume(.Identifier, "Expected property name"); - - const property_name: []const u8 = self.parser.previous_token.?.lexeme; - - if (fields.get(property_name) != null) { - self.reportError(.property_already_exists, "A property with that name already exists."); - } - - try self.consume(.Equal, "Expected `=` after property name."); - - const expr = try self.expression(false); - - try fields.put(property_name, {}); - try object_type.resolved_type.?.Object.fields.put(property_name, expr.type_def.?); - try node.properties.put(property_name, expr); - - if (!self.check(.RightBrace) or self.check(.Comma)) { - try self.consume(.Comma, "Expected `,` after field initialization."); - } - } - - try self.consume(.RightBrace, "Expected `}` after object initialization."); - - node.node.type_def = try (try self.gc.type_registry.getTypeDef(object_type)).toInstance(self.gc.allocator, &self.gc.type_registry); - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn parseGenericResolve(self: *Self, original_type: *ObjTypeDef) !*ObjTypeDef { - var resolved_generics = std.ArrayList(*ObjTypeDef).init(self.gc.allocator); - - try self.consume(.Less, "Expected `<` at start of generic types list"); - - while (!self.check(.Greater) and !self.check(.Eof)) { - const resolved_generic = try self.parseTypeDef(null, true); - - if (resolved_generic.def_type == .Any) { - self.reportError(.any_generic, "`any` not allowed as generic type"); - } - - try resolved_generics.append(resolved_generic); - - if (!self.check(.Greater)) { - try self.consume(.Comma, "Expected `,` between generic types"); - } - } - - resolved_generics.shrinkAndFree(resolved_generics.items.len); - - try self.consume(.Greater, "Expected `>` after generic types list"); - - return try original_type.populateGenerics( - self.parser.previous_token.?, - if (original_type.def_type == .Function) - original_type.resolved_type.?.Function.id - else if (original_type.def_type == .Object) - original_type.resolved_type.?.Object.id - else - null, - resolved_generics.items, - &self.gc.type_registry, - null, - ); - } - - // This does not produce its own node, it's only use to populate its generic type - fn genericResolve(self: *Self, _: bool, expr: *ParseNode) anyerror!*ParseNode { - expr.type_def = if (expr.type_def) |type_def| - try self.parseGenericResolve(type_def) - else - null; - - return expr; - } - - fn objectInit(self: *Self, _: bool, object: *ParseNode) anyerror!*ParseNode { - var node = try self.gc.allocator.create(ObjectInitNode); - node.* = ObjectInitNode.init(self.gc.allocator, object); - node.node.location = object.location; - - while (!self.check(.RightBrace) and !self.check(.Eof)) { - try self.consume(.Identifier, "Expected property name"); - - const property_name: []const u8 = self.parser.previous_token.?.lexeme; - - var property_placeholder: ?*ObjTypeDef = null; - - // Object is placeholder, create placeholder for the property and link it - if (object.type_def != null and object.type_def.?.def_type == .Placeholder) { - var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, self.parser.previous_token.?), - }; - placeholder_resolved_type.Placeholder.name = try self.gc.copyString(property_name); - - property_placeholder = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = placeholder_resolved_type, - }, - ); - - try PlaceholderDef.link(object.type_def.?, property_placeholder.?, .FieldAccess); - } - - // Named variable with the same name as property - if (self.check(.Comma) or self.check(.RightBrace)) { - try node.properties.put( - property_name, - try self.expression(true), - ); - } else { - try self.consume(.Equal, "Expected `=` after property name."); - - try node.properties.put( - property_name, - try self.expression(false), - ); - } - - if (!self.check(.RightBrace) or self.check(.Comma)) { - try self.consume(.Comma, "Expected `,` after field initialization."); - } - } - - try self.consume(.RightBrace, "Expected `}` after object initialization."); - - node.node.type_def = if (object.type_def) |type_def| - try type_def.toInstance(self.gc.allocator, &self.gc.type_registry) - else - null; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - pub fn expression(self: *Self, hanging: bool) !*ParseNode { - return try self.parsePrecedence(.Assignment, hanging); - } - - // Returns a list of break jumps to patch - fn block(self: *Self, loop_scope: ?LoopScope) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var node = try self.gc.allocator.create(BlockNode); - node.* = BlockNode.init(self.gc.allocator); - - while (!self.check(.RightBrace) and !self.check(.Eof)) { - if (try self.declarationOrStatement(loop_scope)) |declOrStmt| { - try node.statements.append(declOrStmt); - } - } - - try self.consume(.RightBrace, "Expected `}}` after block."); - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - inline fn getRule(token: TokenType) ParseRule { - return rules[@intFromEnum(token)]; - } - - fn parsePrecedence(self: *Self, precedence: Precedence, hanging: bool) !*ParseNode { - // In case we are already parsing an expression, the current unwrap chain should not impact deeper expressions - // Exemple: canBeNull?.aMap[expression] <- here `expression` should not be transformed into an optional - const previous_opt_jumps = self.opt_jumps; - self.opt_jumps = null; - - // If hanging is true, that means we already read the start of the expression - if (!hanging) { - _ = try self.advance(); - } - - var prefixRule: ?ParseFn = getRule(self.parser.previous_token.?.token_type).prefix; - if (prefixRule == null) { - self.reportError(.syntax, "Expected expression."); - - // TODO: find a way to continue or catch that error - return CompileError.Unrecoverable; - } - - var canAssign: bool = @intFromEnum(precedence) <= @intFromEnum(Precedence.Assignment); - var node: *ParseNode = try prefixRule.?(self, canAssign); - - while (@intFromEnum(getRule(self.parser.current_token.?.token_type).precedence) >= @intFromEnum(precedence)) { - // Patch optional jumps - if (self.opt_jumps) |jumps| { - assert(jumps.items.len > 0); - var first_jump: Precedence = jumps.items[0]; - - if (@intFromEnum(getRule(self.parser.current_token.?.token_type).precedence) < @intFromEnum(first_jump)) { - jumps.deinit(); - self.opt_jumps = null; - - node.patch_opt_jumps = true; - - if (node.type_def != null) { - node.type_def = try node.type_def.?.cloneOptional(&self.gc.type_registry); - } - } - } - - _ = try self.advance(); - - var infixRule: InfixParseFn = getRule(self.parser.previous_token.?.token_type).infix.?; - node = try infixRule(self, canAssign, node); - } - - if (self.opt_jumps) |jumps| { - jumps.deinit(); - self.opt_jumps = null; - - node.patch_opt_jumps = true; - - if (node.type_def != null) { - node.type_def = try node.type_def.?.cloneOptional(&self.gc.type_registry); - } - } - - if (canAssign and (try self.match(.Equal))) { - self.reportError(.assignable, "Invalid assignment target."); - } - - self.opt_jumps = previous_opt_jumps; - - return node; - } - - fn namedVariable(self: *Self, name: Token, can_assign: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var var_def: ?*ObjTypeDef = null; - var slot: usize = undefined; - var slot_type: SlotType = undefined; - var slot_constant = false; - if (try self.resolveLocal(self.current.?, name)) |uslot| { - var_def = self.current.?.locals[uslot].type_def; - slot = uslot; - slot_type = .Local; - slot_constant = self.current.?.locals[uslot].constant; - } else if (try self.resolveUpvalue(self.current.?, name)) |uslot| { - var_def = self.current.?.enclosing.?.locals[self.current.?.upvalues[uslot].index].type_def; - slot = uslot; - slot_type = .UpValue; - slot_constant = self.current.?.enclosing.?.locals[self.current.?.upvalues[uslot].index].constant; - } else if (try self.resolveGlobal(null, name)) |uslot| { - var_def = self.globals.items[uslot].type_def; - slot = uslot; - slot_type = .Global; - slot_constant = self.globals.items[uslot].constant; - } else { - slot = try self.declarePlaceholder(name, null); - var_def = self.globals.items[slot].type_def; - slot_type = .Global; - } - - const value = if (can_assign and try self.match(.Equal)) - try self.expression(false) - else - null; - - var node = try self.gc.allocator.create(NamedVariableNode); - node.* = NamedVariableNode{ - .identifier = start_location, - .value = value, - .slot = slot, - .slot_type = slot_type, - .slot_constant = slot_constant, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = var_def; - - return &node.node; - } - - fn yield(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var node = try self.gc.allocator.create(YieldNode); - node.* = YieldNode{ - .expression = try self.parsePrecedence(.Primary, false), - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = node.expression.type_def; - - return &node.node; - } - - fn resolveFiber(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var node = try self.gc.allocator.create(ResolveNode); - node.* = ResolveNode{ - .fiber = try self.parsePrecedence(.Primary, false), - }; - - const fiber_type = node.fiber.type_def; - - if (fiber_type == null) { - unreachable; - } else if (fiber_type.?.def_type == .Placeholder) { - const return_placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, self.parser.previous_token.?), - }; - const return_placeholder = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = return_placeholder_resolved_type, - }, - ); - - try PlaceholderDef.link(fiber_type.?, return_placeholder, .Yield); - - node.node.type_def = return_placeholder; - } else { - if (fiber_type.?.def_type != .Fiber) { - self.reporter.reportErrorAt(.resolvable, node.fiber.location, "Can't be resolveed"); - } else { - const fiber = fiber_type.?.resolved_type.?.Fiber; - - node.node.type_def = fiber.return_type; - } - } - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn resumeFiber(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var node = try self.gc.allocator.create(ResumeNode); - node.* = ResumeNode{ - .fiber = try self.parsePrecedence(.Primary, false), - }; - - const fiber_type = node.fiber.type_def; - - if (fiber_type == null) { - unreachable; - } else if (fiber_type.?.def_type == .Placeholder) { - const yield_placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, self.parser.previous_token.?), - }; - var yield_placeholder = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = yield_placeholder_resolved_type, - }, - ); - - yield_placeholder = try yield_placeholder.cloneOptional(&self.gc.type_registry); - - try PlaceholderDef.link(fiber_type.?, yield_placeholder, .Yield); - - node.node.type_def = yield_placeholder; - } else { - if (fiber_type.?.def_type != .Fiber) { - self.reporter.reportErrorAt(.resumable, node.fiber.location, "Can't be resumed"); - } else { - const fiber = fiber_type.?.resolved_type.?.Fiber; - - // Resume returns null if nothing was yielded and/or fiber reached its return statement - node.node.type_def = fiber.yield_type; - } - } - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn asyncCall(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - const callable = try self.parsePrecedence(.Call, false); - - var node = try self.gc.allocator.create(AsyncCallNode); - node.* = AsyncCallNode{ - .call = callable, - }; - - // Expression after `&` must either be a call or a dot call - if (callable.node_type != .Call and (callable.node_type != .Dot or DotNode.cast(callable).?.call == null)) { - self.reporter.reportErrorAt(.syntax, callable.location, "Expected function call after `async`"); - - return &node.node; - } - - var call_node = switch (callable.node_type) { - .Call => CallNode.cast(callable).?, - .Dot => DotNode.cast(callable).?.call.?, - else => unreachable, - }; - call_node.async_call = true; - const function_type = call_node.callable_type; - - if (function_type == null) { - unreachable; - } else if (function_type.?.def_type == .Placeholder) { - // create placeholders for return and yield types and link them with .Call and .Yield - const return_placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, self.parser.previous_token.?), - }; - const return_placeholder = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = return_placeholder_resolved_type, - }, - ); - - try PlaceholderDef.link(function_type.?, return_placeholder, .Call); - - const yield_placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, self.parser.previous_token.?), - }; - var yield_placeholder = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = yield_placeholder_resolved_type, - }, - ); - - try PlaceholderDef.link(function_type.?, yield_placeholder, .Yield); - - const fiber_def = ObjFiber.FiberDef{ - .return_type = return_placeholder, - .yield_type = yield_placeholder, - }; - - const resolved_type = ObjTypeDef.TypeUnion{ - .Fiber = fiber_def, - }; - - node.node.type_def = try self.gc.type_registry.getTypeDef(ObjTypeDef{ - .optional = try self.match(.Question), - .def_type = .Fiber, - .resolved_type = resolved_type, - }); - } else { - if (function_type.?.def_type != .Function) { - self.reporter.reportErrorAt(.callable, call_node.callee.location, "Can't be called"); - } else { - const return_type = function_type.?.resolved_type.?.Function.return_type; - const yield_type = function_type.?.resolved_type.?.Function.yield_type; - - const fiber_def = ObjFiber.FiberDef{ - .return_type = return_type, - .yield_type = yield_type, - }; - - const resolved_type = ObjTypeDef.TypeUnion{ - .Fiber = fiber_def, - }; - - node.node.type_def = try self.gc.type_registry.getTypeDef( - ObjTypeDef{ - .optional = try self.match(.Question), - .def_type = .Fiber, - .resolved_type = resolved_type, - }, - ); - } - } - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn pattern(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var node = try self.gc.allocator.create(PatternNode); - - const source_slice = self.parser.previous_token.?.literal_string.?; - // Replace escaped pattern delimiter with delimiter - const source = try std.mem.replaceOwned( - u8, - self.gc.allocator, - source_slice, - "\\\"", - "\"", - ); - - var err_code: c_int = undefined; - var err_offset: usize = undefined; - const reg = pcre.compile( - source.ptr, - source.len, - // TODO: provide options to user - 0, - &err_code, - &err_offset, - null, - ); - - if (reg == null) { - var location = self.parser.previous_token.?.clone(); - location.column += @intCast(err_offset + 2); - location.lexeme = location.lexeme[@intCast(2 + err_offset)..@intCast(2 + err_offset + 1)]; - - var err_buf: [256]u8 = undefined; - const err_len = pcre.getErrorMessage( - err_code, - &err_buf, - err_buf.len, - ); - - self.reporter.reportErrorFmt( - .pattern, - location, - "Could not compile pattern, error at {}: {s}", - .{ - err_offset, - err_buf[0..@intCast(err_len)], - }, - ); - return CompileError.Unrecoverable; - } - - var constant = try self.gc.allocateObject( - ObjPattern, - .{ - .source = source, - .pattern = reg.?, - }, - ); - - node.* = PatternNode{ - .constant = constant, - }; - node.node.type_def = try self.gc.type_registry.getTypeDef(.{ - .def_type = .Pattern, - }); - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn integer(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var node = try self.gc.allocator.create(IntegerNode); - - node.* = IntegerNode{ - .integer_constant = self.parser.previous_token.?.literal_integer.?, - }; - node.node.type_def = try self.gc.type_registry.getTypeDef(.{ - .def_type = .Integer, - }); - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn range(self: *Self, _: bool, low: *ParseNode) anyerror!*ParseNode { - var node = try self.gc.allocator.create(RangeNode); - const int_type = try self.gc.type_registry.getTypeDef(.{ - .def_type = .Integer, - }); - - const high = try self.expression(false); - - self.markInitialized(); - - node.* = RangeNode{ - .low = low, - .hi = high, - }; - const list_def = ObjList.ListDef.init(self.gc.allocator, int_type); - const resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ - .List = list_def, - }; - node.node.type_def = try self.gc.type_registry.getTypeDef( - .{ - .optional = false, - .def_type = .List, - .resolved_type = resolved_type, - }, - ); - node.node.location = low.location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn float(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var node = try self.gc.allocator.create(FloatNode); - - node.* = FloatNode{ - .float_constant = self.parser.previous_token.?.literal_float.?, - }; - node.node.type_def = try self.gc.type_registry.getTypeDef(.{ - .def_type = .Float, - }); - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn string(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - const string_token = self.parser.previous_token.?; - var string_parser = StringParser.init( - self, - string_token.literal_string.?, - self.script_name, - string_token.line, - string_token.column, - ); - const string_node = try string_parser.parse(); - string_node.node.location = start_location; - string_node.node.end_location = self.parser.previous_token.?; - - return &string_node.node; - } - - fn grouping(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - var node = try self.gc.allocator.create(GroupingNode); - node.* = GroupingNode{ - .expression = try self.expression(false), - }; - node.node.type_def = node.expression.type_def; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - try self.consume(.RightParen, "Expected ')' after expression."); - - return &node.node; - } - - fn nullLiteral(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - var node = try self.gc.allocator.create(NullNode); - - node.* = NullNode{}; - - node.node.type_def = try self.gc.type_registry.getTypeDef(.{ - .def_type = .Void, - }); - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn literal(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - switch (self.parser.previous_token.?.token_type) { - .False => { - var node = try self.gc.allocator.create(BooleanNode); - - node.* = BooleanNode{ .constant = false }; - - node.node.type_def = try self.gc.type_registry.getTypeDef(.{ - .def_type = .Bool, - }); - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - }, - .True => { - var node = try self.gc.allocator.create(BooleanNode); - - node.* = BooleanNode{ .constant = true }; - - node.node.type_def = try self.gc.type_registry.getTypeDef(.{ - .def_type = .Bool, - }); - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - }, - .Null => return self.nullLiteral(), - .Void => { - var node = try self.gc.allocator.create(VoidNode); - - node.* = VoidNode{}; - - node.node.type_def = try self.gc.type_registry.getTypeDef(.{ - .def_type = .Void, - }); - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - }, - else => unreachable, - } - } - - fn typeOfExpression(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - const expr = try self.parsePrecedence(.Unary, false); - - var node = try self.gc.allocator.create(TypeOfExpressionNode); - node.* = .{ - .expression = expr, - }; - node.node.type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .Type }); - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn typeExpression(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - const type_def = try self.parseTypeDef(null, true); - - try self.consume(.Greater, "Expected `>` after type expression."); - - var node = try self.gc.allocator.create(TypeExpressionNode); - node.* = .{ - .value = type_def.toValue(), - }; - node.node.type_def = try self.gc.type_registry.getTypeDef(.{ .def_type = .Type }); - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn unary(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var operator: TokenType = self.parser.previous_token.?.token_type; - - var left: *ParseNode = try self.parsePrecedence(.Unary, false); - - var node = try self.gc.allocator.create(UnaryNode); - node.* = UnaryNode{ - .left = left, - .operator = operator, - }; - node.node.type_def = left.type_def; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn inlineIf(self: *Self, _: bool) anyerror!*ParseNode { - return try self.@"if"(false, null); - } - - fn argumentList(self: *Self, trailing_comma: *bool) !std.AutoArrayHashMap(*ObjString, *ParseNode) { - var arguments = std.AutoArrayHashMap(*ObjString, *ParseNode).init(self.gc.allocator); - - var arg_count: u8 = 0; - while (!(try self.match(.RightParen)) and !(try self.match(.Eof))) { - var hanging = false; - const arg_name: ?Token = if (try self.match(.Identifier)) - self.parser.previous_token.? - else - null; - - if (arg_name != null) { - if (arg_count == 0 or self.check(.Comma) or self.check(.RightParen)) { - // The identifier we just parsed might not be the argument name but the start of an expression or the expression itself - hanging = !(try self.match(.Colon)); - } else { - try self.consume(.Colon, "Expected `:` after argument name."); - } - } else if (arg_count != 0 and arg_name == null) { - self.reportError(.syntax, "Expected argument name."); - break; - } - - const is_named_expr = arg_count != 0 and arg_name != null and hanging and (self.check(.Comma) or self.check(.RightParen)); - - try arguments.put( - try self.gc.copyString( - if ((!hanging and arg_name != null) or is_named_expr) - arg_name.?.lexeme - else - "$", - ), - try self.expression(hanging), - ); - - if (arg_count == 255) { - self.reportError(.arguments_count, "Can't have more than 255 arguments."); - - return arguments; - } - - arg_count += 1; - - if (!self.check(.RightParen)) { - trailing_comma.* = true; - try self.consume(.Comma, "Expected `,` after call argument"); - } else { - trailing_comma.* = false; - } - } - - return arguments; - } - - fn call(self: *Self, _: bool, callee: *ParseNode) anyerror!*ParseNode { - const function_type = if (callee.type_def != null and callee.type_def.?.def_type == .Function) - callee.type_def.? - else - null; - _ = function_type; - - var trailing_comma = false; - - var node = try self.gc.allocator.create(CallNode); - node.* = CallNode{ - .callee = callee, - // In the case of a dot call, callee's type will change to the function return type - // so we keep a reference to it here - .callable_type = callee.type_def, - .arguments = try self.argumentList(&trailing_comma), - .catch_default = try self.inlineCatch(), - .trailing_comma = trailing_comma, - }; - - // Node type is Function or Native return type or nothing/placeholder - node.node.type_def = if (callee.type_def != null and callee.type_def.?.def_type == .Function) - callee.type_def.?.resolved_type.?.Function.return_type - else if (callee.type_def != null and callee.type_def.?.def_type == .Enum) - try (try callee.type_def.?.toInstance(self.gc.allocator, &self.gc.type_registry)).cloneOptional(&self.gc.type_registry) - else - null; - - // If null, create placeholder - if (node.node.type_def == null) { - if (callee.type_def == null or callee.type_def.?.def_type != .Placeholder) { - self.reporter.reportErrorAt(.callable, callee.location, "Can't be called"); - } else { - var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, node.node.location), - }; - - node.node.type_def = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = placeholder_resolved_type, - }, - ); - - try PlaceholderDef.link(callee.type_def.?, node.node.type_def.?, .Call); - } - } - - node.node.location = callee.location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn unwrap(self: *Self, _: bool, unwrapped: *ParseNode) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var node = try self.gc.allocator.create(UnwrapNode); - node.* = UnwrapNode{ - .unwrapped = unwrapped, - .original_type = unwrapped.type_def, - }; - - node.node.type_def = if (unwrapped.type_def) |type_def| try type_def.cloneNonOptional(&self.gc.type_registry) else null; - - if (self.opt_jumps == null) { - self.opt_jumps = std.ArrayList(Precedence).init(self.gc.allocator); - } - try self.opt_jumps.?.append(getRule(self.parser.current_token.?.token_type).precedence); - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn forceUnwrap(self: *Self, _: bool, unwrapped: *ParseNode) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var node = try self.gc.allocator.create(ForceUnwrapNode); - node.* = ForceUnwrapNode{ - .unwrapped = unwrapped, - .original_type = unwrapped.type_def, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - node.node.type_def = if (unwrapped.type_def) |type_def| try type_def.cloneNonOptional(&self.gc.type_registry) else null; - - return &node.node; - } - - fn variable(self: *Self, can_assign: bool) anyerror!*ParseNode { - return try self.namedVariable(self.parser.previous_token.?, can_assign); - } - - fn dot(self: *Self, can_assign: bool, callee: *ParseNode) anyerror!*ParseNode { - const start_location = callee.location; - - try self.consume(.Identifier, "Expected property name after `.`"); - var member_name_token: Token = self.parser.previous_token.?; - var member_name: []const u8 = member_name_token.lexeme; - - var node = try self.gc.allocator.create(DotNode); - node.* = DotNode{ - .callee = callee, - .identifier = self.parser.previous_token.?, - }; - - node.node.location = start_location; - - // Check that name is a property - - // If not even a placeholder, we most likely raised an error before - if (callee.type_def != null) { - const callee_def_type = callee.type_def.?.def_type; - switch (callee_def_type) { - .String => { - if (try ObjString.memberDef(self, member_name)) |member_type_def| { - const member = if (try self.match(.DoubleColon)) - try self.parseGenericResolve(member_type_def) - else - member_type_def; - - if (try self.match(.LeftParen)) { - // `call` will look to the parent node for the function definition - node.node.type_def = member; - node.member_type_def = member; - - node.call = CallNode.cast(try self.call(can_assign, &node.node)).?; - - // Node type is the return type of the call - node.node.type_def = node.call.?.node.type_def; - } else { - // String has only native functions members - node.node.type_def = member; - } - } else { - self.reportError(.property_does_not_exists, "String property doesn't exist."); - } - }, - .Pattern => { - if (try ObjPattern.memberDef(self, member_name)) |member_type_def| { - const member = if (try self.match(.DoubleColon)) - try self.parseGenericResolve(member_type_def) - else - member_type_def; - - if (try self.match(.LeftParen)) { - // `call` will look to the parent node for the function definition - node.node.type_def = member; - node.member_type_def = member; - - node.call = CallNode.cast(try self.call(can_assign, &node.node)).?; - - // Node type is the return type of the call - node.node.type_def = node.call.?.node.type_def; - } else { - // Pattern has only native functions members - node.node.type_def = member; - } - } else { - self.reportError(.property_does_not_exists, "Pattern property doesn't exist."); - } - }, - .Fiber => { - if (try ObjFiber.memberDef(self, member_name)) |member_type_def| { - const member = if (try self.match(.DoubleColon)) - try self.parseGenericResolve(member_type_def) - else - member_type_def; - - if (try self.match(.LeftParen)) { - // `call` will look to the parent node for the function definition - node.node.type_def = member; - node.member_type_def = member; - - node.call = CallNode.cast(try self.call(can_assign, &node.node)).?; - - // Node type is the return type of the call - node.node.type_def = node.call.?.node.type_def; - } else { - // Fiber has only native functions members - node.node.type_def = member; - } - } else { - self.reportError(.property_does_not_exists, "Fiber property doesn't exist."); - } - }, - .Object => { - var obj_def: ObjObject.ObjectDef = callee.type_def.?.resolved_type.?.Object; - - var property_type: ?*ObjTypeDef = obj_def.static_fields.get(member_name) orelse obj_def.static_placeholders.get(member_name); - - // Not found, create a placeholder, this is a root placeholder not linked to anything - // TODO: test with something else than a name - if (property_type == null and self.current_object != null and std.mem.eql(u8, self.current_object.?.name.lexeme, obj_def.name.string)) { - var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, member_name_token), - }; - placeholder_resolved_type.Placeholder.name = try self.gc.copyString(member_name_token.lexeme); - - var placeholder: *ObjTypeDef = try self.gc.type_registry.getTypeDef(.{ - .optional = false, - .def_type = .Placeholder, - .resolved_type = placeholder_resolved_type, - }); - - if (BuildOptions.debug_placeholders) { - std.debug.print( - "static placeholder @{} for `{s}`\n", - .{ - @intFromPtr(placeholder), - member_name_token.lexeme, - }, - ); - } - try callee.type_def.?.resolved_type.?.Object.static_placeholders.put(member_name, placeholder); - - property_type = placeholder; - } else if (property_type == null) { - self.reportErrorFmt(.property_does_not_exists, "Static property `{s}` does not exists in {s}", .{ member_name, obj_def.name.string }); - } - - property_type = if (property_type != null and try self.match(.DoubleColon)) - try self.parseGenericResolve(property_type.?) - else - property_type; - - // Do we assign it ? - if (can_assign and try self.match(.Equal)) { - node.value = try self.expression(false); - - node.node.type_def = property_type; - } else if (try self.match(.LeftParen)) { // Do we call it - // `call` will look to the parent node for the function definition - node.node.type_def = property_type; - node.member_type_def = property_type; - - node.call = CallNode.cast(try self.call(can_assign, &node.node)).?; - - // Node type is the return type of the call - node.node.type_def = node.call.?.node.type_def; - } else { // access only - node.node.type_def = property_type; - } - }, - .ForeignContainer => { - const f_def = callee.type_def.?.resolved_type.?.ForeignContainer; - - if (f_def.buzz_type.get(member_name)) |field| { - if (can_assign and try self.match(.Equal)) { - node.value = try self.expression(false); - } - - node.node.type_def = field; - } else { - self.reportErrorFmt( - .property_does_not_exists, - "Property `{s}` does not exists in object `{s}`", - .{ - member_name, - f_def.name.string, - }, - ); - } - }, - .ObjectInstance => { - var object: *ObjTypeDef = callee.type_def.?.resolved_type.?.ObjectInstance; - var obj_def: ObjObject.ObjectDef = object.resolved_type.?.Object; - - // Is it a method - var property_type: ?*ObjTypeDef = obj_def.methods.get(member_name); - - // Is it a property - property_type = property_type orelse obj_def.fields.get(member_name) orelse obj_def.placeholders.get(member_name); - - // Else create placeholder - if (property_type == null and self.current_object != null and std.mem.eql(u8, self.current_object.?.name.lexeme, obj_def.name.string)) { - var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, member_name_token), - }; - placeholder_resolved_type.Placeholder.name = try self.gc.copyString(member_name); - - var placeholder: *ObjTypeDef = try self.gc.type_registry.getTypeDef(.{ - .optional = false, - .def_type = .Placeholder, - .resolved_type = placeholder_resolved_type, - }); - - if (BuildOptions.debug_placeholders) { - std.debug.print( - "property placeholder @{} for `{s}.{s}`\n", - .{ - @intFromPtr(placeholder), - object.resolved_type.?.Object.name.string, - member_name_token.lexeme, - }, - ); - } - try object.resolved_type.?.Object.placeholders.put(member_name, placeholder); - - property_type = placeholder; - } else if (property_type == null) { - self.reportErrorFmt( - .property_does_not_exists, - "Property `{s}` does not exists in object `{s}`", - .{ member_name, obj_def.name.string }, - ); - } - - property_type = if (property_type != null and try self.match(.DoubleColon)) - try self.parseGenericResolve(property_type.?) - else - property_type; - - // If its a field or placeholder, we can assign to it - // TODO: here get info that field is constant or not - if (can_assign and try self.match(.Equal)) { - node.value = try self.expression(false); - - node.node.type_def = property_type; - } else if (try self.match(.LeftParen)) { // If it's a method or placeholder we can call it - // `call` will look to the parent node for the function definition - node.node.type_def = property_type; - node.member_type_def = property_type; - - node.call = CallNode.cast(try self.call(can_assign, &node.node)).?; - - // Node type is the return type of the call - node.node.type_def = node.call.?.node.type_def; - } else { - node.node.type_def = property_type; - } - }, - .ProtocolInstance => { - var protocol: *ObjTypeDef = callee.type_def.?.resolved_type.?.ProtocolInstance; - var protocol_def: ObjObject.ProtocolDef = protocol.resolved_type.?.Protocol; - - var method_type: ?*ObjTypeDef = protocol_def.methods.get(member_name); - - // Else create placeholder - if (method_type == null) { - self.reportErrorFmt( - .property_does_not_exists, - "Method `{s}` does not exists in protocol `{s}`", - .{ - member_name, - protocol_def.name.string, - }, - ); - } - - // Only call is allowed - method_type = if (method_type != null and try self.match(.DoubleColon)) - try self.parseGenericResolve(method_type.?) - else - method_type; - if (try self.match(.LeftParen)) { - // `call` will look to the parent node for the function definition - node.node.type_def = method_type; - node.member_type_def = method_type; - - node.call = CallNode.cast(try self.call(can_assign, &node.node)).?; - - // Node type is the return type of the call - node.node.type_def = node.call.?.node.type_def; - } else { - node.node.type_def = method_type; - } - }, - .Enum => { - const enum_def = callee.type_def.?.resolved_type.?.Enum; - - for (enum_def.cases.items, 0..) |case, index| { - if (mem.eql(u8, case, member_name)) { - var enum_instance_resolved_type: ObjTypeDef.TypeUnion = .{ - .EnumInstance = callee.type_def.?, - }; - - var enum_instance: *ObjTypeDef = try self.gc.type_registry.getTypeDef(.{ - .optional = false, - .def_type = .EnumInstance, - .resolved_type = enum_instance_resolved_type, - }); - - node.node.type_def = enum_instance; - node.enum_index = index; - break; - } - } - - if (node.node.type_def == null) { - // TODO: reportWithOrigin - self.reportErrorFmt( - .enum_case, - "Enum case `{s}` does not exists.", - .{ - member_name, - }, - ); - } - }, - .EnumInstance => { - // Only available field is `.value` to get associated value - if (!mem.eql(u8, member_name, "value")) { - self.reportError(.property_does_not_exists, "Enum provides only field `value`."); - } - - node.node.type_def = callee.type_def.?.resolved_type.?.EnumInstance.resolved_type.?.Enum.enum_type; - }, - .List => { - if (try ObjList.ListDef.member(callee.type_def.?, self, member_name)) |member_type_def| { - const member = if (try self.match(.DoubleColon)) - try self.parseGenericResolve(member_type_def) - else - member_type_def; - - if (try self.match(.LeftParen)) { - // `call` will look to the parent node for the function definition - node.node.type_def = member; - node.member_type_def = member; - - node.call = CallNode.cast(try self.call(can_assign, &node.node)).?; - - // Node type is the return type of the call - node.node.type_def = node.call.?.node.type_def; - } else { - node.node.type_def = member; - } - } else { - self.reportError(.property_does_not_exists, "List property doesn't exist."); - } - }, - .Map => { - if (try ObjMap.MapDef.member(callee.type_def.?, self, member_name)) |member_type_def| { - const member = if (try self.match(.DoubleColon)) - try self.parseGenericResolve(member_type_def) - else - member_type_def; - - if (try self.match(.LeftParen)) { - // `call` will look to the parent node for the function definition - node.node.type_def = member; - node.member_type_def = member; - - node.call = CallNode.cast(try self.call(can_assign, &node.node)).?; - - // Node type is the return type of the call - node.node.type_def = node.call.?.node.type_def; - } else { - node.node.type_def = member; - } - } else { - self.reportError(.property_does_not_exists, "Map property doesn't exist."); - } - }, - .Placeholder => { - // We know nothing of the field - var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, member_name_token), - }; - - placeholder_resolved_type.Placeholder.name = try self.gc.copyString(member_name); - - var placeholder = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = placeholder_resolved_type, - }, - ); - - try PlaceholderDef.link( - callee.type_def.?, - placeholder, - .FieldAccess, - ); - - placeholder = if (try self.match(.DoubleColon)) - try self.parseGenericResolve(placeholder) - else - placeholder; - - if (can_assign and try self.match(.Equal)) { - node.value = try self.expression(false); - } else if (try self.match(.LeftParen)) { - // `call` will look to the parent node for the function definition - node.node.type_def = placeholder; - node.member_type_def = placeholder; - - node.call = CallNode.cast(try self.call(can_assign, &node.node)).?; - // TODO: here maybe we invoke instead of call?? - - // Node type is the return type of the call - node.node.type_def = node.call.?.node.type_def; - } else { - node.node.type_def = placeholder; - } - }, - else => { - self.reporter.reportErrorFmt( - .field_access, - callee.location, - "`{s}` is not field accessible", - .{ - (try callee.type_def.?.toStringAlloc(self.gc.allocator)).items, - }, - ); - }, - } - } - - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn and_(self: *Self, _: bool, left: *ParseNode) anyerror!*ParseNode { - const start_location = left.location; - - var right: *ParseNode = try self.parsePrecedence(.And, false); - - var node = try self.gc.allocator.create(BinaryNode); - node.* = BinaryNode{ - .left = left, - .right = right, - .operator = .And, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Bool, - }, - ); - - return &node.node; - } - - fn or_(self: *Self, _: bool, left: *ParseNode) anyerror!*ParseNode { - const start_location = left.location; - - var right: *ParseNode = try self.parsePrecedence(.And, false); - - var node = try self.gc.allocator.create(BinaryNode); - node.* = BinaryNode{ - .left = left, - .right = right, - .operator = .Or, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Bool, - }, - ); - - return &node.node; - } - - fn is(self: *Self, _: bool, left: *ParseNode) anyerror!*ParseNode { - const start_location = left.location; - - const constant = (try self.parseTypeDef(null, true)).toValue(); - - var node = try self.gc.allocator.create(IsNode); - node.* = IsNode{ - .left = left, - .constant = constant, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Bool, - }, - ); - - return &node.node; - } - - fn as(self: *Self, _: bool, left: *ParseNode) anyerror!*ParseNode { - const start_location = left.location; - - const constant = (try self.parseTypeDef(null, false)).toValue(); - - var node = try self.gc.allocator.create(AsNode); - node.* = AsNode{ - .left = left, - .constant = constant, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = try ObjTypeDef.cast(constant.obj()).?.cloneOptional(&self.gc.type_registry); - - return &node.node; - } - - fn binary(self: *Self, _: bool, left: *ParseNode) anyerror!*ParseNode { - const start_location = left.location; - - const operator: TokenType = self.parser.previous_token.?.token_type; - const rule: ParseRule = getRule(operator); - - const right: *ParseNode = try self.parsePrecedence( - @enumFromInt(@intFromEnum(rule.precedence) + 1), - false, - ); - - var node = try self.gc.allocator.create(BinaryNode); - node.* = BinaryNode{ - .left = left, - .right = right, - .operator = operator, - }; - - node.node.type_def = switch (operator) { - .QuestionQuestion => right.type_def, - - .Greater, - .Less, - .GreaterEqual, - .LessEqual, - .BangEqual, - .EqualEqual, - => try self.gc.type_registry.getTypeDef(.{ .def_type = .Bool }), - - .Plus => left.type_def orelse right.type_def, - - .ShiftLeft, - .ShiftRight, - .Ampersand, - .Bor, - .Xor, - => try self.gc.type_registry.getTypeDef(.{ .def_type = .Integer }), - - .Minus, - .Star, - .Percent, - .Slash, - => try self.gc.type_registry.getTypeDef( - ObjTypeDef{ - .def_type = if ((left.type_def != null and left.type_def.?.def_type == .Float) or (right.type_def != null and right.type_def.?.def_type == .Float)) - .Float - else - .Integer, - }, - ), - - else => unreachable, - }; - - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return &node.node; - } - - fn subscript(self: *Self, can_assign: bool, subscripted: *ParseNode) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - const index: *ParseNode = try self.expression(false); - - if (subscripted.type_def.?.def_type == .Placeholder and index.type_def.?.def_type == .Placeholder) { - try PlaceholderDef.link(subscripted.type_def.?, index.type_def.?, .Key); - } - - var subscripted_type_def: ?*ObjTypeDef = null; - - if (subscripted.type_def) |type_def| { - if (!type_def.optional) { - switch (type_def.def_type) { - .Placeholder => { - var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, self.parser.previous_token.?), - }; - - var placeholder = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = placeholder_resolved_type, - }, - ); - - try PlaceholderDef.link(type_def, placeholder, .Subscript); - - subscripted_type_def = placeholder; - }, - .String => subscripted_type_def = type_def, - .List => subscripted_type_def = type_def.resolved_type.?.List.item_type, - .Map => subscripted_type_def = try type_def.resolved_type.?.Map.value_type.cloneOptional(&self.gc.type_registry), - else => self.reportErrorFmt( - .subscriptable, - "Type `{s}` is not subscriptable", - .{(try type_def.toStringAlloc(self.gc.allocator)).items}, - ), - } - } else { - self.reportError(.subscriptable, "Optional type is not subscriptable"); - } - } - - try self.consume(.RightBracket, "Expected `]`."); - - var value: ?*ParseNode = null; - if (can_assign and try self.match(.Equal) and (subscripted.type_def == null or subscripted.type_def.?.def_type != .String)) { - value = try self.expression(false); - - if (subscripted.type_def.?.def_type == .Placeholder and value.?.type_def.?.def_type == .Placeholder) { - try PlaceholderDef.link(subscripted.type_def.?, value.?.type_def.?, .Subscript); - } - } - - var node = try self.gc.allocator.create(SubscriptNode); - node.* = SubscriptNode{ - .subscripted = subscripted, - .index = index, - .value = value, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = subscripted_type_def; - - return &node.node; - } - - fn list(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var trailing_comma: bool = false; - var items = std.ArrayList(*ParseNode).init(self.gc.allocator); - var item_type: ?*ObjTypeDef = null; - - // A list expression can specify its type `[, ...]` - if (try self.match(.Less)) { - item_type = try self.parseTypeDef(null, true); - - try self.consume(.Greater, "Expected `>` after list type."); - } - - if (item_type == null or try self.match(.Comma)) { - var common_type: ?*ObjTypeDef = null; - while (!(try self.match(.RightBracket)) and !(try self.match(.Eof))) { - var actual_item: *ParseNode = try self.expression(false); - - try items.append(actual_item); - - if (item_type == null) { - if (common_type == null) { - common_type = actual_item.type_def; - } else if (actual_item.type_def) |actual_type_def| { - if (!common_type.?.eql(actual_type_def)) { - if (common_type.?.def_type == .ObjectInstance and actual_type_def.def_type == .ObjectInstance) { - common_type = common_type.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.both_conforms(actual_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object) orelse common_type; - common_type = try common_type.?.toInstance(self.gc.allocator, &self.gc.type_registry); - } else { - common_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Any }); - } - } - } - } - - if (!self.check(.RightBracket)) { - trailing_comma = true; - try self.consume(.Comma, "Expected `,` after list item."); - } else { - trailing_comma = false; - } - } - - if (self.parser.previous_token.?.token_type != .RightBracket) { - self.reportErrorAtCurrent(.syntax, "Expected `]`"); - } - - item_type = item_type orelse common_type; - } else { - try self.consume(.RightBracket, "Expected `]`"); - } - - // Either item type was specified with `` or the list is not empty and we could infer it - if (item_type == null) { - self.reportError(.list_item_type, "List item type can't be infered"); - - item_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); - } - - var list_def = ObjList.ListDef.init(self.gc.allocator, item_type.?); - - var resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ .List = list_def }; - - var list_type: *ObjTypeDef = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .List, - .resolved_type = resolved_type, - }, - ); - - var node = try self.gc.allocator.create(ListNode); - node.* = ListNode{ - .items = items.items, - .trailing_comma = trailing_comma, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = list_type; - - return &node.node; - } - - fn map(self: *Self, _: bool) anyerror!*ParseNode { - const start_location = self.parser.previous_token.?; - - var value_type: ?*ObjTypeDef = null; - var key_type: ?*ObjTypeDef = null; - var trailing_comma = false; - - // A map expression can specify its type `{, ...}` - if (try self.match(.Less)) { - key_type = try self.parseTypeDef(null, true); - - try self.consume(.Comma, "Expected `,` after key type"); - - value_type = try self.parseTypeDef(null, true); - - try self.consume(.Greater, "Expected `>` after map type."); - } - - var keys = std.ArrayList(*ParseNode).init(self.gc.allocator); - var values = std.ArrayList(*ParseNode).init(self.gc.allocator); - - if (key_type == null or try self.match(.Comma)) { - var common_key_type: ?*ObjTypeDef = null; - var common_value_type: ?*ObjTypeDef = null; - while (!(try self.match(.RightBrace)) and !(try self.match(.Eof))) { - var key: *ParseNode = try self.expression(false); - try self.consume(.Colon, "Expected `:` after key."); - var value: *ParseNode = try self.expression(false); - - try keys.append(key); - try values.append(value); - - if (key_type == null) { - if (common_key_type == null) { - common_key_type = key.type_def; - } else if (key.type_def) |actual_type_def| { - if (!common_key_type.?.eql(actual_type_def)) { - if (common_key_type.?.def_type == .ObjectInstance and actual_type_def.def_type == .ObjectInstance) { - common_key_type = common_key_type.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.both_conforms(actual_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object) orelse common_key_type; - common_key_type = try common_key_type.?.toInstance(self.gc.allocator, &self.gc.type_registry); - } else { - common_key_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Any }); - } - } - } - } - - if (value_type == null) { - if (common_value_type == null) { - common_value_type = value.type_def; - } else if (value.type_def) |actual_type_def| { - if (!common_value_type.?.eql(actual_type_def)) { - if (common_value_type.?.def_type == .ObjectInstance and actual_type_def.def_type == .ObjectInstance) { - common_value_type = common_value_type.?.resolved_type.?.ObjectInstance.resolved_type.?.Object.both_conforms(actual_type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object) orelse common_value_type; - common_value_type = try common_value_type.?.toInstance(self.gc.allocator, &self.gc.type_registry); - } else { - common_value_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Any }); - } - } - } - } - - if (!self.check(.RightBrace)) { - trailing_comma = true; - try self.consume(.Comma, "Expected `,` after map entry."); - } else { - trailing_comma = false; - } - } - - key_type = key_type orelse common_key_type; - value_type = value_type orelse common_value_type; - } else { - try self.consume(.RightBrace, "Expected `}`"); - } - - if (key_type == null and value_type == null) { - self.reportError(.map_key_type, "Unknown map key and value type"); - - key_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); - value_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); - } - - var map_def = ObjMap.MapDef.init(self.gc.allocator, key_type.?, value_type.?); - - var resolved_type: ObjTypeDef.TypeUnion = ObjTypeDef.TypeUnion{ .Map = map_def }; - - var map_type: *ObjTypeDef = try self.gc.type_registry.getTypeDef( - .{ - .optional = try self.match(.Question), - .def_type = .Map, - .resolved_type = resolved_type, - }, - ); - - var node = try self.gc.allocator.create(MapNode); - node.* = MapNode{ - .keys = keys.items, - .values = values.items, - .trailing_comma = trailing_comma, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - node.node.type_def = map_type; - - return &node.node; - } - - fn fun(self: *Self, _: bool) anyerror!*ParseNode { - return try self.function(null, .Anonymous, null); - } - - fn function(self: *Self, name: ?Token, function_type: FunctionType, this: ?*ObjTypeDef) !*ParseNode { - const start_location = self.parser.previous_token.?; - - var function_node = try self.gc.allocator.create(FunctionNode); - function_node.* = try FunctionNode.init( - self, - function_type, - self.script_name, - if (name) |uname| uname.lexeme else "anonymous", - ); - try self.beginFrame(function_type, function_node, this); - self.beginScope(); - - // The functiont tyepdef is created in several steps, some need already parsed information like return type - // We create the incomplete type now and enrich it. - var function_typedef: ObjTypeDef = .{ - .def_type = .Function, - }; - - var function_def = ObjFunction.FunctionDef{ - .id = ObjFunction.FunctionDef.nextId(), - .script_name = try self.gc.copyString(self.script_name), - .name = if (name) |uname| - try self.gc.copyString(uname.lexeme) - else - try self.gc.copyString("anonymous"), - .return_type = undefined, - .yield_type = undefined, - .parameters = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(self.gc.allocator), - .defaults = std.AutoArrayHashMap(*ObjString, Value).init(self.gc.allocator), - .function_type = function_type, - .generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(self.gc.allocator), - .resolved_generics = null, - }; - - var function_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = function_def }; - - function_typedef.resolved_type = function_resolved_type; - - // We replace it with a self.gc.type_registry.getTypeDef pointer at the end - function_node.node.type_def = &function_typedef; - - // So any reference to a generic in the function's body can be resolved - self.current.?.generics = &function_node.node.type_def.?.resolved_type.?.Function.generic_types; - - // Parse generic & argument list - if (function_type == .Test) { - try self.consume(.String, "Expected a string after `test`."); - function_node.test_message = self.parser.previous_token.?.literal_string; - } else { - // Generics - if (try self.match(.DoubleColon)) { - try self.consume(.Less, "Expected `<` at start of generic types list."); - - var i: usize = 0; - while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { - try self.consume(.Identifier, "Expected generic type identifier"); - - const generic_identifier = try self.gc.copyString(self.parser.previous_token.?.lexeme); - if ((self.current.?.generics == null or self.current.?.generics.?.get(generic_identifier) == null) and (self.current_object == null or self.current_object.?.generics == null or self.current_object.?.generics.?.get(generic_identifier) == null)) { - const generic = ObjTypeDef.GenericDef{ - .origin = function_node.node.type_def.?.resolved_type.?.Function.id, - .index = i, - }; - const resolved_type = ObjTypeDef.TypeUnion{ .Generic = generic }; - try function_node.node.type_def.?.resolved_type.?.Function.generic_types.put( - generic_identifier, - try self.gc.type_registry.getTypeDef( - ObjTypeDef{ - .def_type = .Generic, - .resolved_type = resolved_type, - }, - ), - ); - } else { - self.reportErrorFmt( - .generic_type, - "Generic type `{s}` already defined", - .{self.parser.previous_token.?.lexeme}, - ); - } - - if (!self.check(.Greater)) { - try self.consume(.Comma, "Expected `,` between generic types"); - } - } - - if (function_node.node.type_def.?.resolved_type.?.Function.generic_types.count() == 0) { - self.reportError( - .generic_type, - "Expected at least one generic type", - ); - } - - try self.consume(.Greater, "Expected `>` after generic types list"); - } - - try self.consume(.LeftParen, "Expected `(` after function name."); - - // Arguments - var arity: usize = 0; - if (!self.check(.RightParen)) { - while (true) { - arity += 1; - if (arity > 255) { - self.reportErrorAtCurrent( - .arguments_count, - "Can't have more than 255 arguments.", - ); - } - - var param_type: *ObjTypeDef = try (try self.parseTypeDef(function_node.node.type_def.?.resolved_type.?.Function.generic_types, true)).toInstance(self.gc.allocator, &self.gc.type_registry); - - var slot: usize = try self.parseVariable( - false, - param_type, - true, // function arguments are constant - "Expected parameter name", - ); - var arg_name: *ObjString = undefined; - - if (self.current.?.scope_depth > 0) { - var local: Local = self.current.?.locals[slot]; - arg_name = local.name; - try function_node.node.type_def.?.resolved_type.?.Function.parameters.put(local.name, local.type_def); - } else { - var global: Global = self.globals.items[slot]; - arg_name = global.name; - try function_node.node.type_def.?.resolved_type.?.Function.parameters.put(global.name, global.type_def); - } - - self.markInitialized(); - - // Default arguments - if (function_type == .Function or function_type == .Method or function_type == .Anonymous or function_type == .Extern) { - if (try self.match(.Equal)) { - var expr = try self.expression(false); - - if (expr.type_def != null and expr.type_def.?.def_type == .Placeholder and param_type.def_type == .Placeholder) { - try PlaceholderDef.link(param_type, expr.type_def.?, .Assignment); - } - - if (!expr.isConstant(expr)) { - self.reportError(.constant_default, "Default parameters must be constant values."); - } - - try function_node.node.type_def.?.resolved_type.?.Function.defaults.put( - arg_name, - try expr.toValue(expr, self.gc), - ); - } else if (param_type.optional) { - try function_node.node.type_def.?.resolved_type.?.Function.defaults.put( - arg_name, - Value.Null, - ); - } - } - - if (!try self.match(.Comma)) break; - } - } - - try self.consume(.RightParen, "Expected `)` after function parameters."); - } - - // Parse return type - var parsed_return_type = false; - if (function_type != .Test and try self.match(.Greater)) { - const return_type = try self.parseTypeDef(function_node.node.type_def.?.resolved_type.?.Function.generic_types, true); - - function_node.node.type_def.?.resolved_type.?.Function.return_type = try return_type.toInstance(self.gc.allocator, &self.gc.type_registry); - - parsed_return_type = true; - } - - // Parse yield type - if (parsed_return_type and function_type.canYield() and (try self.match(.Greater))) { - const yield_type = try self.parseTypeDef(function_node.node.type_def.?.resolved_type.?.Function.generic_types, true); - - if (!yield_type.optional and yield_type.def_type != .Void) { - self.reportError(.yield_type, "Expected optional type or void"); - } - - function_node.node.type_def.?.resolved_type.?.Function.yield_type = try yield_type.toInstance(self.gc.allocator, &self.gc.type_registry); - } else { - function_node.node.type_def.?.resolved_type.?.Function.yield_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); - } - - // Error set - if (function_type.canHaveErrorSet() and (try self.match(.BangGreater))) { - var error_types = std.ArrayList(*ObjTypeDef).init(self.gc.allocator); - const end_token: TokenType = if (function_type.canOmitBody()) .Semicolon else .LeftBrace; - while (!self.check(end_token) and !self.check(.Eof)) { - const error_type = try self.parseTypeDef(function_node.node.type_def.?.resolved_type.?.Function.generic_types, true); - try error_types.append(error_type); - - if (error_type.optional) { - self.reportError(.error_type, "Error type can't be optional"); - } - - if (!self.check(end_token)) { - try self.consume(.Comma, "Expected `,` after error type"); - } - } - - if (error_types.items.len > 0) { - function_node.node.type_def.?.resolved_type.?.Function.error_types = error_types.items; - } else { - error_types.deinit(); - } - } - - // Parse body - if (try self.match(.Arrow)) { - function_node.node.type_def.?.resolved_type.?.Function.lambda = true; - function_node.arrow_expr = try self.expression(false); - - if (function_node.body) |placeholder_body| { - self.gc.allocator.destroy(placeholder_body); - } - function_node.body = null; - - if (!parsed_return_type and function_node.arrow_expr.?.type_def != null) { - function_node.node.type_def.?.resolved_type.?.Function.return_type = function_node.arrow_expr.?.type_def.?; - parsed_return_type = true; - } - } else if (!function_type.canOmitBody()) { - if (!parsed_return_type and !function_type.canOmitReturn()) { - self.reportError(.syntax, "Expected `>` after function argument list."); - } - - try self.consume(.LeftBrace, "Expected `{` before function body."); - function_node.body = BlockNode.cast(try self.block(null)).?; - } - - if (!parsed_return_type) { - function_node.node.type_def.?.resolved_type.?.Function.return_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); - } - - if (function_type == .Extern) { - if (self.flavor.resolveImports()) { - // Search for a dylib/so/dll with the same name as the current script - if (try self.importLibSymbol( - self.script_name, - function_node.node.type_def.?.resolved_type.?.Function.name.string, - )) |native| { - function_node.native = native; - } else { - return error.BuzzNoDll; - } - } - } else { - // Bind upvalues - var i: usize = 0; - while (i < self.current.?.upvalue_count) : (i += 1) { - try function_node.upvalue_binding.put( - self.current.?.upvalues[i].index, - if (self.current.?.upvalues[i].is_local) true else false, - ); - } - } - - function_node.node.type_def = try self.gc.type_registry.getTypeDef(function_typedef); - function_node.node.location = start_location; - function_node.node.end_location = self.parser.previous_token.?; - - return &self.endFrame().node; - } - - fn inlineCatch(self: *Self) !?*ParseNode { - if (try self.match(.Catch)) { - return try self.expression(false); - } - - return null; - } - - // `test` is just like a function but we don't parse arguments and we don't care about its return type - fn testStatement(self: *Self) !*ParseNode { - const start_location = self.parser.previous_token.?; - - var function_def_placeholder: ObjTypeDef = .{ - .def_type = .Function, - }; - - var test_id = std.ArrayList(u8).init(self.gc.allocator); - try test_id.writer().print("$test#{} {s}", .{ self.test_count, self.parser.current_token.?.literal_string.? }); - // TODO: this string is never freed - - self.test_count += 1; - - const name_token: Token = Token{ - .token_type = .Test, - .lexeme = test_id.items, - .line = start_location.line, - .column = start_location.column, - .source = start_location.source, - .script_name = start_location.script_name, - }; - - const slot = try self.declareVariable(&function_def_placeholder, name_token, true, true); - - self.markInitialized(); - - const function_node = try self.function(name_token, FunctionType.Test, null); - - if (function_node.type_def) |type_def| { - self.globals.items[slot].type_def = type_def; - } - - // Make it as a global definition - var node = try self.gc.allocator.create(VarDeclarationNode); - node.* = VarDeclarationNode{ - .name = name_token, - .value = function_node, - .type_def = function_node.type_def.?, - .constant = true, - .slot = slot, - .slot_type = .Global, - .expression = false, - }; - node.node.location = start_location; - node.node.end_location = self.parser.previous_token.?; - - return node.toNode(); - } - - fn searchPaths(self: *Self, file_name: []const u8) !std.ArrayList([]const u8) { - var paths = std.ArrayList([]const u8).init(self.gc.allocator); - - for (search_paths) |path| { - const filled = try std.mem.replaceOwned(u8, self.gc.allocator, path, "?", file_name); - defer self.gc.allocator.free(filled); - const suffixed = try std.mem.replaceOwned(u8, self.gc.allocator, filled, "!", "buzz"); - defer self.gc.allocator.free(suffixed); - const prefixed = try std.mem.replaceOwned(u8, self.gc.allocator, suffixed, "$", buzz_lib_path()); - - try paths.append(prefixed); - } - - return paths; - } - - fn searchLibPaths(self: *Self, file_name: []const u8) !std.ArrayList([]const u8) { - var paths = std.ArrayList([]const u8).init(self.gc.allocator); - - for (lib_search_paths) |path| { - const filled = try std.mem.replaceOwned(u8, self.gc.allocator, path, "?", file_name); - defer self.gc.allocator.free(filled); - const suffixed = try std.mem.replaceOwned( - u8, - self.gc.allocator, - filled, - "!", - switch (builtin.os.tag) { - .linux, .freebsd, .openbsd => "so", - .windows => "dll", - .macos, .tvos, .watchos, .ios => "dylib", - else => unreachable, - }, - ); - defer self.gc.allocator.free(suffixed); - const prefixed = try std.mem.replaceOwned(u8, self.gc.allocator, suffixed, "$", buzz_lib_path()); - - try paths.append(prefixed); - } - - for (Parser.user_library_paths orelse &[_][]const u8{}) |path| { - var filled = std.ArrayList(u8).init(self.gc.allocator); - - try filled.writer().print( - "{s}{s}{s}.{s}", - .{ - path, - if (!std.mem.endsWith(u8, path, "/")) "/" else "", - file_name, - switch (builtin.os.tag) { - .linux, .freebsd, .openbsd => "so", - .windows => "dll", - .macos, .tvos, .watchos, .ios => "dylib", - else => unreachable, - }, - }, - ); - - filled.shrinkAndFree(filled.items.len); - - try paths.append(filled.items); - - var prefixed_filled = std.ArrayList(u8).init(self.gc.allocator); - - try prefixed_filled.writer().print( - "{s}{s}lib{s}.{s}", - .{ - path, - if (!std.mem.endsWith(u8, path, "/")) "/" else "", - file_name, - switch (builtin.os.tag) { - .linux, .freebsd, .openbsd => "so", - .windows => "dll", - .macos, .tvos, .watchos, .ios => "dylib", - else => unreachable, - }, - }, - ); - - prefixed_filled.shrinkAndFree(prefixed_filled.items.len); - - try paths.append(prefixed_filled.items); - } - - return paths; - } - - fn searchZdefLibPaths(self: *Self, file_name: []const u8) !std.ArrayList([]const u8) { - var paths = std.ArrayList([]const u8).init(self.gc.allocator); - - for (zdef_search_paths) |path| { - const filled = try std.mem.replaceOwned(u8, self.gc.allocator, path, "?", file_name); - defer self.gc.allocator.free(filled); - const suffixed = try std.mem.replaceOwned( - u8, - self.gc.allocator, - filled, - "!", - switch (builtin.os.tag) { - .linux, .freebsd, .openbsd => "so", - .windows => "dll", - .macos, .tvos, .watchos, .ios => "dylib", - else => unreachable, - }, - ); - try paths.append(suffixed); - } - - for (Parser.user_library_paths orelse &[_][]const u8{}) |path| { - var filled = std.ArrayList(u8).init(self.gc.allocator); - - try filled.writer().print( - "{s}{s}{s}.{s}", - .{ - path, - if (!std.mem.endsWith(u8, path, "/")) "/" else "", - file_name, - switch (builtin.os.tag) { - .linux, .freebsd, .openbsd => "so", - .windows => "dll", - .macos, .tvos, .watchos, .ios => "dylib", - else => unreachable, - }, - }, - ); - - filled.shrinkAndFree(filled.items.len); - - try paths.append(filled.items); - - var prefixed_filled = std.ArrayList(u8).init(self.gc.allocator); - - try prefixed_filled.writer().print( - "{s}{s}lib{s}.{s}", - .{ - path, - if (!std.mem.endsWith(u8, path, "/")) "/" else "", - file_name, - switch (builtin.os.tag) { - .linux, .freebsd, .openbsd => "so", - .windows => "dll", - .macos, .tvos, .watchos, .ios => "dylib", - else => unreachable, - }, - }, - ); - - prefixed_filled.shrinkAndFree(prefixed_filled.items.len); - - try paths.append(prefixed_filled.items); - } - - return paths; - } - - fn importScript( - self: *Self, - file_name: []const u8, - prefix: ?[]const u8, - imported_symbols: *std.StringHashMap(void), - ) anyerror!?ScriptImport { - var import: ?ScriptImport = self.imports.get(file_name); - - if (import == null) { - const paths = try self.searchPaths(file_name); - defer { - for (paths.items) |path| { - self.gc.allocator.free(path); - } - paths.deinit(); - } - - // Find and read file - var file: ?std.fs.File = null; - var absolute_path: ?[]const u8 = null; - var owned = false; - for (paths.items) |path| { - if (std.fs.path.isAbsolute(path)) { - file = std.fs.openFileAbsolute(path, .{}) catch null; - if (file != null) { - absolute_path = path; - break; - } - } else { - file = std.fs.cwd().openFile(path, .{}) catch null; - if (file != null) { - absolute_path = try std.fs.cwd().realpathAlloc(self.gc.allocator, path); - owned = true; - break; - } - } - } - - if (file == null) { - var search_report = std.ArrayList(u8).init(self.gc.allocator); - defer search_report.deinit(); - var writer = search_report.writer(); - - for (paths.items) |path| { - try writer.print(" no file `{s}`\n", .{path}); - } - - self.reportErrorFmt( - .script_not_found, - "buzz script `{s}` not found:\n{s}", - .{ - file_name, - search_report.items, - }, - ); - - return null; - } - - defer file.?.close(); - defer { - if (owned) { - self.gc.allocator.free(absolute_path.?); - } - } - - // TODO: put source strings in a ArenaAllocator that frees everything at the end of everything - const source = try self.gc.allocator.alloc(u8, (try file.?.stat()).size); - // defer self.gc.allocator.free(source); - - _ = try file.?.readAll(source); - - var parser = Parser.init(self.gc, self.imports, true, self.flavor); - defer parser.deinit(); - - if (try parser.parse(source, file_name)) |import_node| { - FunctionNode.cast(import_node).?.import_root = true; - - import = ScriptImport{ - .function = import_node, - .globals = std.ArrayList(Global).init(self.gc.allocator), - .absolute_path = try self.gc.copyString(absolute_path.?), - }; - - for (parser.globals.items) |*global| { - if (global.exported) { - global.*.exported = false; - - if (global.export_alias) |export_alias| { - global.*.name = try self.gc.copyString(export_alias); - global.*.export_alias = null; - } - } else { - global.*.hidden = true; - } - - global.*.prefix = prefix; - - try import.?.globals.append(global.*); - } - - try self.imports.put(file_name, import.?); - } - } - - if (import) |imported| { - const selective_import = imported_symbols.count() > 0; - for (imported.globals.items) |*global| { - if (!global.hidden) { - if (imported_symbols.get(global.name.string) != null) { - _ = imported_symbols.remove(global.name.string); - } else if (selective_import) { - global.hidden = true; - } - - // Search for name collision - if ((try self.resolveGlobal(prefix, Token.identifier(global.name.string))) != null) { - self.reporter.reportWithOrigin( - .shadowed_global, - self.parser.previous_token.?, - global.location, - "Shadowed global `{s}`", - .{global.name.string}, - null, - ); - } - - global.*.prefix = prefix; - } - - // TODO: we're forced to import all and hide some because globals are indexed and not looked up by name at runtime - // Only way to avoid this is to go back to named globals at runtime. Then again, is it worth it? - try self.globals.append(global.*); - } - } else { - // TODO: when it cannot load dynamic library, the error is the same - self.reportErrorFmt(.compile, "Could not compile import or import external dynamic library `{s}`", .{file_name}); - } - - return import; - } - - // TODO: when to close the lib? - fn importLibSymbol(self: *Self, full_file_name: []const u8, symbol: []const u8) !?*ObjNative { - // Remove .buzz extension, this occurs if this is the script being run or if the script was imported like so `import lib/std.buzz` - // We consider that any other extension is silly from the part of the user - const file_name = - if (std.mem.endsWith(u8, full_file_name, ".buzz")) full_file_name[0..(full_file_name.len - 5)] else full_file_name; - - const file_basename = std.fs.path.basename(file_name); - const paths = try self.searchLibPaths(file_basename); - defer { - for (paths.items) |path| { - self.gc.allocator.free(path); - } - paths.deinit(); - } - - var lib: ?std.DynLib = null; - for (paths.items) |path| { - lib = std.DynLib.open(path) catch null; - if (lib != null) { - break; - } - } - - if (lib) |*dlib| { - // Convert symbol names to zig slices - const ssymbol = try self.gc.allocator.dupeZ(u8, symbol); - defer self.gc.allocator.free(ssymbol); - - // Lookup symbol NativeFn - const opaque_symbol_method = dlib.lookup(*anyopaque, ssymbol); - - if (opaque_symbol_method == null) { - self.reportErrorFmt( - .symbol_not_found, - "Could not find symbol `{s}` in lib `{s}`", - .{ - symbol, - file_name, - }, - ); - return null; - } - - // Create a ObjNative with it - return try self.gc.allocateObject( - ObjNative, - .{ - .native = opaque_symbol_method.?, - }, - ); - } - - var search_report = std.ArrayList(u8).init(self.gc.allocator); - defer search_report.deinit(); - var writer = search_report.writer(); - - for (paths.items) |path| { - try writer.print(" no file `{s}`\n", .{path}); - } - - self.reportErrorFmt( - .library_not_found, - "External library `{s}` not found: {s}{s}\n", - .{ - file_basename, - if (builtin.link_libc) - std.mem.sliceTo(dlerror(), 0) - else - "", - search_report.items, - }, - ); - - return null; - } - - fn parseFunctionType(self: *Self, parent_generic_types: ?std.AutoArrayHashMap(*ObjString, *ObjTypeDef)) !*ObjTypeDef { - assert(self.parser.previous_token.?.token_type == .Function or self.parser.previous_token.?.token_type == .Extern); - - const is_extern = self.parser.previous_token.?.token_type == .Extern; - - if (is_extern) { - try self.consume(.Function, "Expected `Function` after `extern`."); - } - - var name: ?*ObjString = null; - if (try self.match(.Identifier)) { - name = try self.gc.copyString(self.parser.previous_token.?.lexeme); - } - - var merged_generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(self.gc.allocator); - defer merged_generic_types.deinit(); - if (parent_generic_types != null) { - var it = parent_generic_types.?.iterator(); - while (it.next()) |kv| { - try merged_generic_types.put(kv.key_ptr.*, kv.value_ptr.*); - } - } - - var generic_types = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(self.gc.allocator); - if (try self.match(.DoubleColon)) { - try self.consume(.Less, "Expected `<` at start of generic types list."); - - var i: usize = 0; - while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { - try self.consume(.Identifier, "Expected generic type identifier"); - - const generic_identifier = try self.gc.copyString(self.parser.previous_token.?.lexeme); - if (generic_types.get(generic_identifier) == null) { - const generic = ObjTypeDef.GenericDef{ - .origin = undefined, - .index = i, - }; - const resolved = ObjTypeDef.TypeUnion{ .Generic = generic }; - const type_def = try self.gc.type_registry.getTypeDef( - ObjTypeDef{ - .def_type = .Generic, - .resolved_type = resolved, - }, - ); - - try generic_types.put( - generic_identifier, - type_def, - ); - try merged_generic_types.put( - generic_identifier, - type_def, - ); - } else { - self.reportErrorFmt( - .generic_type, - "Generic type `{s}` already defined", - .{self.parser.previous_token.?.lexeme}, - ); - } - - if (!self.check(.Greater)) { - try self.consume(.Comma, "Expected `,` between generic types"); - } - } - - if (generic_types.count() == 0) { - self.reportError(.generic_type, "Expected at least one generic type"); - } - - try self.consume(.Greater, "Expected `>` after generic types list"); - } - - try self.consume(.LeftParen, "Expected `(` after function name."); - - var parameters = std.AutoArrayHashMap(*ObjString, *ObjTypeDef).init(self.gc.allocator); - var defaults = std.AutoArrayHashMap(*ObjString, Value).init(self.gc.allocator); - var arity: usize = 0; - if (!self.check(.RightParen)) { - while (true) { - arity += 1; - if (arity > 255) { - self.reportErrorAtCurrent(.arguments_count, "Can't have more than 255 arguments."); - } - - var param_type: *ObjTypeDef = try self.parseTypeDef(merged_generic_types, true); - try self.consume(.Identifier, "Expected argument name"); - var param_name: []const u8 = self.parser.previous_token.?.lexeme; - var arg_name = try self.gc.copyString(param_name); - - try parameters.put(arg_name, param_type); - - if (try self.match(.Equal)) { - var expr = try self.expression(false); - - if (expr.type_def != null and expr.type_def.?.def_type == .Placeholder and param_type.def_type == .Placeholder) { - try PlaceholderDef.link(param_type, expr.type_def.?, .Assignment); - } - - if (!expr.isConstant(expr)) { - self.reportError( - .constant_default, - "Default parameters must be constant values.", - ); - } - - try defaults.put(arg_name, try expr.toValue(expr, self.gc)); - } else if (param_type.optional) { - try defaults.put(arg_name, Value.Null); - } - - if (!try self.match(.Comma)) break; - } - } - - try self.consume(.RightParen, "Expected `)` after function parameters."); - - var return_type: *ObjTypeDef = if (try self.match(.Greater)) - try self.parseTypeDef(null, true) - else - try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); - - var yield_type: *ObjTypeDef = if (try self.match(.Greater)) - try self.parseTypeDef(null, true) - else - try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }); - - var error_types: ?std.ArrayList(*ObjTypeDef) = null; - if (try self.match(.BangGreater)) { - error_types = std.ArrayList(*ObjTypeDef).init(self.gc.allocator); - while (!self.check(.Eof)) { - const error_type = try self.parseTypeDef(generic_types, true); - try error_types.?.append(error_type); - - if (error_type.optional) { - self.reportError(.error_type, "Error type can't be optional"); - } - - if (!self.check(.Comma)) { - break; - } - } - } - - var function_typedef: ObjTypeDef = .{ - .def_type = .Function, - .optional = try self.match(.Question), - }; - - var function_def: ObjFunction.FunctionDef = .{ - .id = ObjFunction.FunctionDef.nextId(), - .script_name = try self.gc.copyString(self.script_name), - .name = name orelse try self.gc.copyString("anonymous"), - .return_type = try return_type.toInstance(self.gc.allocator, &self.gc.type_registry), - .yield_type = try yield_type.toInstance(self.gc.allocator, &self.gc.type_registry), - .parameters = parameters, - .defaults = defaults, - .function_type = if (is_extern) .Extern else .Anonymous, - .generic_types = generic_types, - .error_types = if (error_types != null) error_types.?.items else null, - }; - - var function_resolved_type: ObjTypeDef.TypeUnion = .{ .Function = function_def }; - - function_typedef.resolved_type = function_resolved_type; - - return try self.gc.type_registry.getTypeDef(function_typedef); - } - - fn parseUserType(self: *Self, instance: bool) !*ObjTypeDef { - var user_type_name: Token = self.parser.previous_token.?.clone(); - var var_type: ?*ObjTypeDef = null; - var global_slot: ?usize = null; - - // Search for a global with that name - if (try self.resolveGlobal(null, user_type_name)) |slot| { - var_type = self.globals.items[slot].type_def; - global_slot = slot; - } - - // If none found, create a placeholder - if (var_type == null) { - var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, user_type_name), - }; - - placeholder_resolved_type.Placeholder.name = try self.gc.copyString(user_type_name.lexeme); - - var_type = try self.gc.type_registry.getTypeDef( - .{ - .def_type = .Placeholder, - .resolved_type = placeholder_resolved_type, - }, - ); - - global_slot = try self.declarePlaceholder(user_type_name, var_type.?); - } - - // Concrete generic types list - if (try self.match(.DoubleColon)) { - try self.consume(.Less, "Expected generic types list after `::`"); - - var resolved_generics = std.ArrayList(*ObjTypeDef).init(self.gc.allocator); - var i: usize = 0; - while (!self.check(.Greater) and !self.check(.Eof)) : (i += 1) { - try resolved_generics.append( - try self.parseTypeDef( - if (self.current.?.generics) |generics| - generics.* - else - null, - true, - ), - ); - - if (!self.check(.Greater)) { - try self.consume(.Comma, "Expected `,` between generic types"); - } - } - - try self.consume(.Greater, "Expected `>` after generic types list"); - - resolved_generics.shrinkAndFree(resolved_generics.items.len); - - if (resolved_generics.items.len == 0) { - self.reportErrorAtCurrent(.generic_type, "Expected at least one type"); - } - - var_type = try var_type.?.populateGenerics( - self.parser.previous_token.?, - var_type.?.resolved_type.?.Object.id, - resolved_generics.items, - &self.gc.type_registry, - null, - ); - } - - return if (instance) - var_type.?.toInstance(self.gc.allocator, &self.gc.type_registry) - else - var_type.?; - } - - pub fn parseTypeDefFrom(self: *Self, source: []const u8) anyerror!*ObjTypeDef { - var type_scanner = Scanner.init(self.gc.allocator, self.script_name, source); - // Replace parser scanner with one that only looks at that substring - const scanner = self.scanner; - self.scanner = type_scanner; - const parser = self.parser; - self.parser = ParserState.init(self.gc.allocator); - - _ = try self.advance(); - - const parsed_type = try self.parseTypeDef(null, true); - - // Restore normal scanner and parser state - self.scanner = scanner; - self.parser.deinit(); - self.parser = parser; - - return parsed_type; - } - - fn parseTypeDef(self: *Self, generic_types: ?std.AutoArrayHashMap(*ObjString, *ObjTypeDef), instance: bool) anyerror!*ObjTypeDef { - if (try self.match(.Str)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .String }); - } else if (try self.match(.Pat)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Pattern }); - } else if (try self.match(.Ud)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .UserData }); - } else if (try self.match(.Type)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Type }); - } else if (try self.match(.Void)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = false, .def_type = .Void }); - } else if (try self.match(.Int)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Integer }); - } else if (try self.match(.Float)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Float }); - } else if (try self.match(.Bool)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Bool }); - } else if (try self.match(.Any)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Any }); - } else if (try self.match(.Type)) { - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Type }); - } else if (try self.match(.LeftBracket)) { - return self.parseListType(generic_types); - } else if (try self.match(.LeftBrace)) { - return self.parseMapType(generic_types); - } else if (try self.match(.Function) or try self.match(.Extern)) { - return try self.parseFunctionType(generic_types); - } else if (try self.match(.Fib)) { - return try self.parseFiberType(generic_types); - } else if (try self.match(.Obj)) { - return if (instance) - try (try self.parseObjType(generic_types)).toInstance(self.gc.allocator, &self.gc.type_registry) - else - try self.parseObjType(generic_types); - } else if ((try self.match(.Identifier))) { - var user_type: ?*ObjTypeDef = null; - // Is it a generic type defined in enclosing functions or object? - if (self.resolveGeneric(try self.gc.copyString(self.parser.previous_token.?.lexeme))) |generic_type| { - user_type = generic_type; - } else if (generic_types != null) { - // Is it generic type defined in a function signature being parsed? - if (generic_types.?.get(try self.gc.copyString(self.parser.previous_token.?.lexeme))) |generic_type| { - user_type = generic_type; - } - } - - // Is it a user defined type (object, enum, etc.) defined in global scope? - if (user_type == null) { - user_type = try self.parseUserType(instance); - } - - if (try self.match(.Question)) { - return try user_type.?.cloneOptional(&self.gc.type_registry); - } - - return user_type.?; - } else { - self.reportErrorAtCurrent(.syntax, "Expected type definition."); - - return try self.gc.type_registry.getTypeDef(.{ .optional = try self.match(.Question), .def_type = .Void }); - } - } - - // Only used to parse anonymouse object type - fn parseObjType(self: *Self, generic_types: ?std.AutoArrayHashMap(*ObjString, *ObjTypeDef)) !*ObjTypeDef { - const start_location = self.parser.previous_token.?; - - try self.consume(.LeftBrace, "Expected `{` after `obj`"); - - const qualifier = try std.mem.replaceOwned(u8, self.gc.allocator, self.script_name, "/", "."); - defer self.gc.allocator.free(qualifier); - var qualified_name = std.ArrayList(u8).init(self.gc.allocator); - defer qualified_name.deinit(); - try qualified_name.writer().print("{s}.anonymous", .{qualifier}); - - var object_def = ObjObject.ObjectDef.init( - self.gc.allocator, - start_location, - try self.gc.copyString("anonymous"), - try self.gc.copyString(qualified_name.items), - true, - ); - - var resolved_type = ObjTypeDef.TypeUnion{ .Object = object_def }; - - var object_type: ObjTypeDef = .{ - .def_type = .Object, - .resolved_type = resolved_type, - }; - - // Anonymous object can only have properties without default values (no methods, no static fields) - // They can't self reference since their anonymous - var fields = std.StringHashMap(void).init(self.gc.allocator); - defer fields.deinit(); - while (!self.check(.RightBrace) and !self.check(.Eof)) { - const property_type = try self.parseTypeDef(generic_types, true); - - try self.consume(.Identifier, "Expected property name."); - const property_name = self.parser.previous_token.?.clone(); - - if (fields.get(property_name.lexeme) != null) { - self.reportError(.property_already_exists, "A property with that name already exists."); - } - - if (!self.check(.RightBrace) or self.check(.Comma)) { - try self.consume(.Comma, "Expected `,` after property definition."); - } - try object_type.resolved_type.?.Object.fields.put(property_name.lexeme, property_type); - try fields.put(property_name.lexeme, {}); - } - - try self.consume(.RightBrace, "Expected `}` after object body."); - - return try self.gc.type_registry.getTypeDef(object_type); - } - - fn parseVariable(self: *Self, identifier_consumed: bool, variable_type: *ObjTypeDef, constant: bool, error_message: []const u8) !usize { - if (!identifier_consumed) { - try self.consume(.Identifier, error_message); - } - - return try self.declareVariable(variable_type, null, constant, true); - } - - inline fn markInitialized(self: *Self) void { - if (self.current.?.scope_depth == 0) { - // assert(!self.globals.items[self.globals.items.len - 1].initialized); - self.globals.items[self.globals.items.len - 1].initialized = true; - } else { - self.current.?.locals[self.current.?.local_count - 1].depth = @as(i32, @intCast(self.current.?.scope_depth)); - } - } - - fn declarePlaceholder(self: *Self, name: Token, placeholder: ?*ObjTypeDef) !usize { - var placeholder_type: *ObjTypeDef = undefined; - - if (placeholder) |uplaceholder| { - placeholder_type = uplaceholder; - } else { - var placeholder_resolved_type: ObjTypeDef.TypeUnion = .{ - .Placeholder = PlaceholderDef.init(self.gc.allocator, name), - }; - placeholder_resolved_type.Placeholder.name = try self.gc.copyString(name.lexeme); - - placeholder_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Placeholder, .resolved_type = placeholder_resolved_type }); - } - - assert(!placeholder_type.optional); - - const global: usize = try self.addGlobal( - name, - placeholder_type, - false, - ); - // markInitialized but we don't care what depth we are in - self.globals.items[global].initialized = true; - - if (BuildOptions.debug_placeholders) { - std.debug.print( - "global placeholder @{} for `{s}` at {}\n", - .{ - @intFromPtr(placeholder_type), - name.lexeme, - global, - }, - ); - } - - return global; - } - - fn declareVariable(self: *Self, variable_type: *ObjTypeDef, name_token: ?Token, constant: bool, check_name: bool) !usize { - var name: Token = name_token orelse self.parser.previous_token.?; - - if (self.current.?.scope_depth > 0) { - // Check a local with the same name doesn't exists - if (self.current.?.local_count > 0) { - var i: usize = self.current.?.local_count - 1; - while (check_name and i >= 0) : (i -= 1) { - var local: *Local = &self.current.?.locals[i]; - - if (local.depth != -1 and local.depth < self.current.?.scope_depth) { - break; - } - - if (!mem.eql(u8, name.lexeme, "_") and mem.eql(u8, name.lexeme, local.name.string)) { - self.reporter.reportWithOrigin( - .variable_already_exists, - name, - local.location, - "A variable named `{s}` already exists", - .{name.lexeme}, - null, - ); - } - - if (i == 0) break; - } - } - - return try self.addLocal(name, variable_type, constant); - } else { - if (check_name) { - // Check a global with the same name doesn't exists - for (self.globals.items, 0..) |*global, index| { - if (!mem.eql(u8, name.lexeme, "_") and mem.eql(u8, name.lexeme, global.name.string) and !global.hidden) { - // If we found a placeholder with that name, try to resolve it with `variable_type` - if (global.type_def.def_type == .Placeholder and global.type_def.resolved_type.?.Placeholder.name != null and mem.eql(u8, name.lexeme, global.type_def.resolved_type.?.Placeholder.name.?.string)) { - // A function declares a global with an incomplete typedef so that it can handle recursion - // The placeholder resolution occurs after we parsed the functions body in `funDeclaration` - if (variable_type.resolved_type != null or @intFromEnum(variable_type.def_type) < @intFromEnum(ObjTypeDef.Type.ObjectInstance)) { - if (BuildOptions.debug_placeholders) { - std.debug.print( - "Global placeholder @{} resolve with @{} {s} (opt {})\n", - .{ - @intFromPtr(global.type_def), - @intFromPtr(variable_type), - (try variable_type.toStringAlloc(self.gc.allocator)).items, - variable_type.optional, - }, - ); - } - - try self.resolvePlaceholder(global.type_def, variable_type, constant); - } - - global.referenced = true; - - return index; - } else if (global.prefix == null) { - self.reportError(.variable_already_exists, "A global with the same name already exists."); - } - } - } - } - - return try self.addGlobal(name, variable_type, constant); - } - } - - fn addLocal(self: *Self, name: Token, local_type: *ObjTypeDef, constant: bool) !usize { - if (self.current.?.local_count == 255) { - self.reportError(.locals_count, "Too many local variables in scope."); - return 0; - } - - const function_type = self.current.?.function_node.node.type_def.?.resolved_type.?.Function.function_type; - self.current.?.locals[self.current.?.local_count] = Local{ - .name = try self.gc.copyString(name.lexeme), - .location = name, - .depth = -1, - .is_captured = false, - .type_def = local_type, - .constant = constant, - // Extern and abstract function arguments are considered referenced - .referenced = function_type == .Extern or function_type == .Abstract, - }; - - self.current.?.local_count += 1; - - return self.current.?.local_count - 1; - } - - fn dumpGlobals(self: *Self) !void { - if (BuildOptions.debug) { - for (self.globals.items, 0..) |global, index| { - std.debug.print( - "global {}: {s} @{} {s}\n", - .{ - index, - global.name.string, - @intFromPtr(global.type_def), - try global.type_def.toString(self.gc.allocator), - }, - ); - } - } - } - - fn addGlobal(self: *Self, name: Token, global_type: *ObjTypeDef, constant: bool) !usize { - // Search for an existing placeholder global with the same name - for (self.globals.items, 0..) |*global, index| { - if (global.type_def.def_type == .Placeholder and global.type_def.resolved_type.?.Placeholder.name != null and mem.eql(u8, name.lexeme, global.name.string)) { - global.exported = self.exporting; - - if (global_type.def_type != .Placeholder) { - try self.resolvePlaceholder(global.type_def, global_type, constant); - } - - return index; - } - } - - if (self.globals.items.len == std.math.maxInt(u24)) { - self.reportError(.globals_count, "Too many global variables."); - return 0; - } - - try self.globals.append( - Global{ - .name = try self.gc.copyString(name.lexeme), - .location = name, - .type_def = global_type, - .constant = constant, - .exported = self.exporting, - }, - ); - - return self.globals.items.len - 1; - } - - fn resolveGeneric(self: *Self, name: *ObjString) ?*ObjTypeDef { - return if (self.current_object != null and self.current_object.?.generics != null) - self.current_object.?.generics.?.get(name) orelse self.current.?.resolveGeneric(name) - else - self.current.?.resolveGeneric(name); - } - - fn resolveLocal(self: *Self, frame: *Frame, name: Token) !?usize { - if (frame.local_count == 0) { - return null; - } - - if (std.mem.eql(u8, name.lexeme, "_")) { - return null; - } - - var i: usize = frame.local_count - 1; - while (i >= 0) : (i -= 1) { - var local: *Local = &frame.locals[i]; - if (mem.eql(u8, name.lexeme, local.name.string)) { - if (local.depth == -1) { - self.reportError(.local_initializer, "Can't read local variable in its own initializer."); - } - - local.referenced = true; - return i; - } - - if (i == 0) break; - } - - return null; - } - - // Will consume tokens if find a prefixed identifier - pub fn resolveGlobal(self: *Self, prefix: ?[]const u8, name: Token) anyerror!?usize { - if (self.globals.items.len == 0) { - return null; - } - - if (std.mem.eql(u8, name.lexeme, "_")) { - return null; - } - - var i: usize = self.globals.items.len - 1; - while (i >= 0) : (i -= 1) { - var global: *Global = &self.globals.items[i]; - if (((prefix == null and global.prefix == null) or (prefix != null and global.prefix != null and mem.eql(u8, prefix.?, global.prefix.?))) and mem.eql(u8, name.lexeme, global.name.string) and !global.hidden) { - if (!global.initialized) { - self.reportErrorFmt( - .global_initializer, - "Can't read global `{s}` variable in its own initializer.", - .{global.name.string}, - ); - } - - global.referenced = true; - - return i; - // Is it an import prefix? - } else if (global.prefix != null and mem.eql(u8, name.lexeme, global.prefix.?)) { - try self.consume(.Dot, "Expected `.` after import prefix."); - try self.consume(.Identifier, "Expected identifier after import prefix."); - return try self.resolveGlobal(global.prefix.?, self.parser.previous_token.?); - } - - if (i == 0) break; - } - - return null; - } - - fn addUpvalue(self: *Self, frame: *Frame, index: usize, is_local: bool) !usize { - var upvalue_count: u8 = frame.upvalue_count; - - var i: usize = 0; - while (i < upvalue_count) : (i += 1) { - var upvalue: *UpValue = &frame.upvalues[i]; - if (upvalue.index == index and upvalue.is_local == is_local) { - return i; - } - } - - if (upvalue_count == 255) { - self.reportError(.closures_count, "Too many closure variables in function."); - return 0; - } - - frame.upvalues[upvalue_count].is_local = is_local; - frame.upvalues[upvalue_count].index = @as(u8, @intCast(index)); - frame.upvalue_count += 1; - - return frame.upvalue_count - 1; - } - - fn resolveUpvalue(self: *Self, frame: *Frame, name: Token) anyerror!?usize { - if (frame.enclosing == null) { - return null; - } - - var local: ?usize = try self.resolveLocal(frame.enclosing.?, name); - if (local) |resolved| { - frame.enclosing.?.locals[resolved].is_captured = true; - return try self.addUpvalue(frame, resolved, true); - } - - var upvalue: ?usize = try self.resolveUpvalue(frame.enclosing.?, name); - if (upvalue) |resolved| { - return try self.addUpvalue(frame, resolved, false); - } - - return null; - } - - inline fn reportErrorAtCurrent(self: *Self, error_type: Reporter.Error, message: []const u8) void { - self.reporter.reportErrorAt(error_type, self.parser.current_token.?, message); - } - - pub inline fn reportError(self: *Self, error_type: Reporter.Error, message: []const u8) void { - self.reporter.reportErrorAt(error_type, self.parser.previous_token.?, message); - } - - inline fn reportErrorFmt(self: *Self, error_type: Reporter.Error, comptime fmt: []const u8, args: anytype) void { - self.reporter.reportErrorFmt(error_type, self.parser.previous_token.?, fmt, args); - } -}; diff --git a/src/repl.zig b/src/repl.zig index 1cd28c43..db0ef34b 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -27,17 +27,13 @@ const ObjNative = _obj.ObjNative; const ObjBoundMethod = _obj.ObjBoundMethod; const ObjFiber = _obj.ObjFiber; const ObjForeignContainer = _obj.ObjForeignContainer; -const _parser = @import("parser.zig"); -const Parser = _parser.Parser; -const CompileError = _parser.CompileError; -const MIRJIT = @import("mirjit.zig"); +const Parser = @import("Parser.zig"); +const CompileError = Parser.CompileError; +const JIT = @import("Jit.zig"); const ln = @import("linenoise.zig"); -const _value = @import("value.zig"); -const Value = _value.Value; -const valueToStringAlloc = _value.valueToStringAlloc; +const Value = @import("value.zig").Value; const dumpStack = @import("disassembler.zig").dumpStack; -const CodeGen = @import("codegen.zig").CodeGen; -const FunctionNode = @import("node.zig").FunctionNode; +const CodeGen = @import("Codegen.zig"); const Scanner = @import("scanner.zig").Scanner; pub fn printBanner(out: std.fs.File.Writer, full: bool) void { @@ -87,14 +83,14 @@ pub fn repl(allocator: std.mem.Allocator) !void { }; var imports = std.StringHashMap(Parser.ScriptImport).init(allocator); var vm = try VM.init(&gc, &import_registry, .Repl); - vm.mir_jit = if (BuildOptions.jit) - MIRJIT.init(&vm) + vm.jit = if (BuildOptions.jit) + JIT.init(&vm) else null; defer { - if (vm.mir_jit != null) { - vm.mir_jit.?.deinit(); - vm.mir_jit = null; + if (vm.jit != null) { + vm.jit.?.deinit(); + vm.jit = null; } } var parser = Parser.init( @@ -107,7 +103,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { &gc, &parser, .Repl, - if (vm.mir_jit) |*jit| jit else null, + if (vm.jit) |*jit| jit else null, ); defer { codegen.deinit(); @@ -252,23 +248,25 @@ fn runSource( var codegen_time: u64 = undefined; var running_time: u64 = undefined; - if (try parser.parse(source, file_name)) |function_node| { + if (try parser.parse(source, file_name)) |ast| { parsing_time = timer.read(); timer.reset(); - if (try codegen.generate(FunctionNode.cast(function_node).?)) |function| { + if (try codegen.generate(ast)) |function| { codegen_time = timer.read(); timer.reset(); try vm.interpret( + ast, function, null, ); // Does the user code ends with a lone expression? - const fnode = FunctionNode.cast(function_node).?; - const last_statement = fnode.body.?.statements.getLastOrNull(); - if (last_statement != null and last_statement.?.node_type == .Expression) { + const fnode = ast.nodes.items(.components)[ast.root.?].Function; + const statements = ast.nodes.items(.components)[fnode.body.?].Block; + const last_statement = if (statements.len > 0) statements[statements.len - 1] else null; + if (last_statement != null and ast.nodes.items(.tag)[last_statement.?] == .Expression) { return vm.pop(); } @@ -282,7 +280,7 @@ fn runSource( const codegen_ms: f64 = @as(f64, @floatFromInt(codegen_time)) / 1000000; const running_ms: f64 = @as(f64, @floatFromInt(running_time)) / 1000000; const gc_ms: f64 = @as(f64, @floatFromInt(gc.gc_time)) / 1000000; - const jit_ms: f64 = if (vm.mir_jit) |jit| + const jit_ms: f64 = if (vm.jit) |jit| @as(f64, @floatFromInt(jit.jit_time)) / 1000000 else 0; @@ -344,7 +342,7 @@ const DumpState = struct { if (value.isNull()) { out.print("null", .{}) catch unreachable; } else if (!value.isObj() or state.seen.get(value.obj()) != null) { - const string = valueToStringAlloc(state.vm.gc.allocator, value) catch std.ArrayList(u8).init(state.vm.gc.allocator); + const string = value.toStringAlloc(state.vm.gc.allocator) catch std.ArrayList(u8).init(state.vm.gc.allocator); defer string.deinit(); out.print("{s}", .{string.items}) catch unreachable; @@ -361,7 +359,7 @@ const DumpState = struct { .Fiber, .EnumInstance, => { - const string = valueToStringAlloc(state.vm.gc.allocator, value) catch std.ArrayList(u8).init(state.vm.gc.allocator); + const string = value.toStringAlloc(state.vm.gc.allocator) catch std.ArrayList(u8).init(state.vm.gc.allocator); defer string.deinit(); out.print("{s}", .{string.items}) catch unreachable; @@ -468,7 +466,7 @@ const DumpState = struct { for (enum_type_def.cases.items, 0..) |case, i| { out.print(" {s} -> ", .{case}) catch unreachable; state.valueDump( - enumeration.cases.items[i], + enumeration.cases[i], out, true, ); diff --git a/src/scanner.zig b/src/scanner.zig index 1db9bd04..044d84b5 100644 --- a/src/scanner.zig +++ b/src/scanner.zig @@ -1,9 +1,7 @@ const std = @import("std"); const mem = std.mem; const Allocator = mem.Allocator; -const tk = @import("token.zig"); -const Token = tk.Token; -const TokenType = tk.TokenType; +const Token = @import("Token.zig"); pub const SourceLocation = struct { start: usize, @@ -53,7 +51,7 @@ pub const Scanner = struct { return self.makeToken(.Eof, null, null, null); } - var char: u8 = self.advance(); + const char: u8 = self.advance(); return try switch (char) { 'b' => self.identifier(), 'a', 'c'...'z', 'A'...'Z' => self.identifier(), @@ -133,7 +131,7 @@ pub const Scanner = struct { fn skipWhitespaces(self: *Self) void { while (true) { - var char: u8 = self.peek(); + const char: u8 = self.peek(); switch (char) { ' ', '\r', '\t' => _ = self.advance(), @@ -181,7 +179,7 @@ pub const Scanner = struct { while (!self.isEOF()) { while (!self.isEOF()) { - var char: u8 = self.peek(); + const char: u8 = self.peek(); if (char == '\n') { self.current.line += 1; @@ -223,7 +221,7 @@ pub const Scanner = struct { self.token_index += 1; return .{ - .token_type = .Identifier, + .tag = .Identifier, .lexeme = string_token.literal_string.?, .literal_string = string_token.literal_string.?, .literal_float = null, @@ -242,11 +240,11 @@ pub const Scanner = struct { } const literal = self.source[self.current.start..self.current.offset]; - const keywordOpt = tk.keywords.get(literal); + const keywordOpt = Token.keywords.get(literal); if (keywordOpt) |keyword| { if (keyword == .As and self.match('?')) { - return self.makeToken(.AsBang, null, null, null); + return self.makeToken(.AsQuestion, null, null, null); } return self.makeToken(keyword, literal, null, null); @@ -546,10 +544,10 @@ pub const Scanner = struct { return true; } - fn makeToken(self: *Self, token_type: TokenType, literal_string: ?[]const u8, literal_float: ?f64, literal_integer: ?i32) Token { + fn makeToken(self: *Self, tag: Token.Type, literal_string: ?[]const u8, literal_float: ?f64, literal_integer: ?i32) Token { self.token_index += 1; return Token{ - .token_type = token_type, + .tag = tag, .lexeme = self.source[self.current.start..self.current.offset], .literal_string = literal_string, .literal_float = literal_float, @@ -565,7 +563,7 @@ pub const Scanner = struct { pub fn highlight(self: *Self, out: anytype, true_color: bool) void { var previous_offset: usize = 0; var token = self.scanToken() catch unreachable; - while (token.token_type != .Eof and token.token_type != .Error) { + while (token.tag != .Eof and token.tag != .Error) { // If there some whitespace or comments between tokens? // In gray because either whitespace or comment if (token.offset > previous_offset) { @@ -594,7 +592,7 @@ pub const Scanner = struct { out.print( "{s}{s}{s}", .{ - switch (token.token_type) { + switch (token.tag) { // Operators .Pipe, .Greater, @@ -669,7 +667,7 @@ pub const Scanner = struct { .TypeOf, .Var, .Question, - .AsBang, + .AsQuestion, => if (true_color) Color.keyword else "\x1b[94m", // Punctuation .LeftBracket, diff --git a/src/string_parser.zig b/src/string_parser.zig index 88150f84..58df846c 100644 --- a/src/string_parser.zig +++ b/src/string_parser.zig @@ -1,18 +1,10 @@ const std = @import("std"); -const _parser = @import("parser.zig"); -const _node = @import("node.zig"); -const Parser = _parser.Parser; -const ParseNode = _node.ParseNode; -const StringNode = _node.StringNode; -const StringLiteralNode = _node.StringLiteralNode; -const ParserState = _parser.ParserState; +const Parser = @import("Parser.zig"); const Scanner = @import("scanner.zig").Scanner; -const _obj = @import("obj.zig"); -const _value = @import("value.zig"); -const Token = @import("token.zig").Token; - -const Value = _value.Value; -const ObjTypeDef = _obj.ObjTypeDef; +const obj = @import("obj.zig"); +const Value = @import("value.zig").Value; +const Token = @import("Token.zig"); +const Ast = @import("Ast.zig"); pub const StringParser = struct { const Self = @This(); @@ -29,16 +21,22 @@ pub const StringParser = struct { offset: usize = 0, previous_interp: ?usize = null, chunk_count: usize = 0, - elements: std.ArrayList(*ParseNode), + elements: std.ArrayList(Ast.Node.Index), line_offset: usize, column_offset: usize, - pub fn init(parser: *Parser, source: []const u8, script_name: []const u8, line_offset: usize, column_offset: usize) Self { + pub fn init( + parser: *Parser, + source: []const u8, + script_name: []const u8, + line_offset: usize, + column_offset: usize, + ) Self { return Self{ .parser = parser, .source = source, .current_chunk = std.ArrayList(u8).init(parser.gc.allocator), - .elements = std.ArrayList(*ParseNode).init(parser.gc.allocator), + .elements = std.ArrayList(Ast.Node.Index).init(parser.gc.allocator), .line_offset = line_offset, .column_offset = column_offset, .script_name = script_name, @@ -63,7 +61,7 @@ pub const StringParser = struct { return self.current; } - pub fn parse(self: *Self) !*StringNode { + pub fn parse(self: *Self) !Ast.Node.Index { while (self.offset < self.source.len) { const char: ?u8 = self.advance(); if (char == null) { @@ -99,22 +97,43 @@ pub const StringParser = struct { self.current_chunk = std.ArrayList(u8).init(self.parser.gc.allocator); } - var node = try self.parser.gc.allocator.create(StringNode); - node.* = .{ .elements = self.elements.items }; - node.node.type_def = try self.parser.gc.type_registry.getTypeDef(.{ .def_type = .String }); - - return node; + self.elements.shrinkAndFree(self.elements.items.len); + + return try self.parser.ast.appendNode( + .{ + .tag = .String, + .location = self.parser.ast.nodes.items(.location)[self.elements.items[0]], + .end_location = self.parser.ast.nodes.items(.location)[self.elements.getLast()], + .type_def = try self.parser.gc.type_registry.getTypeDef( + .{ + .def_type = .String, + }, + ), + .components = .{ + .String = self.elements.items, + }, + }, + ); } fn push(self: *Self, chars: []const u8) !void { - var node = try self.parser.gc.allocator.create(StringLiteralNode); - node.* = .{ - .constant = try self.parser.gc.copyString(chars), - }; - node.node.type_def = try self.parser.gc.type_registry.getTypeDef(.{ .def_type = .String }); - node.node.location = self.parser.parser.previous_token.?; - - try self.elements.append(node.toNode()); + try self.elements.append( + try self.parser.ast.appendNode( + .{ + .tag = .StringLiteral, + .location = self.parser.current_token.? - 1, + .end_location = self.parser.current_token.? - 1, + .type_def = try self.parser.gc.type_registry.getTypeDef( + .{ + .def_type = .String, + }, + ), + .components = .{ + .StringLiteral = try self.parser.gc.copyString(chars), + }, + }, + ), + ); } fn inc(self: *Self) !void { @@ -122,38 +141,30 @@ pub const StringParser = struct { } fn interpolation(self: *Self) !void { - var expr: []const u8 = self.source[self.offset..]; + const expr = self.source[self.offset..]; - var expr_scanner = Scanner.init(self.parser.gc.allocator, self.parser.script_name, expr); + var expr_scanner = Scanner.init( + self.parser.gc.allocator, + self.parser.script_name, + expr, + ); expr_scanner.line_offset = self.line_offset; expr_scanner.column_offset = self.column_offset; // Replace parser scanner with one that only looks at that substring - var scanner = self.parser.scanner; + const scanner = self.parser.scanner; self.parser.scanner = expr_scanner; - var parser = self.parser.parser; - self.parser.parser = ParserState.init(self.parser.gc.allocator); try self.parser.advance(); // Parse expression try self.elements.append(try self.parser.expression(false)); - const current: Token = self.parser.parser.current_token.?; // } - var delta: usize = self.parser.scanner.?.current.offset; - - if (self.parser.parser.ahead.items.len > 0) { - const next = self.parser.parser.ahead.items[self.parser.parser.ahead.items.len - 1]; - delta = delta - next.lexeme.len - next.offset + current.offset; - } - - self.offset += delta - 1; + self.offset += self.parser.scanner.?.current.offset - 1; self.previous_interp = self.offset; // Put back parser's scanner self.parser.scanner = scanner; - self.parser.parser.deinit(); - self.parser.parser = parser; // Consume closing `}` _ = self.advance(); diff --git a/src/value.zig b/src/value.zig index ede2bb82..1f1074ab 100644 --- a/src/value.zig +++ b/src/value.zig @@ -9,39 +9,40 @@ const objToString = _obj.objToString; const copyObj = _obj.copyObj; const ObjTypeDef = _obj.ObjTypeDef; -// TODO: do it for little endian? - +pub const Float = f64; +pub const Integer = i32; const Tag = u3; -pub const TagBoolean: Tag = 0; -pub const TagInteger: Tag = 1; -pub const TagNull: Tag = 2; -pub const TagVoid: Tag = 3; -pub const TagObj: Tag = 4; -pub const TagError: Tag = 5; -/// Most significant bit. -pub const SignMask: u64 = 1 << 63; +pub const Value = packed struct { + pub const TagBoolean: Tag = 0; + pub const TagInteger: Tag = 1; + pub const TagNull: Tag = 2; + pub const TagVoid: Tag = 3; + pub const TagObj: Tag = 4; + pub const TagError: Tag = 5; -/// QNAN and one extra bit to the right. -pub const TaggedValueMask: u64 = 0x7ffc000000000000; + /// Most significant bit. + pub const SignMask: u64 = 1 << 63; -/// TaggedMask + Sign bit indicates a pointer value. -pub const PointerMask: u64 = TaggedValueMask | SignMask; + /// QNAN and one extra bit to the right. + pub const TaggedValueMask: u64 = 0x7ffc000000000000; -pub const BooleanMask: u64 = TaggedValueMask | (@as(u64, TagBoolean) << 32); -pub const FalseMask: u64 = BooleanMask; -pub const TrueBitMask: u64 = 1; -pub const TrueMask: u64 = BooleanMask | TrueBitMask; + /// TaggedMask + Sign bit indicates a pointer value. + pub const PointerMask: u64 = TaggedValueMask | SignMask; -pub const IntegerMask: u64 = TaggedValueMask | (@as(u64, TagInteger) << 32); -pub const NullMask: u64 = TaggedValueMask | (@as(u64, TagNull) << 32); -pub const VoidMask: u64 = TaggedValueMask | (@as(u64, TagVoid) << 32); -pub const ErrorMask: u64 = TaggedValueMask | (@as(u64, TagError) << 32); + pub const BooleanMask: u64 = TaggedValueMask | (@as(u64, TagBoolean) << 32); + pub const FalseMask: u64 = BooleanMask; + pub const TrueBitMask: u64 = 1; + pub const TrueMask: u64 = BooleanMask | TrueBitMask; -pub const TagMask: u32 = (1 << 3) - 1; -pub const TaggedPrimitiveMask = TaggedValueMask | (@as(u64, TagMask) << 32); + pub const IntegerMask: u64 = TaggedValueMask | (@as(u64, TagInteger) << 32); + pub const NullMask: u64 = TaggedValueMask | (@as(u64, TagNull) << 32); + pub const VoidMask: u64 = TaggedValueMask | (@as(u64, TagVoid) << 32); + pub const ErrorMask: u64 = TaggedValueMask | (@as(u64, TagError) << 32); + + pub const TagMask: u32 = (1 << 3) - 1; + pub const TaggedPrimitiveMask = TaggedValueMask | (@as(u64, TagMask) << 32); -pub const Value = packed struct { val: u64, pub const Null = Value{ .val = NullMask }; @@ -163,135 +164,119 @@ pub const Value = packed struct { return self; } -}; -test "NaN boxing" { - const boolean = Value.fromBoolean(true); - const integer = Value.fromInteger(42); - const float = Value.fromFloat(42.24); + pub fn toStringAlloc(value: Value, allocator: Allocator) (Allocator.Error || std.fmt.BufPrintError)!std.ArrayList(u8) { + var str = std.ArrayList(u8).init(allocator); - std.debug.assert(boolean.isBool() and boolean.boolean()); - std.debug.assert(integer.isInteger() and integer.integer() == 42); - std.debug.assert(float.isFloat() and float.float() == 42.24); - std.debug.assert(Value.Null.isNull()); - std.debug.assert(Value.Void.isVoid()); - std.debug.assert(Value.False.isBool()); - std.debug.assert(Value.True.isBool()); -} + try value.toString(&str.writer()); -// TODO: move those in Value struct - -pub fn valueToStringAlloc(allocator: Allocator, value: Value) (Allocator.Error || std.fmt.BufPrintError)!std.ArrayList(u8) { - var str = std.ArrayList(u8).init(allocator); - - try valueToString(&str.writer(), value); - - return str; -} + return str; + } -pub fn valueToString(writer: *const std.ArrayList(u8).Writer, value: Value) (Allocator.Error || std.fmt.BufPrintError)!void { - if (value.isObj()) { - try objToString(writer, value.obj()); + pub fn toString(self: Value, writer: *const std.ArrayList(u8).Writer) (Allocator.Error || std.fmt.BufPrintError)!void { + if (self.isObj()) { + try objToString(writer, self.obj()); - return; - } + return; + } - if (value.isFloat()) { - try writer.print("{d}", .{value.float()}); - return; - } + if (self.isFloat()) { + try writer.print("{d}", .{self.float()}); + return; + } - switch (value.getTag()) { - TagBoolean => try writer.print("{}", .{value.boolean()}), - TagInteger => try writer.print("{d}", .{value.integer()}), - TagNull => try writer.print("null", .{}), - TagVoid => try writer.print("void", .{}), - else => try writer.print("{d}", .{value.float()}), - } -} - -pub fn valueEql(a: Value, b: Value) bool { - // zig fmt: off - if (a.isObj() != b.isObj() - or a.isNumber() != b.isNumber() - or (!a.isNumber() and !b.isNumber() and a.getTag() != b.getTag())) { - return false; + switch (self.getTag()) { + TagBoolean => try writer.print("{}", .{self.boolean()}), + TagInteger => try writer.print("{d}", .{self.integer()}), + TagNull => try writer.print("null", .{}), + TagVoid => try writer.print("void", .{}), + else => try writer.print("{d}", .{self.float()}), + } } - // zig fmt: on - if (a.isObj()) { - return a.obj().eql(b.obj()); - } + pub fn eql(a: Value, b: Value) bool { + // zig fmt: off + if (a.isObj() != b.isObj() + or a.isNumber() != b.isNumber() + or (!a.isNumber() and !b.isNumber() and a.getTag() != b.getTag())) { + return false; + } + // zig fmt: on - if (a.isInteger() or a.isFloat()) { - const a_f: ?f64 = if (a.isFloat()) a.float() else null; - const b_f: ?f64 = if (b.isFloat()) b.float() else null; - const a_i: ?i32 = if (a.isInteger()) a.integer() else null; - const b_i: ?i32 = if (b.isInteger()) b.integer() else null; + if (a.isObj()) { + return a.obj().eql(b.obj()); + } - if (a_f) |af| { - if (b_f) |bf| { - return af == bf; - } else { - return af == @as(f64, @floatFromInt(b_i.?)); - } - } else { - if (b_f) |bf| { - return @as(f64, @floatFromInt(a_i.?)) == bf; + if (a.isInteger() or a.isFloat()) { + const a_f: ?f64 = if (a.isFloat()) a.float() else null; + const b_f: ?f64 = if (b.isFloat()) b.float() else null; + const a_i: ?i32 = if (a.isInteger()) a.integer() else null; + const b_i: ?i32 = if (b.isInteger()) b.integer() else null; + + if (a_f) |af| { + if (b_f) |bf| { + return af == bf; + } else { + return af == @as(f64, @floatFromInt(b_i.?)); + } } else { - return a_i.? == b_i.?; + if (b_f) |bf| { + return @as(f64, @floatFromInt(a_i.?)) == bf; + } else { + return a_i.? == b_i.?; + } } } + + return switch (a.getTag()) { + TagBoolean => a.boolean() == b.boolean(), + TagNull => true, + TagVoid => true, + else => unreachable, + }; } - return switch (a.getTag()) { - TagBoolean => a.boolean() == b.boolean(), - TagNull => true, - TagVoid => true, - else => unreachable, - }; -} + pub fn is(type_def_val: Value, value: Value) bool { + const type_def: *ObjTypeDef = ObjTypeDef.cast(type_def_val.obj()).?; -pub fn valueIs(type_def_val: Value, value: Value) bool { - const type_def: *ObjTypeDef = ObjTypeDef.cast(type_def_val.obj()).?; + if (type_def.def_type == .Any) { + return true; + } - if (type_def.def_type == .Any) { - return true; - } + if (value.isObj()) { + return value.obj().is(type_def); + } - if (value.isObj()) { - return value.obj().is(type_def); - } + if (value.isFloat()) { + return type_def.def_type == .Float; + } - if (value.isFloat()) { - return type_def.def_type == .Float; + return switch (value.getTag()) { + TagBoolean => type_def.def_type == .Bool, + TagInteger => type_def.def_type == .Integer, + // TODO: this one is ambiguous at runtime, is it the `null` constant? or an optional local with a null value? + TagNull => type_def.def_type == .Void or type_def.optional, + TagVoid => type_def.def_type == .Void, + else => type_def.def_type == .Float, + }; } - return switch (value.getTag()) { - TagBoolean => type_def.def_type == .Bool, - TagInteger => type_def.def_type == .Integer, - // TODO: this one is ambiguous at runtime, is it the `null` constant? or an optional local with a null value? - TagNull => type_def.def_type == .Void or type_def.optional, - TagVoid => type_def.def_type == .Void, - else => type_def.def_type == .Float, - }; -} - -pub fn valueTypeEql(value: Value, type_def: *ObjTypeDef) bool { - if (value.isObj()) { - return value.obj().typeEql(type_def); - } + pub fn typeEql(value: Value, type_def: *ObjTypeDef) bool { + if (value.isObj()) { + return value.obj().typeEql(type_def); + } - if (value.isFloat()) { - return type_def.def_type == .Float; - } + if (value.isFloat()) { + return type_def.def_type == .Float; + } - return switch (value.getTag()) { - TagBoolean => type_def.def_type == .Bool, - TagInteger => type_def.def_type == .Integer, - // TODO: this one is ambiguous at runtime, is it the `null` constant? or an optional local with a null value? - TagNull => type_def.def_type == .Void or type_def.optional, - TagVoid => type_def.def_type == .Void, - else => type_def.def_type == .Float, - }; -} + return switch (value.getTag()) { + TagBoolean => type_def.def_type == .Bool, + TagInteger => type_def.def_type == .Integer, + // TODO: this one is ambiguous at runtime, is it the `null` constant? or an optional local with a null value? + TagNull => type_def.def_type == .Void or type_def.optional, + TagVoid => type_def.def_type == .Void, + else => type_def.def_type == .Float, + }; + } +}; diff --git a/src/vm.zig b/src/vm.zig index 03c6ff04..43159afb 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -1,26 +1,22 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; -const _value = @import("value.zig"); -const _chunk = @import("chunk.zig"); +const Value = @import("value.zig").Value; +const Chunk = @import("Chunk.zig"); +const OpCode = Chunk.OpCode; +const Ast = @import("Ast.zig"); const _disassembler = @import("disassembler.zig"); const _obj = @import("obj.zig"); -const _node = @import("node.zig"); const Allocator = std.mem.Allocator; const BuildOptions = @import("build_options"); const _memory = @import("memory.zig"); const GarbageCollector = _memory.GarbageCollector; const TypeRegistry = _memory.TypeRegistry; -const MIRJIT = @import("mirjit.zig"); -const Token = @import("token.zig").Token; -const Reporter = @import("reporter.zig"); -const FFI = @import("ffi.zig"); - -const Value = _value.Value; -const valueToString = _value.valueToString; -const valueToStringAlloc = _value.valueToStringAlloc; -const valueEql = _value.valueEql; -const valueIs = _value.valueIs; +const JIT = @import("Jit.zig"); +const Token = @import("Token.zig"); +const Reporter = @import("Reporter.zig"); +const FFI = @import("FFI.zig"); + const ObjType = _obj.ObjType; const Obj = _obj.Obj; const ObjNative = _obj.ObjNative; @@ -43,10 +39,7 @@ const ObjBoundMethod = _obj.ObjBoundMethod; const ObjTypeDef = _obj.ObjTypeDef; const ObjPattern = _obj.ObjPattern; const ObjForeignContainer = _obj.ObjForeignContainer; -const FunctionNode = _node.FunctionNode; const cloneObject = _obj.cloneObject; -const OpCode = _chunk.OpCode; -const Chunk = _chunk.Chunk; const disassembleChunk = _disassembler.disassembleChunk; const dumpStack = _disassembler.dumpStack; const jmp = @import("jmp.zig").jmp; @@ -79,7 +72,7 @@ pub const CallFrame = struct { error_value: ?Value = null, // Line in source code where the call occured - call_site: ?Token, + call_site: ?Ast.TokenIndex, // Offset at which error can be handled (means we're in a try block) try_ip: ?usize = null, @@ -162,7 +155,7 @@ pub const Fiber = struct { }; if (stack_slice != null) { - std.mem.copy(Value, self.stack, stack_slice.?); + std.mem.copyForwards(Value, self.stack, stack_slice.?); self.stack_top = @as([*]Value, @ptrCast(self.stack[stack_slice.?.len..])); } else { @@ -230,7 +223,7 @@ pub const Fiber = struct { .OP_FIBER_FOREACH => { _ = vm.pop(); - var value_slot: *Value = @ptrCast(vm.current_fiber.stack_top - 2); + const value_slot: *Value = @ptrCast(vm.current_fiber.stack_top - 2); value_slot.* = top; }, @@ -316,7 +309,7 @@ pub const Fiber = struct { // We don't care about the returned value _ = vm.pop(); - var value_slot: *Value = @ptrCast(vm.current_fiber.stack_top - 2); + const value_slot: *Value = @ptrCast(vm.current_fiber.stack_top - 2); value_slot.* = Value.Null; }, @@ -341,30 +334,28 @@ pub const VM = struct { gc: *GarbageCollector, current_fiber: *Fiber, + current_ast: Ast, main_fiber: *Fiber, globals: std.ArrayList(Value), globals_count: usize = 0, import_registry: *ImportRegistry, - mir_jit: ?MIRJIT = null, + jit: ?JIT = null, flavor: RunFlavor, reporter: Reporter, ffi: FFI, pub fn init(gc: *GarbageCollector, import_registry: *ImportRegistry, flavor: RunFlavor) !Self { - var main_fiber = try gc.allocator.create(Fiber); - - var self: Self = .{ + return .{ .gc = gc, .import_registry = import_registry, .globals = std.ArrayList(Value).init(gc.allocator), - .current_fiber = main_fiber, - .main_fiber = main_fiber, + .current_ast = undefined, + .current_fiber = undefined, + .main_fiber = undefined, .flavor = flavor, .reporter = Reporter{ .allocator = gc.allocator }, .ffi = FFI.init(gc), }; - - return self; } pub fn deinit(self: *Self) void { @@ -374,7 +365,7 @@ pub const VM = struct { } pub fn cliArgs(self: *Self, args: ?[][:0]u8) !*ObjList { - var list_def: ObjList.ListDef = ObjList.ListDef.init( + const list_def = ObjList.ListDef.init( self.gc.allocator, try self.gc.allocateObject( ObjTypeDef, @@ -382,11 +373,11 @@ pub const VM = struct { ), ); - var list_def_union: ObjTypeDef.TypeUnion = .{ + const list_def_union: ObjTypeDef.TypeUnion = .{ .List = list_def, }; - var list_def_type: *ObjTypeDef = try self.gc.allocateObject( + const list_def_type: *ObjTypeDef = try self.gc.allocateObject( ObjTypeDef, ObjTypeDef{ .def_type = .List, @@ -467,7 +458,7 @@ pub const VM = struct { } inline fn swap(self: *Self, from: u8, to: u8) void { - var temp: Value = (self.current_fiber.stack_top - to - 1)[0]; + const temp: Value = (self.current_fiber.stack_top - to - 1)[0]; (self.current_fiber.stack_top - to - 1)[0] = (self.current_fiber.stack_top - from - 1)[0]; (self.current_fiber.stack_top - from - 1)[0] = temp; } @@ -484,7 +475,7 @@ pub const VM = struct { return self.currentFrame().?.closure.globals; } - pub fn interpret(self: *Self, function: *ObjFunction, args: ?[][:0]u8) MIRJIT.Error!void { + pub fn interpret(self: *Self, ast: Ast, function: *ObjFunction, args: ?[][:0]u8) JIT.Error!void { const fiber_def = ObjFiber.FiberDef{ .return_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), .yield_type = try self.gc.type_registry.getTypeDef(.{ .def_type = .Void }), @@ -500,6 +491,8 @@ pub const VM = struct { .resolved_type = resolved_type, }); + self.current_ast = ast; + self.current_fiber = try self.gc.allocator.create(Fiber); self.current_fiber.* = try Fiber.init( self.gc.allocator, type_def, @@ -539,8 +532,8 @@ pub const VM = struct { } inline fn readInstruction(self: *Self) u32 { - const current_frame: *CallFrame = self.currentFrame().?; - var instruction: u32 = current_frame.closure.function.chunk.code.items[current_frame.ip]; + const current_frame = self.currentFrame().?; + const instruction = current_frame.closure.function.chunk.code.items[current_frame.ip]; current_frame.ip += 1; @@ -1033,7 +1026,7 @@ pub const VM = struct { } fn OP_TO_STRING(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - const str = valueToStringAlloc(self.gc.allocator, self.pop()) catch |e| { + const str = self.pop().toStringAlloc(self.gc.allocator) catch |e| { panic(e); unreachable; }; @@ -1085,7 +1078,7 @@ pub const VM = struct { } fn OP_CLOSURE(self: *Self, current_frame: *CallFrame, _: u32, _: OpCode, arg: u24) void { - var function: *ObjFunction = self.readConstant(arg).obj().access(ObjFunction, .Function, self.gc).?; + const function: *ObjFunction = self.readConstant(arg).obj().access(ObjFunction, .Function, self.gc).?; var closure: *ObjClosure = self.gc.allocateObject( ObjClosure, ObjClosure.init(self.gc.allocator, self, function) catch |e| { @@ -1101,8 +1094,8 @@ pub const VM = struct { var i: usize = 0; while (i < function.upvalue_count) : (i += 1) { - var is_local: bool = self.readByte() == 1; - var index: u8 = self.readByte(); + const is_local: bool = self.readByte() == 1; + const index: u8 = self.readByte(); if (is_local) { closure.upvalues.append(self.captureUpvalue(&(current_frame.slots[index])) catch |e| { @@ -1404,7 +1397,7 @@ pub const VM = struct { panic(e); unreachable; }).?; - var member_value: Value = member.toValue(); + const member_value: Value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; self.callValue(member_value, arg_count, catch_value, false) catch |e| { @@ -1437,7 +1430,7 @@ pub const VM = struct { panic(e); unreachable; }).?; - var member_value: Value = member.toValue(); + const member_value: Value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; self.callValue(member_value, arg_count, catch_value, false) catch |e| { @@ -1470,7 +1463,7 @@ pub const VM = struct { panic(e); unreachable; }).?; - var member_value: Value = member.toValue(); + const member_value: Value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; self.callValue(member_value, arg_count, catch_value, false) catch |e| { panic(e); @@ -1504,7 +1497,7 @@ pub const VM = struct { unreachable; }).?; - var member_value: Value = member.toValue(); + const member_value: Value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; self.callValue(member_value, arg_count, catch_value, false) catch |e| { panic(e); @@ -1538,7 +1531,7 @@ pub const VM = struct { unreachable; }).?; - var member_value: Value = member.toValue(); + const member_value: Value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; self.callValue(member_value, arg_count, catch_value, false) catch |e| { panic(e); @@ -1561,7 +1554,7 @@ pub const VM = struct { // result_count > 0 when the return is `export` inline fn returnFrame(self: *Self) bool { - var result = self.pop(); + const result = self.pop(); const frame: *CallFrame = self.currentFrame().?; @@ -1650,13 +1643,13 @@ pub const VM = struct { // defer gn.deinit(); // } - vm.interpret(closure.function, null) catch |e| { + vm.interpret(self.current_ast, closure.function, null) catch |e| { panic(e); unreachable; }; // Top of stack is how many export we got - var exported_count = vm.peek(0).integer(); + const exported_count = vm.peek(0).integer(); // Copy them to this vm globals var import_cache = std.ArrayList(Value).init(self.gc.allocator); @@ -1834,8 +1827,8 @@ pub const VM = struct { } fn OP_LIST_APPEND(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - var list: *ObjList = self.peek(1).obj().access(ObjList, .List, self.gc).?; - var list_value: Value = self.peek(0); + var list = self.peek(1).obj().access(ObjList, .List, self.gc).?; + const list_value = self.peek(0); list.rawAppend(self.gc, list_value) catch |e| { panic(e); @@ -1884,9 +1877,9 @@ pub const VM = struct { } fn OP_SET_MAP(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - var map: *ObjMap = self.peek(2).obj().access(ObjMap, .Map, self.gc).?; - var key: Value = self.peek(1); - var value: Value = self.peek(0); + var map = self.peek(2).obj().access(ObjMap, .Map, self.gc).?; + const key = self.peek(1); + const value = self.peek(0); map.set(self.gc, key, value) catch |e| { panic(e); @@ -1911,7 +1904,7 @@ pub const VM = struct { } fn OP_GET_LIST_SUBSCRIPT(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - var list: *ObjList = self.peek(1).obj().access(ObjList, .List, self.gc).?; + const list = self.peek(1).obj().access(ObjList, .List, self.gc).?; const index = self.peek(0).integer(); if (index < 0) { @@ -1948,7 +1941,7 @@ pub const VM = struct { return; } - var list_item: Value = list.items.items[list_index]; + const list_item = list.items.items[list_index]; // Pop list and index _ = self.pop(); @@ -1972,8 +1965,8 @@ pub const VM = struct { } fn OP_GET_MAP_SUBSCRIPT(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - var map: *ObjMap = self.peek(1).obj().access(ObjMap, .Map, self.gc).?; - var index: Value = self.peek(0); + var map = self.peek(1).obj().access(ObjMap, .Map, self.gc).?; + const index = self.peek(0); // Pop map and key _ = self.pop(); @@ -2001,7 +1994,7 @@ pub const VM = struct { } fn OP_GET_STRING_SUBSCRIPT(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - var str = self.peek(1).obj().access(ObjString, .String, self.gc).?; + const str = self.peek(1).obj().access(ObjString, .String, self.gc).?; const index = self.peek(0).integer(); if (index < 0) { @@ -2022,7 +2015,7 @@ pub const VM = struct { const str_index: usize = @intCast(index); if (str_index < str.string.len) { - var str_item: Value = (self.gc.copyString(&([_]u8{str.string[str_index]})) catch |e| { + const str_item = (self.gc.copyString(&([_]u8{str.string[str_index]})) catch |e| { panic(e); unreachable; }).toValue(); @@ -2159,7 +2152,7 @@ pub const VM = struct { } fn OP_GET_ENUM_CASE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - var enum_: *ObjEnum = self.peek(0).obj().access(ObjEnum, .Enum, self.gc).?; + const enum_ = self.peek(0).obj().access(ObjEnum, .Enum, self.gc).?; _ = self.pop(); @@ -2191,10 +2184,10 @@ pub const VM = struct { } fn OP_GET_ENUM_CASE_VALUE(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - var enum_case: *ObjEnumInstance = self.peek(0).obj().access(ObjEnumInstance, .EnumInstance, self.gc).?; + const enum_case = self.peek(0).obj().access(ObjEnumInstance, .EnumInstance, self.gc).?; _ = self.pop(); - self.push(enum_case.enum_ref.cases.items[enum_case.case]); + self.push(enum_case.enum_ref.cases[enum_case.case]); const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2211,12 +2204,12 @@ pub const VM = struct { } fn OP_GET_ENUM_CASE_FROM_VALUE(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - var case_value = self.pop(); - var enum_: *ObjEnum = self.pop().obj().access(ObjEnum, .Enum, self.gc).?; + const case_value = self.pop(); + const enum_ = self.pop().obj().access(ObjEnum, .Enum, self.gc).?; var found = false; - for (enum_.cases.items, 0..) |case, index| { - if (valueEql(case, case_value)) { + for (enum_.cases, 0..) |case, index| { + if (case.eql(case_value)) { var enum_case: *ObjEnumInstance = self.gc.allocateObject(ObjEnumInstance, ObjEnumInstance{ .enum_ref = enum_, .case = @intCast(index), @@ -2388,8 +2381,8 @@ pub const VM = struct { fn OP_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { const name = self.readString(arg); - var property: Value = self.peek(0); - var object: *ObjObject = self.peek(1).obj().access(ObjObject, .Object, self.gc).?; + const property = self.peek(0); + var object = self.peek(1).obj().access(ObjObject, .Object, self.gc).?; if (object.type_def.resolved_type.?.Object.fields.contains(name.string)) { object.setField(self.gc, name, property) catch |e| { @@ -3269,7 +3262,7 @@ pub const VM = struct { } fn OP_EQUAL(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - self.push(Value.fromBoolean(valueEql(self.pop(), self.pop()))); + self.push(Value.fromBoolean(self.pop().eql(self.pop()))); const next_full_instruction: u32 = self.readInstruction(); @call( @@ -3286,7 +3279,7 @@ pub const VM = struct { } fn OP_IS(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - self.push(Value.fromBoolean(valueIs(self.pop(), self.pop()))); + self.push(Value.fromBoolean(self.pop().is(self.pop()))); const next_full_instruction: u32 = self.readInstruction(); @call( @@ -3411,8 +3404,8 @@ pub const VM = struct { fn OP_LIST_FOREACH(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { var key_slot: *Value = @ptrCast(self.current_fiber.stack_top - 3); - var value_slot: *Value = @ptrCast(self.current_fiber.stack_top - 2); - var list: *ObjList = self.peek(0).obj().access(ObjList, .List, self.gc).?; + const value_slot: *Value = @ptrCast(self.current_fiber.stack_top - 2); + var list = self.peek(0).obj().access(ObjList, .List, self.gc).?; // Get next index key_slot.* = if (list.rawNext( @@ -3447,14 +3440,14 @@ pub const VM = struct { fn OP_ENUM_FOREACH(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { var value_slot: *Value = @ptrCast(self.current_fiber.stack_top - 2); - var enum_case: ?*ObjEnumInstance = if (value_slot.*.isNull()) + const enum_case = if (value_slot.*.isNull()) null else value_slot.obj().access(ObjEnumInstance, .EnumInstance, self.gc).?; var enum_: *ObjEnum = self.peek(0).obj().access(ObjEnum, .Enum, self.gc).?; // Get next enum case - var next_case: ?*ObjEnumInstance = enum_.rawNext(self, enum_case) catch |e| { + const next_case = enum_.rawNext(self, enum_case) catch |e| { panic(e); unreachable; }; @@ -3475,12 +3468,12 @@ pub const VM = struct { } fn OP_MAP_FOREACH(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - var key_slot: *Value = @ptrCast(self.current_fiber.stack_top - 3); - var value_slot: *Value = @ptrCast(self.current_fiber.stack_top - 2); + const key_slot: *Value = @ptrCast(self.current_fiber.stack_top - 3); + const value_slot: *Value = @ptrCast(self.current_fiber.stack_top - 2); var map: *ObjMap = self.peek(0).obj().access(ObjMap, .Map, self.gc).?; - var current_key: ?Value = if (!key_slot.*.isNull()) key_slot.* else null; + const current_key = if (!key_slot.*.isNull()) key_slot.* else null; - var next_key: ?Value = map.rawNext(current_key); + const next_key = map.rawNext(current_key); key_slot.* = if (next_key) |unext_key| unext_key else Value.Null; if (next_key) |unext_key| { @@ -3502,7 +3495,7 @@ pub const VM = struct { } fn OP_FIBER_FOREACH(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { - var value_slot: *Value = @ptrCast(self.current_fiber.stack_top - 2); + const value_slot: *Value = @ptrCast(self.current_fiber.stack_top - 2); var fiber = self.peek(0).obj().access(ObjFiber, .Fiber, self.gc).?; if (fiber.fiber.status == .Over) { @@ -3608,7 +3601,7 @@ pub const VM = struct { ); } - pub fn throw(self: *Self, code: Error, payload: Value, previous_stack: ?*std.ArrayList(CallFrame), previous_error_site: ?Token) MIRJIT.Error!void { + pub fn throw(self: *Self, code: Error, payload: Value, previous_stack: ?*std.ArrayList(CallFrame), previous_error_site: ?Ast.TokenIndex) JIT.Error!void { var stack = if (previous_stack) |pstack| pstack.* else @@ -3663,7 +3656,7 @@ pub const VM = struct { } } - const value_str = try valueToStringAlloc(self.gc.allocator, processed_payload); + const value_str = try processed_payload.toStringAlloc(self.gc.allocator); defer value_str.deinit(); self.reportRuntimeError( @@ -3700,7 +3693,7 @@ pub const VM = struct { } } - fn reportRuntimeError(self: *Self, message: []const u8, error_site: ?Token, stack: []const CallFrame) void { + fn reportRuntimeError(self: *Self, message: []const u8, error_site: ?Ast.TokenIndex, stack: []const CallFrame) void { var notes = std.ArrayList(Reporter.Note).init(self.gc.allocator); defer { for (notes.items) |note| { @@ -3730,7 +3723,7 @@ pub const VM = struct { else unext.closure.function.name.string, if (frame.call_site) |call_site| - call_site.script_name + self.current_ast.tokens.items(.script_name)[call_site] else frame.closure.function.type_def.resolved_type.?.Function.script_name.string, }, @@ -3746,7 +3739,7 @@ pub const VM = struct { else " ╰─", if (frame.call_site) |call_site| - call_site.script_name + self.current_ast.tokens.items(.script_name)[call_site] else frame.closure.function.type_def.resolved_type.?.Function.script_name.string, }, @@ -3757,8 +3750,8 @@ pub const VM = struct { writer.print( ":{d}:{d}", .{ - call_site.line + 1, - call_site.column, + self.current_ast.tokens.items(.line)[call_site] + 1, + self.current_ast.tokens.items(.column)[call_site], }, ) catch @panic("Could not report error"); } @@ -3776,7 +3769,7 @@ pub const VM = struct { .error_type = .runtime, .items = &[_]Reporter.ReportItem{ .{ - .location = error_site orelse stack[0].call_site.?, + .location = self.current_ast.tokens.get(error_site orelse stack[0].call_site.?), .kind = .@"error", .message = message, }, @@ -3787,20 +3780,20 @@ pub const VM = struct { err_report.reportStderr(&self.reporter) catch @panic("Could not report error"); } - fn call(self: *Self, closure: *ObjClosure, arg_count: u8, catch_value: ?Value, in_fiber: bool) MIRJIT.Error!void { + fn call(self: *Self, closure: *ObjClosure, arg_count: u8, catch_value: ?Value, in_fiber: bool) JIT.Error!void { closure.function.call_count += 1; var native = closure.function.native; - if (self.mir_jit) |*mir_jit| { - mir_jit.call_count += 1; + if (self.jit) |*jit| { + jit.call_count += 1; // Do we need to jit the function? // TODO: figure out threshold strategy if (!in_fiber and self.shouldCompileFunction(closure)) { var timer = std.time.Timer.start() catch unreachable; var success = true; - mir_jit.compileFunction(closure) catch |err| { - if (err == MIRJIT.Error.CantCompile) { + jit.compileFunction(self.current_ast, closure) catch |err| { + if (err == JIT.Error.CantCompile) { success = false; } else { return err; @@ -3817,7 +3810,7 @@ pub const VM = struct { ); } - mir_jit.jit_time += timer.read(); + jit.jit_time += timer.read(); if (success) { native = closure.function.native; @@ -3844,7 +3837,9 @@ pub const VM = struct { if (self.flavor == .Test and closure.function.type_def.resolved_type.?.Function.function_type == .Test) { std.debug.print( "\x1b[33m▶ Test: {s}\x1b[0m\n", - .{@as(*FunctionNode, @ptrCast(@alignCast(closure.function.node))).test_message.?}, + .{ + self.current_ast.tokens.items(.lexeme)[self.current_ast.nodes.items(.components)[closure.function.node].Function.test_message.?], + }, ); } @@ -3990,7 +3985,7 @@ pub const VM = struct { self.push(Value.fromObj(bound.toObj())); } - pub fn callValue(self: *Self, callee: Value, arg_count: u8, catch_value: ?Value, in_fiber: bool) MIRJIT.Error!void { + pub fn callValue(self: *Self, callee: Value, arg_count: u8, catch_value: ?Value, in_fiber: bool) JIT.Error!void { var obj: *Obj = callee.obj(); switch (obj.obj_type) { .Bound => { @@ -4069,7 +4064,7 @@ pub const VM = struct { }, .String => { if (try ObjString.member(self, name)) |member| { - var member_value: Value = member.toValue(); + const member_value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; try self.callValue(member_value, arg_count, catch_value, in_fiber); @@ -4081,7 +4076,7 @@ pub const VM = struct { }, .Pattern => { if (try ObjPattern.member(self, name)) |member| { - var member_value: Value = member.toValue(); + const member_value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; try self.callValue(member_value, arg_count, catch_value, in_fiber); @@ -4093,7 +4088,7 @@ pub const VM = struct { }, .Fiber => { if (try ObjFiber.member(self, name)) |member| { - var member_value: Value = member.toValue(); + const member_value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; try self.callValue(member_value, arg_count, catch_value, in_fiber); @@ -4107,7 +4102,7 @@ pub const VM = struct { const list = obj.access(ObjList, .List, self.gc).?; if (try list.member(self, name)) |member| { - var member_value: Value = member.toValue(); + const member_value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; try self.callValue(member_value, arg_count, catch_value, in_fiber); @@ -4121,7 +4116,7 @@ pub const VM = struct { const map = obj.access(ObjMap, .Map, self.gc).?; if (try map.member(self, name)) |member| { - var member_value: Value = member.toValue(); + const member_value = member.toValue(); (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; try self.callValue(member_value, arg_count, catch_value, in_fiber); @@ -4178,18 +4173,20 @@ pub const VM = struct { return false; } - if (self.mir_jit != null and (self.mir_jit.?.compiled_closures.get(closure) != null or self.mir_jit.?.blacklisted_closures.get(closure) != null)) { + if (self.jit != null and (self.jit.?.compiled_closures.get(closure) != null or self.jit.?.blacklisted_closures.get(closure) != null)) { return false; } - const function_node: *FunctionNode = @ptrCast(@alignCast(closure.function.node)); - const user_hot = function_node.node.docblock != null and std.mem.indexOf(u8, function_node.node.docblock.?.lexeme, "@hot") != null; + const user_hot = if (self.current_ast.nodes.items(.components)[closure.function.node].Function.docblock) |docblock| + std.mem.indexOf(u8, self.current_ast.tokens.items(.lexeme)[docblock], "@hot") != null + else + false; return if (BuildOptions.jit_always_on) return true else user_hot or (closure.function.call_count > 10 and - (@as(f128, @floatFromInt(closure.function.call_count)) / @as(f128, @floatFromInt(self.mir_jit.?.call_count))) > BuildOptions.jit_prof_threshold); + (@as(f128, @floatFromInt(closure.function.call_count)) / @as(f128, @floatFromInt(self.jit.?.call_count))) > BuildOptions.jit_prof_threshold); } }; diff --git a/tests/006-enums.buzz b/tests/006-enums.buzz index bd7236a1..5dce28c4 100644 --- a/tests/006-enums.buzz +++ b/tests/006-enums.buzz @@ -40,7 +40,7 @@ test "Enums" { NaturalEnum? fromValue = NaturalEnum(0); assert(fromValue != null, message: "Could get enum instance from value"); - assert(fromValue!.value == 0, message: "Could get correct enum instance from value"); + assert(fromValue?.value == 0, message: "Could get correct enum instance from value"); } test "Enum case as default value" { diff --git a/tests/024-os.buzz b/tests/024-os.buzz index 8e227611..768997e6 100644 --- a/tests/024-os.buzz +++ b/tests/024-os.buzz @@ -19,7 +19,7 @@ test "os.tmpFilename" { } test "os.execute" { - assert(os.execute(["/bin/ls", "-l"]) == 0, message: "Could execute a command"); + assert(os.execute(["./zig-out/bin/buzz", "--version"]) == 0, message: "Could execute a command"); } test "os.sleep" { diff --git a/tests/027-std.buzz b/tests/027-run-file.buzz similarity index 100% rename from tests/027-std.buzz rename to tests/027-run-file.buzz diff --git a/tests/032-debug.buzz b/tests/032-debug.buzz index 826fbf68..c63bde50 100644 --- a/tests/032-debug.buzz +++ b/tests/032-debug.buzz @@ -2,17 +2,18 @@ import "std"; import "debug" as debug; import "serialize"; -test "Get AST" { - str source = debug.ast(` - import \"std\"; - - fun main([str] _) > void -> print(\"hello world\"); - `, scriptName: "test"); - - Boxed jsonAST = jsonDecode(source); - - print(jsonEncode(jsonAST)); -} +| TODO: put back one debug.ast is reactivated +| test "Get AST" { +| str source = debug.ast(` +| import \"std\"; +| +| fun main([str] _) > void -> print(\"hello world\"); +| `, scriptName: "test"); +| +| Boxed jsonAST = jsonDecode(source); +| +| print(jsonEncode(jsonAST)); +| } enum MyEnum { One, diff --git a/tests/041-iterator.buzz b/tests/041-iterator.buzz index 31736027..3bbafb8e 100644 --- a/tests/041-iterator.buzz +++ b/tests/041-iterator.buzz @@ -21,4 +21,25 @@ test "finobacci generator" { i = i + 1; } +} + +object Hello { + fun range() > [int] > int? { + var list = []; + foreach (int i in 0..10) { + _ = yield i; + + list.append(i); + } + + return list; + } +} + +test "dot async call" { + var hello = Hello{}; + + foreach (int i in &hello.range()) { + print("i {i}"); + } } \ No newline at end of file diff --git a/tests/044-break-continue.buzz b/tests/044-break-continue.buzz index 632b3be8..c0e2bd55 100644 --- a/tests/044-break-continue.buzz +++ b/tests/044-break-continue.buzz @@ -1,6 +1,6 @@ import "std"; -test "break properly jumps and closes scope" { +test "continue properly jumps and closes scope" { [int] list = [1, 2, 3, 4, 5]; foreach (int value in list) { str _ = "hello there!";