diff --git a/src/ast.zig b/src/ast.zig index 248741f..4c6c796 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -1,26 +1,18 @@ const std = @import("std"); -const token = @import("./token.zig"); -const UUID = @import("./utils/uuid.zig").UUID; +const token = @import("token.zig"); +const UUID = @import("utils/uuid.zig").UUID; const Token = token.Token; const Allocator = std.mem.Allocator; pub const Tree = struct { - arena: std.heap.ArenaAllocator.State, - allocator: Allocator, - root: []const Statement, - source: []const u8, - - pub fn deinit(self: *const Tree) void { - self.arena.promote(self.allocator).deinit(); - } pub fn print(self: *const Tree, writer: anytype) void { writer.print("\n===TREE===", .{}); for (self.root) |state| { state.print(writer, "", 0); } - writer.print("\n", .{}); + writer.print("\n=== ===\n", .{}); } }; @@ -89,25 +81,35 @@ pub const Expression = struct { writer.print("\n", .{}); var d: usize = 0; while (d < depth) : (d += 1) { - writer.print(" ", .{}); + writer.print(" ", .{}); } writer.print("{s}", .{prefix}); switch (self.type) { .binary => |b| { - writer.print("BINARY::{s}", .{b.operator.toString()}); - b.left.print(writer, "LEFT::", depth + 1); - b.right.print(writer, "RIGHT::", depth + 1); + writer.print("BINARY:: {s}", .{@tagName(b.operator)}); + b.left.print(writer, "LEFT:: ", depth + 1); + b.right.print(writer, "RIGHT:: ", depth + 1); }, .call => |c| { - writer.print("CALL::{d}", .{c.arguments.len}); + writer.print("CALL::", .{}); + c.target.print(writer, "TARGET::", depth + 1); for (c.arguments) |arg| { arg.print(writer, "ARG::", depth + 1); } }, - .identifier => |i| writer.print("{s}", .{i}), - .number => |n| writer.print("{d}", .{n}), + .indexer => |i| { + writer.print("INDEXER:: ", .{}); + i.target.print(writer, "TARGET:: ", depth + 1); + i.index.print(writer, "INDEX:: ", depth + 1); + }, + .identifier => |i| writer.print("IDENTIFIER:: {s}", .{i}), + .number => |n| writer.print("NUM:: {d}", .{n}), + .string => |s| { + writer.print("STRING:: {s}", .{s.raw}); + for (s.expressions) |e| e.print(writer, "EXP:: ", depth + 1); + }, .function => |f| { - writer.print("FUNCTION::{s}", .{f.parameters}); + writer.print("FUNCTION:: {s}", .{f.parameters}); for (f.body) |s| { s.print(writer, "", depth + 1); } @@ -210,22 +212,34 @@ pub const Statement = struct { } writer.print("{s}", .{prefix}); switch (self.type) { + .include => |i| { + writer.print("INCLUDE:: {s}", .{i.path}); + for (i.contents) |c| c.print(writer, "", depth + 1); + }, .block => |b| { - for (b) |s| s.print(writer, "BLOCK::", depth + 1); + for (b) |s| s.print(writer, "", depth + 1); }, - .expression => |e| e.print(writer, "EXPRESSION::", depth + 1), + .expression => |e| e.print(writer, "EXPRESSION:: ", depth), .@"if" => |i| { - writer.print("IF", .{}); - i.condition.print(writer, "CONDITION", depth + 1); - for (i.then_branch) |s| s.print(writer, "THEN", depth + 1); + writer.print("IF:: ", .{}); + i.condition.print(writer, "CONDITION:: ", depth + 1); + for (i.then_branch) |s| s.print(writer, "THEN:: ", depth + 1); if (i.else_branch) |eb| { - for (eb) |s| s.print(writer, "ELSE", depth + 1); + for (eb) |s| s.print(writer, "ELSE:: ", depth + 1); + } + }, + .@"enum" => |e| { + writer.print("ENUM:: {s} [", .{e.name}); + for (e.values) |v| { + writer.print("{s},", .{v}); } + writer.print("]", .{}); }, - .return_expression => |re| re.print(writer, "RETURN VALUE::", depth + 1), - .return_void => writer.print("RETURN::", .{}), + .return_expression => |re| re.print(writer, "RETURN VALUE:: ", depth + 1), + .return_void => writer.print("RETURN VOID", .{}), .variable => |v| { - v.initializer.print(writer, "VARIABLE::", depth + 1); + writer.print("VARIABLE:: {s}", .{v.name}); + v.initializer.print(writer, "", depth + 1); }, else => { writer.print("{any}", .{self}); diff --git a/src/cli.zig b/src/cli.zig index 7dada1e..d4e4dcb 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -137,6 +137,7 @@ pub fn main() !void { var mod = Module.init(allocator, full_path) catch |err| { return checkVerbose(err); }; + errdefer mod.deinit(); mod.use_loc = try checkFlag("--loc"); if (is_loc) { @@ -175,11 +176,11 @@ pub fn main() !void { return; } - var bytecode = mod.generateBytecode() catch |err| { + var bytecode = mod.generateBytecode(allocator) catch |err| { return checkVerbose(err); }; - defer bytecode.free(allocator); mod.deinit(); + defer bytecode.free(allocator); if (is_compile) { if (is_dry) { @@ -203,9 +204,9 @@ pub fn main() !void { const vm_alloc = arena.allocator(); if (is_test) { const maybe_count = args.next(); - if (maybe_count == null) return usage("Auto requires a play count."); + if (maybe_count == null) return usage("Test requires a play count."); auto_count = std.fmt.parseInt(u64, maybe_count.?, 10) catch { - return usage("Invalid auto count specified"); + return usage("Invalid test count specified"); }; var i: usize = 0; diff --git a/src/compiler-error.zig b/src/compiler-error.zig index 59d4f23..d86415b 100644 --- a/src/compiler-error.zig +++ b/src/compiler-error.zig @@ -15,7 +15,7 @@ pub const CompilerErr = struct { }; pub const CompilerErrors = struct { - list: std.ArrayListUnmanaged(CompilerErr), + list: std.ArrayList(CompilerErr), allocator: std.mem.Allocator, // used for interpolatedExpressions @@ -24,14 +24,12 @@ pub const CompilerErrors = struct { offset_col: usize = 0, pub fn init(allocator: std.mem.Allocator) CompilerErrors { - return .{ .list = std.ArrayListUnmanaged(CompilerErr){}, .allocator = allocator }; + return .{ .list = std.ArrayList(CompilerErr).init(allocator), .allocator = allocator }; } pub fn add(self: *CompilerErrors, comptime fmt: []const u8, token: Token, severity: CompilerErr.Severity, args: anytype) !void { const msg = try std.fmt.allocPrint(self.allocator, fmt, args); - errdefer self.allocator.free(msg); - - return self.list.append(self.allocator, .{ + try self.list.append(.{ .fmt = msg, .severity = severity, .token = token, @@ -41,11 +39,10 @@ pub const CompilerErrors = struct { for (self.list.items) |err| { self.allocator.free(err.fmt); } - self.list.deinit(self.allocator); - self.* = undefined; + self.list.deinit(); } - pub fn write(self: *CompilerErrors, source: []const u8, writer: anytype) !void { + pub fn write(self: *CompilerErrors, file_path: []const u8, source: []const u8, writer: anytype) !void { while (self.list.popOrNull()) |err| { defer self.allocator.free(err.fmt); const color_prefix = switch (err.severity) { @@ -64,7 +61,7 @@ pub const CompilerErrors = struct { start = @min(start, source.len - 1); end = @min(end, source.len); - try writer.print("type: {s}, line: {}, column_start: {}, column_end: {}, source_start: {}, source_end: {}\n", .{ tok.toString(err.token.token_type), line, column, column + end - start, start, end }); + try writer.print("file: {s}\ntype: {s}, line: {}, column_start: {}, column_end: {}, source_start: {}, source_end: {}\n", .{ file_path, tok.toString(err.token.token_type), line, column, column + end - start, start, end }); var lines = std.mem.splitSequence(u8, source, "\n"); var lineNumber: usize = 1; diff --git a/src/compiler.test.zig b/src/compiler.test.zig index 30f3707..1ec7036 100644 --- a/src/compiler.test.zig +++ b/src/compiler.test.zig @@ -24,9 +24,8 @@ const Module = module.Module; const Compiler = compiler.Compiler; pub fn compileSource(source: []const u8, mod: *Module) !Bytecode { - const file = try allocator.create(module.File); + const file = try mod.arena.allocator().create(module.File); file.* = .{ - .allocator = allocator, .path = "", .name = "", .dir_name = "", @@ -38,17 +37,7 @@ pub fn compileSource(source: []const u8, mod: *Module) !Bytecode { }; mod.entry = file; try mod.includes.putNoClobber(file.path, file); - file.buildTree() catch |err| { - const errWriter = std.io.getStdErr().writer(); - try file.errors.write(source, errWriter); - return err; - }; - - var comp = try Compiler.init(allocator); - defer comp.deinit(); - - try comp.compile(mod); - return comp.bytecode(); + return mod.generateBytecode(allocator); } test "Basic Compile" { @@ -73,15 +62,10 @@ test "Basic Compile" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); for (case.instructions, 0..) |instruction, i| { errdefer std.log.warn("Error on {}", .{i}); @@ -166,15 +150,10 @@ test "Conditionals Compile" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); for (case.instructions, 0..) |instruction, i| { errdefer std.log.warn("Error on instruction:{}", .{i}); @@ -258,15 +237,10 @@ test "Variables" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); for (case.instructions, 0..) |instruction, i| { errdefer std.log.warn("{s} -- {}", .{ case.input, i }); @@ -318,15 +292,10 @@ test "Strings" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); for (case.instructions, 0..) |instruction, i| { errdefer std.log.warn("{}", .{i}); @@ -420,15 +389,10 @@ test "Lists" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); for (case.instructions, 0..) |instruction, i| { errdefer std.log.warn("{}", .{i}); @@ -617,15 +581,10 @@ test "Maps and Sets" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); for (case.instructions, 0..) |instruction, i| { errdefer std.log.warn("Error on: {} \n{s}", .{ i, case.input }); @@ -711,15 +670,10 @@ test "Index" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); for (case.instructions, 0..) |instruction, i| { errdefer std.log.warn("Error on: {}", .{i}); @@ -1111,16 +1065,11 @@ test "Functions" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { errdefer std.log.warn("{s}", .{case.input}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); errdefer bytecode.print(std.debug); for (case.instructions, 0..) |instruction, i| { @@ -1262,16 +1211,11 @@ test "Locals" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { errdefer std.log.warn("{s}", .{case.input}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); for (case.instructions, 0..) |instruction, i| { errdefer std.log.warn("Error on: {}", .{i}); @@ -1325,16 +1269,11 @@ test "Builtin Functions" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { errdefer std.log.warn("{s}", .{case.input}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); errdefer bytecode.print(std.debug); for (case.instructions, 0..) |instruction, i| { @@ -1606,16 +1545,11 @@ test "Closures" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { errdefer std.log.warn("{s}", .{case.input}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); errdefer bytecode.print(std.debug); for (case.instructions, 0..) |instruction, i| { @@ -1684,16 +1618,11 @@ test "Classes" { }, }; - const errWriter = std.io.getStdIn().writer(); inline for (test_cases) |case| { errdefer std.log.warn("{s}", .{case.input}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(case.input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(case.input, mod); defer bytecode.free(allocator); errdefer bytecode.print(std.debug); for (case.instructions, 0..) |instruction, i| { @@ -1715,13 +1644,9 @@ test "Global Jump Error" { \\ => START ; - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - const err = compileSource(input, &mod); - const errWriter = std.io.getStdErr().writer(); - try mod.writeErrors(errWriter); - + const err = compileSource(input, mod); try testing.expect(Compiler.Error.CompilerError == err); } @@ -1737,15 +1662,9 @@ test "Serialize" { \\ const map = Map{1:2.2, 3: 4.4} ; - const errWriter = std.io.getStdIn().writer(); - errdefer std.log.warn("{s}", .{input}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var bytecode = compileSource(input, &mod) catch |err| { - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(input, mod); defer bytecode.free(allocator); // this doesn't need to be a file, but it's nice to sometimes not delete it and inspect it diff --git a/src/compiler.zig b/src/compiler.zig index ef9a4fe..86f3844 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -59,7 +59,7 @@ pub const Compiler = struct { divert_log: std.ArrayList(JumpTree.Entry), visit_tree: VisitTree, - types: std.StringHashMap(*const ast.Statement), + types: std.StringHashMap(ast.Statement), pub const Chunk = struct { instructions: std.ArrayList(u8), @@ -112,7 +112,7 @@ pub const Compiler = struct { .jump_tree = try JumpTree.init(allocator), .visit_tree = try VisitTree.init(allocator), .divert_log = std.ArrayList(JumpTree.Entry).init(allocator), - .types = std.StringHashMap(*const ast.Statement).init(allocator), + .types = std.StringHashMap(ast.Statement).init(allocator), .err = undefined, .module = undefined, }; @@ -263,7 +263,14 @@ pub const Compiler = struct { pub fn precompileJumps(self: *Compiler, stmt: ast.Statement, node: *JumpTree.Node) Error!void { switch (stmt.type) { .include => |i| { + const tmp = self.err; + if (self.module.includes.get(i.path)) |file| { + self.err = &file.errors; + } else { + return self.fail("Unknown include file {s}", stmt.token, .{i.path}); + } for (i.contents) |s| try self.precompileJumps(s, node); + self.err = tmp; }, .bough => |b| { try self.compileVisitDecl(b.name, stmt.token); @@ -320,7 +327,14 @@ pub const Compiler = struct { const token = stmt.token; switch (stmt.type) { .include => |i| { + const tmp = self.err; + if (self.module.includes.get(i.path)) |file| { + self.err = &file.errors; + } else { + return self.fail("Unknown include file {s}", stmt.token, .{i.path}); + } try self.compileBlock(i.contents); + self.err = tmp; }, .@"if" => |i| { try self.compileExpression(i.condition); @@ -468,7 +482,7 @@ pub const Compiler = struct { try self.setSymbol(symbol, token, true); }, .class => |c| { - try self.types.put(c.name, &stmt); + try self.types.putNoClobber(c.name, stmt); try self.enterScope(.local); for (c.fields, 0..) |field, i| { try self.compileExpression(&field); @@ -485,13 +499,12 @@ pub const Compiler = struct { try self.setSymbol(symbol, token, true); }, .@"enum" => |e| { - try self.types.put(e.name, &stmt); - var names = std.ArrayList([]const u8).init(self.allocator); - defer names.deinit(); + try self.types.putNoClobber(e.name, stmt); + var values = try self.allocator.alloc([]const u8, e.values.len); const obj = try self.allocator.create(Value.Obj); - for (e.values) |value| { - try names.append(try self.allocator.dupe(u8, value)); + for (e.values, 0..) |value, i| { + values[i] = try self.allocator.dupe(u8, value); } obj.* = .{ @@ -499,7 +512,7 @@ pub const Compiler = struct { .@"enum" = .{ .is_seq = e.is_seq, .name = try self.allocator.dupe(u8, e.name), - .values = try names.toOwnedSlice(), + .values = values, }, }, }; @@ -1009,7 +1022,7 @@ pub const Compiler = struct { _ = try self.writeInt(u8, @as(u8, @intCast(free_symbols.len)), token); }, .instance => |ins| { - const cls: ?*const ast.Statement = self.types.get(ins.name); + const cls: ?ast.Statement = self.types.get(ins.name); if (cls == null or cls.?.type != .class) return self.fail("Unknown class '{s}'", token, .{ins.name}); for (ins.fields, 0..) |field, i| { if (!arrayOfTypeContains(u8, cls.?.type.class.field_names, ins.field_names[i])) diff --git a/src/export.zig b/src/export.zig index 2465214..dd92790 100644 --- a/src/export.zig +++ b/src/export.zig @@ -107,11 +107,12 @@ fn writeBytecode(path_ptr: [*]const u8, path_length: usize, writer: anytype) voi }; defer alloc.free(full_path); var mod = Module{ + .arena = arena, .allocator = comp_alloc, .entry = undefined, - .includes = std.StringArrayHashMap(*File).init(comp_alloc), + .includes = std.StringHashMap(*File).init(comp_alloc), }; - const file = File.create(comp_alloc, full_path, &mod) catch |err| { + const file = File.create(&mod, full_path) catch |err| { log("Could not create Module File: {s}", .{@errorName(err)}, .err); return; }; diff --git a/src/locale.zig b/src/locale.zig index 5fc898a..88adda1 100644 --- a/src/locale.zig +++ b/src/locale.zig @@ -1,9 +1,11 @@ const std = @import("std"); -const File = @import("module.zig").File; +const module = @import("module.zig"); const ast = @import("ast.zig"); const UUID = @import("utils/uuid.zig").UUID; const parser_test = @import("parser.test.zig"); const Token = @import("token.zig").Token; +const Module = module.Module; +const File = module.File; pub const Locale = struct { const Error = error{ @@ -14,14 +16,15 @@ pub const Locale = struct { // This could be refactored to a fmt with an option to add localization ids // Would need to modify so every node in the ast writes its output pub fn validateFileAtPath(full_path: []const u8, allocator: std.mem.Allocator) ![]const u8 { - var file = try File.create(allocator, full_path, null); - defer file.destroy(); - try file.loadSource(); - try file.buildTree(); - return validateFile(file, allocator); + var mod = try Module.init(allocator, full_path); + mod.allow_includes = false; + defer mod.deinit(); + try mod.entry.loadSource(); + try mod.entry.buildTree(); + return validateFile(mod.entry, allocator); } - fn validateFile(file: *File, allocator: std.mem.Allocator) ![]const u8 { + fn validateFile(file: *module.File, allocator: std.mem.Allocator) ![]const u8 { if (!file.source_loaded) return error.FileSourceNotLoaded; if (!file.tree_loaded) return error.FileTreeNotLoaded; var buf = std.ArrayList(u8).init(allocator); @@ -95,7 +98,7 @@ pub const Locale = struct { if (buf.items[start] == '@') { var end: usize = start; while (buf.items[end] != ' ' and buf.items[end] != 0 and buf.items[end] != '\n') : (end += 1) {} - const len = end - start - 1; + const len = end - start; try buf.replaceRange(start, len, &tmp); count.* += UUID.Size + 1 - len; } else { @@ -106,11 +109,12 @@ pub const Locale = struct { } pub fn exportFileAtPath(full_path: []const u8, writer: anytype, allocator: std.mem.Allocator) !void { - var file = try File.create(allocator, full_path, null); - defer file.destroy(); - try file.loadSource(); - try file.buildTree(); - try exportFile(file, writer); + var mod = try Module.init(allocator, full_path); + mod.allow_includes = false; + defer mod.deinit(); + try mod.entry.loadSource(); + try mod.entry.buildTree(); + try exportFile(mod.entry, writer); } // Basic implementation @@ -141,7 +145,7 @@ pub const Locale = struct { .dialogue => |d| { if (UUID.isEmpty(d.id) or UUID.isAuto(d.id)) return error.InvalidLocazationId; const str = d.content.type.string; - try writer.print("\"{s}\",\"{s}\",\"{s}\",\"{s}\"\n", .{ &d.id, &d.speaker, str.raw, str.value }); + try writer.print("\"{s}\",\"{s}\",\"{s}\",\"{s}\"\n", .{ &d.id, d.speaker orelse "NONE", str.raw, str.value }); }, .@"for" => |f| { for (f.body) |s| try exportStatement(s, writer); @@ -175,7 +179,7 @@ test "Localization Ids" { \\ var added = "one" + "two" \\ const alreadySet = "set" \\ === START { - \\ :Speaker: "Dialogue content withhout id" + \\ :Speaker: "Dialogue content without id" \\ :Speaker: "More dialogue with id"@JUWH59VY-LRIGSPSB \\ fork { \\ ~ "Choice without id" => END @@ -186,9 +190,10 @@ test "Localization Ids" { \\ } ; - const file = try parser_test.parseSource(input); - defer file.destroy(); - const update = try Locale.validateFile(input, file.tree, std.testing.allocator); + const mod = try parser_test.parseSource(input); + defer mod.deinit(); + const file = mod.entry; + const update = try Locale.validateFile(file, std.testing.allocator); defer std.testing.allocator.free(update); std.log.warn("{s}", .{update}); } @@ -219,6 +224,7 @@ test "Update File Localization Ids" { const full_path = try std.fs.cwd().realpathAlloc(std.testing.allocator, file_name); defer std.testing.allocator.free(full_path); const validated = try Locale.validateFileAtPath(full_path, std.testing.allocator); + defer std.testing.allocator.free(validated); try file.seekTo(0); try file.writeAll(validated); @@ -252,8 +258,9 @@ test "Export Localization CSV Tree" { \\ } ; - const file = try parser_test.parseSource(input); - defer file.destroy(); + const mod = try parser_test.parseSource(input); + defer mod.deinit(); + const file = mod.entry; const writer = std.io.getStdOut().writer(); try Locale.exportFile(file, writer); } diff --git a/src/module.zig b/src/module.zig index d43f4af..14aea53 100644 --- a/src/module.zig +++ b/src/module.zig @@ -9,42 +9,48 @@ const std = @import("std"); const fs = std.fs; /// Group of files +/// Manages all allocations and clears all with deinit pub const Module = struct { + arena: std.heap.ArenaAllocator, allocator: std.mem.Allocator, entry: *File, use_loc: bool = false, - includes: std.StringArrayHashMap(*File), + includes: std.StringHashMap(*File), + allow_includes: bool = true, pub fn init(allocator: std.mem.Allocator, entry_path: []const u8) !*Module { - var mod = try allocator.create(Module); + const mod = try initEmpty(allocator); + mod.entry = try mod.addFileAtPath(entry_path); + return mod; + } + + /// Used for initialzing with source directly rather than a file path + pub fn initEmpty(allocator: std.mem.Allocator) !*Module { + const mod = try allocator.create(Module); mod.* = .{ .allocator = allocator, + .arena = std.heap.ArenaAllocator.init(allocator), .entry = undefined, - .includes = std.StringArrayHashMap(*File).init(allocator), + .includes = undefined, }; - const file = try File.create(allocator, entry_path, mod); - mod.entry = file; - try mod.includes.putNoClobber(file.path, file); + mod.includes = std.StringHashMap(*File).init(mod.arena.allocator()); return mod; } - /// Used for initialzing with source direstly rather than a file path - pub fn create(allocator: std.mem.Allocator) Module { - return .{ - .allocator = allocator, - .entry = undefined, - .includes = std.StringArrayHashMap(*File).init(allocator), - }; + pub fn addFileAtPath(self: *Module, path: []const u8) !*File { + const file = try File.create(self, path); + try self.includes.putNoClobber(file.path, file); + return file; } - pub fn generateBytecode(self: *Module) !Bytecode { + pub fn generateBytecode(self: *Module, allocator: std.mem.Allocator) !Bytecode { try self.entry.loadSource(); self.entry.buildTree() catch |err| { try self.writeErrors(std.io.getStdErr().writer()); return err; }; - var compiler = try Compiler.init(self.allocator); + var compiler = try Compiler.init(allocator); compiler.use_loc = self.use_loc; defer compiler.deinit(); @@ -59,27 +65,18 @@ pub const Module = struct { var it = self.includes.iterator(); while (it.next()) |kvp| { var file = kvp.value_ptr.*; - try file.errors.write(file.source, writer); + if (file.source_loaded) try file.errors.write(file.path, file.source, writer); } } pub fn deinit(self: *Module) void { var it = self.includes.iterator(); - const maybe_first = it.next(); - // TODO Refactor this. - // The file.tree arean allocator deinits all other trees - // that have been included, so we only deinit the first tree - if (maybe_first) |first| { - first.value_ptr.*.destroy(); - self.allocator.free(first.key_ptr.*); - } - while (it.next()) |kvp| { - kvp.value_ptr.*.tree_loaded = false; - kvp.value_ptr.*.destroy(); - self.allocator.free(kvp.key_ptr.*); + kvp.value_ptr.*.errors.deinit(); } - self.includes.deinit(); + var arena = self.arena; + arena.deinit(); + self.allocator.destroy(self); } }; @@ -88,7 +85,7 @@ pub const File = struct { name: []const u8, dir_name: []const u8, dir: fs.Dir, - module: ?*Module = null, + module: *Module, errors: CompilerErrors, source: []const u8 = undefined, @@ -98,30 +95,20 @@ pub const File = struct { loc: []const u8 = undefined, loc_loaded: bool = false, - allocator: std.mem.Allocator, - - pub fn create(allocator: std.mem.Allocator, path: []const u8, module: ?*Module) !*File { + pub fn create(module: *Module, path: []const u8) !*File { + const allocator = module.arena.allocator(); const file = try allocator.create(File); file.* = .{ - .allocator = allocator, - .path = path, + .path = try allocator.dupe(u8, path), .name = fs.path.basename(path), .dir_name = fs.path.dirname(path) orelse "", .dir = try fs.openDirAbsolute(fs.path.dirname(path) orelse path, .{}), .module = module, - .errors = CompilerErrors.init(allocator), + .errors = CompilerErrors.init(module.arena.allocator()), }; return file; } - pub fn destroy(self: *File) void { - if (self.source_loaded) self.allocator.free(self.source); - if (self.tree_loaded) self.tree.deinit(); - if (self.loc_loaded) self.allocator.free(self.loc); - self.errors.deinit(); - self.allocator.destroy(self); - } - pub fn loadSource(self: *File) !void { if (self.source_loaded) return; const file = try fs.openFileAbsolute(self.path, .{}); @@ -129,60 +116,56 @@ pub const File = struct { const stat = try file.stat(); const file_size = stat.size; - const source = try self.allocator.alloc(u8, file_size); + const source = try self.module.arena.allocator().alloc(u8, file_size); try file.reader().readNoEof(source); self.source = source; self.source_loaded = true; } pub fn unloadSource(self: *File) void { - if (!self.source_loaded) return; - self.allocator.free(self.source); + if (self.source_loaded) self.module.arena.allocator().free(self.source); self.source_loaded = false; } pub fn loadLoc(self: *File) !void { if (self.loc_loaded) return; - var csv_path = try self.allocator.alloc(u8, self.path.len + 4); - defer self.allocator.free(csv_path); + const allocator = self.module.arena.allocator(); + var csv_path = try allocator.alloc(u8, self.path.len + 4); + defer allocator.free(csv_path); @memcpy(csv_path[0..self.path.len], self.path); @memcpy(csv_path[self.path.len..], ".csv"); - defer self.allocator.free(csv_path); + defer allocator.free(csv_path); const file = fs.openFileAbsolute(csv_path, .{}) catch return error.FileNotFound; defer file.close(); const stat = try file.stat(); const file_size = stat.size; - const source = try self.allocator.alloc(u8, file_size); + const source = try allocator.alloc(u8, file_size); try file.reader().readNoEof(source); self.loc = source; self.loc_loaded = true; } pub fn unloadLoc(self: *File) void { - if (!self.loc_loaded) return; - self.allocator.free(self.loc); + if (self.loc_loaded) self.module.arena.allocator().free(self.loc); self.loc_loaded = false; - self.loc = undefined; } pub fn buildTree(self: *File) !void { if (self.tree_loaded) return; if (!self.source_loaded) return error.ParserError; var lexer = Lexer.init(self.source, 0); - var arena = std.heap.ArenaAllocator.init(self.allocator); - errdefer arena.deinit(); + const alloc = self.module.arena.allocator(); var parser = Parser{ .current_token = lexer.next(), .peek_token = lexer.next(), - .arena = arena.state, - .allocator = arena.allocator(), + .allocator = alloc, .lexer = &lexer, .file = self, }; - var nodes = std.ArrayList(Statement).init(parser.allocator); + var nodes = std.ArrayList(Statement).init(alloc); errdefer nodes.deinit(); while (!parser.currentIs(.eof)) : (parser.next()) { @@ -191,10 +174,12 @@ pub const File = struct { self.tree = Tree{ .root = try nodes.toOwnedSlice(), - .arena = arena.state, - .allocator = self.allocator, - .source = self.source, }; self.tree_loaded = true; } + + pub fn unloadTree(self: *File) void { + if (self.tree_loaded) self.tree.deinit(); + self.tree_loaded = false; + } }; diff --git a/src/parser.test.zig b/src/parser.test.zig index 255ba5f..7e59a83 100644 --- a/src/parser.test.zig +++ b/src/parser.test.zig @@ -9,27 +9,27 @@ const File = module.File; const testing = std.testing; const allocator = testing.allocator; -pub fn parseSource(source: []const u8) !*File { - const file = try allocator.create(File); - errdefer file.destroy(); - errdefer file.source_loaded = false; +pub fn parseSource(source: []const u8) !*Module { + const mod = try Module.initEmpty(allocator); + errdefer mod.deinit(); + const file = try mod.arena.allocator().create(File); file.* = .{ - .allocator = allocator, + .module = mod, .path = "", .name = "", .dir_name = "", .dir = undefined, .source = source, .source_loaded = true, - .errors = Errors.init(allocator), + .errors = Errors.init(mod.arena.allocator()), }; + mod.entry = file; file.buildTree() catch |err| { const errWriter = std.io.getStdErr().writer(); - try file.errors.write(source, errWriter); + try file.errors.write("N/A", source, errWriter); return err; }; - file.source_loaded = false; - return file; + return mod; } test "Parse Include" { @@ -54,8 +54,9 @@ test "Parse Declaration" { \\ const str = "string value" ; - const file = try parseSource(t); - defer file.destroy(); + const mod = try parseSource(t); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; try testing.expect(tree.root.len == 7); try testing.expect(!tree.root[0].type.variable.is_mutable); @@ -84,8 +85,9 @@ test "Parse Function Declaration" { \\ return result \\ } ; - const file = try parseSource(t); - defer file.destroy(); + const mod = try parseSource(t); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; try testing.expect(tree.root.len == 2); try testing.expect(!tree.root[0].type.variable.is_mutable); @@ -99,8 +101,9 @@ test "Parse Function Arguments" { \\ const sum = |x, y| return x + y \\ sum(1, 2) + sum(3, 4) ; - const file = try parseSource(t); - defer file.destroy(); + const mod = try parseSource(t); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; try testing.expect(tree.root.len == 2); try testing.expect(tree.root[1].type.expression.type.binary.operator == .add); @@ -121,8 +124,9 @@ test "Parse Enums" { \\ } \\ var value = Test.one ; - const file = try parseSource(t); - defer file.destroy(); + const mod = try parseSource(t); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; try testing.expect(tree.root.len == 2); const e = tree.root[0].type.@"enum"; @@ -144,8 +148,9 @@ test "Parse Iterable Types" { }; inline for (test_cases) |case| { - const file = try parseSource(case.input); - defer file.destroy(); + const mod = try parseSource(case.input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; const node = tree.root[0].type.variable; try testing.expectEqualStrings(case.id, node.name); @@ -167,8 +172,9 @@ test "Parse Empty Iterable Types" { \\ const emptyMap = Map{} \\ const emptySet = Set{} ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; var decl = tree.root[0].type.variable; try testing.expectEqualStrings("emptyMap", decl.name); @@ -183,8 +189,9 @@ test "Parse Nested Iterable Types" { const input = \\ List{List{1,2}} ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; try testing.expect(tree.root[0].type.expression.type == .list); @@ -198,8 +205,9 @@ test "Parse Extern" { }; inline for (test_cases) |case| { - const file = try parseSource(case.input); - defer file.destroy(); + const mod = try parseSource(case.input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; const decl = tree.root[0].type.variable; try testing.expectEqualStrings(case.id, decl.name); @@ -221,8 +229,9 @@ test "Parse Enum" { \\ } \\ const val = En.three ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; var decl = tree.root[0].type.@"enum"; try testing.expectEqualStrings("E", decl.name); @@ -254,8 +263,9 @@ test "Parse If" { \\ value = 5 \\ } ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; var if_stmt = tree.root[1].type.@"if"; @@ -279,8 +289,9 @@ test "Parse Call expression" { const input = \\ add(1, 2 * 3, 4 + 5) ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; const call = tree.root[0].type.expression.type.call; @@ -301,8 +312,9 @@ test "Parse For loop" { \\ } ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; var loop = tree.root[0].type.@"for"; @@ -323,8 +335,9 @@ test "Parse While loop" { const input = \\ while x < y { x }" ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; const loop = tree.root[0].type.@"while"; @@ -336,8 +349,9 @@ test "Parse Indexing" { const input = \\ test.other.third.final ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; var idx = tree.root[0].type.expression.type.indexer; @@ -356,8 +370,9 @@ test "Parse Bough" { \\ :Speaker: "Text goes here" # tagline #tagother#taglast \\ } ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; const bough = tree.root[0].type.bough; try testing.expectEqualStrings(bough.name, "BOUGH"); @@ -376,8 +391,9 @@ test "Parse No Speaker" { \\ :: "Text goes here" \\ } ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; const line = tree.root[0].type.bough.body[0].type.dialogue; try testing.expect(line.speaker == null); @@ -389,8 +405,9 @@ test "Parse divert" { \\ === BOUGH {} \\ => BOUGH ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; const divert = tree.root[1].type.divert.path; try testing.expectEqualStrings("BOUGH", divert[0]); @@ -408,8 +425,9 @@ test "Parse Forks" { \\ } \\ === END {} ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; const fork = tree.root[0].type.bough.body[0].type.fork; @@ -432,8 +450,9 @@ test "Parse Inline Code" { \\ :Speaker: "{sayHello()}, how are you?" \\ } ; - const file = try parseSource(input); - defer file.destroy(); + const mod = try parseSource(input); + defer mod.deinit(); + const file = mod.entry; const tree = file.tree; const dialogue = tree.root[0].type.bough.body[0].type.dialogue; const string = dialogue.content.type.string; diff --git a/src/parser.zig b/src/parser.zig index 827588a..60068fe 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1,7 +1,6 @@ const std = @import("std"); const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; -const Arena = std.heap.ArenaAllocator; const testing = std.testing; const Lexer = @import("lexer.zig").Lexer; const tok = @import("token.zig"); @@ -9,7 +8,6 @@ const ast = @import("ast.zig"); const File = @import("module.zig").File; const Errors = @import("compiler-error.zig").CompilerErrors; const UUID = @import("utils/uuid.zig").UUID; -const Tree = ast.Tree; const Statement = ast.Statement; const Expression = ast.Expression; const TokenType = tok.TokenType; @@ -51,61 +49,9 @@ fn findPrecedence(token_type: TokenType) Precedence { }; } -/// Used for parsing files -/// Required if topi file has "include" statements -pub fn parseFile(allocator: Allocator, dir: std.fs.Dir, path: []const u8, err: *Errors) Parser.Error!Tree { - const file = dir.openFile(path, .{}) catch |e| { - err.add("Could not open file {s}: {}", undefined, .err, .{ path, e }) catch {}; - return Parser.Error.ParserError; - }; - defer file.close(); - - const stat = file.stat() catch |e| { - err.add("Could not read file stats {s}: {}", undefined, .err, .{ path, e }) catch {}; - return Parser.Error.ParserError; - }; - const file_size = stat.size; - const source = try allocator.alloc(u8, file_size); - defer allocator.free(source); - file.reader().readNoEof(source) catch |e| { - err.add("Could not read file {s}: {}", undefined, .err, .{ path, e }) catch {}; - return Parser.Error.ParserError; - }; - errdefer err.write(source, std.io.getStdErr().writer()) catch {}; - var lexer = Lexer.init(source); - var arena = std.heap.ArenaAllocator.init(allocator); - errdefer arena.deinit(); - - var parser = Parser{ - .current_token = lexer.next(), - .peek_token = lexer.next(), - .arena = arena.state, - .allocator = arena.allocator(), - .lexer = &lexer, - .directory = dir, - .source = source, - .err = err, - }; - - var nodes = ArrayList(Statement).init(parser.allocator); - errdefer nodes.deinit(); - - while (!parser.currentIs(.eof)) : (parser.next()) { - try nodes.append(try parser.statement()); - } - - return Tree{ - .root = try nodes.toOwnedSlice(), - .arena = arena.state, - .allocator = allocator, - .source = source, - }; -} - pub const Parser = struct { current_token: Token, peek_token: Token, - arena: std.heap.ArenaAllocator.State, allocator: Allocator, lexer: *Lexer, file: *File, @@ -124,7 +70,7 @@ pub const Parser = struct { interpolated: []const u8, }; - fn allocate(self: Parser, value: anytype) !*@TypeOf(value) { + inline fn allocate(self: Parser, value: anytype) !*@TypeOf(value) { const T = @TypeOf(value); std.debug.assert(@typeInfo(T) != .Pointer); const ptr = try self.allocator.create(T); @@ -133,7 +79,7 @@ pub const Parser = struct { return ptr; } - fn fail(self: *Parser, comptime msg: []const u8, token: Token, args: anytype) Error { + inline fn fail(self: *Parser, comptime msg: []const u8, token: Token, args: anytype) Error { try self.file.errors.add(msg, token, .err, args); return Error.ParserError; } @@ -196,11 +142,9 @@ pub const Parser = struct { fn includeStatement(self: *Parser) Error!Statement { const start = self.current_token; self.next(); - const path = try self.getStringValue(); - if (std.mem.eql(u8, self.file.path, "")) return self.fail("File Path not set", self.current_token, .{}); - const full_path = try std.fs.path.resolve(self.allocator, &.{ self.file.dir_name, path }); - if (self.file.module == null or self.file.module.?.includes.contains(full_path)) { + const path = self.file.source[self.current_token.start..self.current_token.end]; + if (!self.file.module.allow_includes) { return .{ .token = start, .type = .{ @@ -212,23 +156,37 @@ pub const Parser = struct { }; } - const file = File.create(self.allocator, full_path, self.file.module) catch |err| { + if (std.mem.eql(u8, self.file.path, "")) return self.fail("Cannot include as current file path not set", start, .{}); + const full_path = try std.fs.path.resolve(self.allocator, &.{ self.file.dir_name, path }); + defer self.allocator.free(full_path); + + if (self.file.module.includes.getKey(full_path)) |k| { + return .{ + .token = start, + .type = .{ + .include = .{ + .path = k, + .contents = &.{}, + }, + }, + }; + } + + const file = self.file.module.addFileAtPath(full_path) catch |err| { return self.fail("Could not create include file '{s}': {}", self.current_token, .{ path, err }); }; file.loadSource() catch |err| { return self.fail("Could not load include file '{s}': {}", self.current_token, .{ path, err }); }; - file.buildTree() catch |err| { return self.fail("Could not build include file tree '{s}': {}", self.current_token, .{ path, err }); }; - try self.file.module.?.includes.putNoClobber(full_path, file); return .{ .token = start, .type = .{ .include = .{ - .path = path, + .path = file.path, .contents = file.tree.root, }, }, @@ -284,8 +242,7 @@ pub const Parser = struct { var values = std.ArrayList([]const u8).init(self.allocator); errdefer values.deinit(); while (!self.currentIs(.right_brace)) { - try values.append(try self.getStringValue()); - self.next(); + try values.append(try self.consumeIdentifier()); if (self.currentIs(.comma)) self.next(); } return .{ @@ -622,7 +579,6 @@ pub const Parser = struct { var parser = Parser{ .current_token = lexer.next(), .peek_token = lexer.next(), - .arena = self.arena, .allocator = self.allocator, .lexer = &lexer, .file = self.file, @@ -1079,7 +1035,7 @@ pub const Parser = struct { fn switchProng(self: *Parser) Error!Statement { const start_token = self.current_token; var values = std.ArrayList(Expression).init(self.allocator); - defer values.deinit(); + errdefer values.deinit(); const is_else = start_token.token_type == .@"else"; if (!is_else) { try values.append(try self.expression(.lowest)); diff --git a/src/vm.test.zig b/src/vm.test.zig index bd56b63..37f64ac 100644 --- a/src/vm.test.zig +++ b/src/vm.test.zig @@ -59,11 +59,7 @@ pub const TestRunner = struct { var test_runner = TestRunner.init(); pub fn initTestVm(source: []const u8, mod: *Module, debug: bool) !Vm { - var bytecode = compileSource(source, mod) catch |err| { - const errWriter = std.io.getStdErr().writer(); - try mod.writeErrors(errWriter); - return err; - }; + var bytecode = try compileSource(source, mod); errdefer bytecode.free(allocator); if (debug) { bytecode.print(std.debug); @@ -93,10 +89,9 @@ test "Basics" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -122,10 +117,9 @@ test "Conditionals" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -151,10 +145,9 @@ test "Variables" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -170,10 +163,9 @@ test "Constant Variables" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - const err = initTestVm(case, &mod, false); + const err = initTestVm(case, mod, false); try testing.expectError(Vm.Error.CompilerError, err); } } @@ -197,11 +189,10 @@ test "Strings" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); errdefer std.log.err("Error on: {s}", .{case.input}); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -222,10 +213,9 @@ test "Lists" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -246,10 +236,9 @@ test "Maps" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -275,10 +264,9 @@ test "Sets" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -338,10 +326,9 @@ test "Index" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -403,10 +390,9 @@ test "Functions" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -475,10 +461,9 @@ test "Locals" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -539,10 +524,9 @@ test "Function Arguments" { , .value = 50.0 }, }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -566,10 +550,9 @@ test "Builtin Functions" { }, }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { @@ -640,10 +623,9 @@ test "Closures" { }, }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -729,10 +711,9 @@ test "Loops" { , .value = 15 }, }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { @@ -751,10 +732,9 @@ test "Classes" { \\ } \\ assert(Test.value == 0, "Test.value == 0") ; - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(input, &mod, false); + var vm = try initTestVm(input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -770,10 +750,9 @@ test "Class Runtime Error" { , }; inline for (tests) |input| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(input, &mod, false); + var vm = try initTestVm(input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); const err = vm.interpret(); @@ -803,10 +782,9 @@ test "Class Compile Error" { \\ var test = new Test{} }; inline for (tests) |input| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - const err = initTestVm(input, &mod, false); + const err = initTestVm(input, mod, false); try testing.expectError(Vm.Error.CompilerError, err); } } @@ -841,10 +819,9 @@ test "Instance" { \\ test.value = Test.value \\ assert(test.value == 0, "test.value == 0") ; - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(input, &mod, false); + var vm = try initTestVm(input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { @@ -889,10 +866,9 @@ test "Enums" { \\ assert(quest == Quest.Complete, "Quest is not Complete") ; - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(input, &mod, false); + var vm = try initTestVm(input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { @@ -929,10 +905,9 @@ test "Enum Error" { }; inline for (tests) |input| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - const err = initTestVm(input, &mod, false); + const err = initTestVm(input, mod, false); try testing.expectError(Vm.Error.CompilerError, err); } } @@ -1012,11 +987,10 @@ test "Boughs" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; std.debug.print("\n======\n", .{}); - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -1042,10 +1016,9 @@ test "Bough Loops" { }, }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { @@ -1127,10 +1100,9 @@ test "Bough Functions" { inline for (test_cases) |case| { std.debug.print("\n======\n", .{}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -1179,10 +1151,9 @@ test "Forks" { inline for (test_cases) |case| { std.debug.print("\n======\n", .{}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -1273,11 +1244,10 @@ test "Visits" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; std.debug.print("\n======\n", .{}); - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -1344,10 +1314,9 @@ test "Jump Backups" { inline for (test_cases) |case| { std.debug.print("\n======\n", .{}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -1392,10 +1361,9 @@ test "Jump Code" { inline for (test_cases) |case| { std.debug.print("\n======\n", .{}); - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { @@ -1510,10 +1478,9 @@ test "Switch" { }, }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { @@ -1536,10 +1503,9 @@ test "Externs and Subscribers" { }; inline for (test_cases) |case| { - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(case.input, &mod, false); + var vm = try initTestVm(case.input, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); const Listener = struct { @@ -1578,10 +1544,9 @@ test "Save and Load State" { ; const alloc = testing.allocator; - var mod = Module.create(allocator); + var mod = try Module.initEmpty(allocator); defer mod.deinit(); - defer mod.entry.source_loaded = false; - var vm = try initTestVm(test_case, &mod, false); + var vm = try initTestVm(test_case, mod, false); defer vm.deinit(); defer vm.bytecode.free(testing.allocator); try vm.interpret(); @@ -1597,17 +1562,68 @@ test "Save and Load State" { \\ var test = "t" ; - var mod2 = Module.create(allocator); + var mod2 = try Module.initEmpty(allocator); defer mod2.deinit(); - defer mod2.entry.source_loaded = false; - var vm2 = try initTestVm(second_case, &mod2, false); + var vm2 = try initTestVm(second_case, mod2, false); defer vm2.deinit(); defer vm2.bytecode.free(testing.allocator); try State.deserialize(&vm2, data.items); - // try vm2.loadState(&save); try testing.expectEqual(vm2.globals[0].number, 1); try testing.expectEqualSlices(u8, vm2.globals[1].obj.data.list.items[0].obj.data.list.items[0].obj.data.string, "changed"); try testing.expectEqual(vm2.globals[2].obj.data.instance.fields[1].number, 2); try vm2.interpret(); try testing.expectEqual(vm2.globals[0].number, 6); } + +test "Includes" { + const main_contents = + \\ include "./test1.topi" + \\ var m = Test.Main + \\ assert(m == Test.Main, "m == Test.Main") + \\ assert(t1 == Test.One, "t1 == Test.One") + \\ assert(t2 == Test.Two, "t2 == Test.Two") + ; + const test1_contents = + \\ include "./test2.topi" + \\ var t1 = Test.One + ; + const test2_contents = + \\ enum Test = { + \\ Main, + \\ One, + \\ Two + \\ } + \\ var t2 = Test.Two + ; + + const cwd = std.fs.cwd(); + const main_file = try cwd.createFile("main.topi", .{ .read = true }); + const test1_file = try cwd.createFile("test1.topi", .{ .read = true }); + const test2_file = try cwd.createFile("test2.topi", .{ .read = true }); + defer cwd.deleteFile("main.topi") catch {}; + defer cwd.deleteFile("test1.topi") catch {}; + defer cwd.deleteFile("test2.topi") catch {}; + + try main_file.writeAll(main_contents); + try test1_file.writeAll(test1_contents); + try test2_file.writeAll(test2_contents); + main_file.close(); + test1_file.close(); + test2_file.close(); + + const err_writer = std.io.getStdIn().writer(); + const entry_path = try std.fs.cwd().realpathAlloc(std.testing.allocator, "main.topi"); + defer std.testing.allocator.free(entry_path); + var mod = try Module.init(std.testing.allocator, entry_path); + defer mod.deinit(); + + const bytecode = mod.generateBytecode(std.testing.allocator) catch |err| { + try mod.writeErrors(err_writer); + return err; + }; + defer bytecode.free(std.testing.allocator); + + var vm = try Vm.init(std.testing.allocator, bytecode, &test_runner.runner); + defer vm.deinit(); + try vm.interpret(); +}