From e9e5165345636ce51ae7df493c671bf662cc18d0 Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Sat, 24 Feb 2024 11:46:27 -0600 Subject: [PATCH] Implement basic tests and error unions --- bootstrap/Compilation.zig | 1412 ++++++++++++++--- bootstrap/backend/llvm.zig | 143 +- bootstrap/data_structures.zig | 13 +- bootstrap/frontend/lexer.zig | 12 +- bootstrap/frontend/parser.zig | 172 +- lib/std/builtin.nat | 2 +- lib/std/start.nat | 7 +- lib/test_runner.nat | 6 + test/build/first/src/main.nat | 3 - test/build/{first => first_build}/.gitignore | 0 test/build/{first => first_build}/build.nat | 11 +- test/build/first_build/src/main.nat | 2 + test/build/link_libc/build.nat | 9 +- test/build/link_libc/src/main.nat | 3 +- test/standalone/add_sub/main.nat | 10 +- test/standalone/and/main.nat | 9 +- test/standalone/array_pointer_store/main.nat | 11 +- .../array_pointer_store_function/main.nat | 10 +- test/standalone/array_store/main.nat | 12 +- test/standalone/assert/main.nat | 3 +- test/standalone/bit_struct/main.nat | 3 +- test/standalone/bit_struct_call/main.nat | 4 +- test/standalone/break/main.nat | 9 +- .../conditional_compilation_basic/main.nat | 9 +- test/standalone/div/main.nat | 9 +- test/standalone/else_if/main.nat | 15 +- test/standalone/first/main.nat | 4 +- test/standalone/foreach/main.nat | 10 +- test/standalone/foreach_slice/main.nat | 11 +- test/standalone/fork/main.nat | 8 +- test/standalone/fork_exec/main.nat | 11 +- test/standalone/function_pointer/main.nat | 10 +- .../function_pointer_struct/main.nat | 10 +- test/standalone/hello_world/main.nat | 3 +- test/standalone/if/main.nat | 13 +- test/standalone/if_not_else/main.nat | 15 +- test/standalone/imul/main.nat | 13 +- test/standalone/loop_break/main.nat | 10 +- test/standalone/loop_return/main.nat | 14 +- .../standalone/loop_return_only_else/main.nat | 9 +- test/standalone/nested_if/main.nat | 10 +- test/standalone/null_terminated/main.nat | 3 +- test/standalone/optional_wrap/main.nat | 4 +- test/standalone/or/main.nat | 10 +- test/standalone/self_exe_path/main.nat | 10 +- test/standalone/shifts/main.nat | 10 +- test/standalone/simple_bool/main.nat | 10 +- test/standalone/size/main.nat | 13 +- test/standalone/slice/main.nat | 10 +- test/standalone/slice2/main.nat | 11 +- test/standalone/slice_expression/main.nat | 3 +- test/standalone/slice_len/main.nat | 10 +- test/standalone/stack/main.nat | 9 +- test/standalone/virtual_memory/main.nat | 11 +- test/standalone/xor/main.nat | 10 +- test/standalone/zero_terminated/main.nat | 3 +- test/tests/first_test/main.nat | 2 + test/tests/main.nat | 2 - 58 files changed, 1668 insertions(+), 513 deletions(-) create mode 100644 lib/test_runner.nat delete mode 100644 test/build/first/src/main.nat rename test/build/{first => first_build}/.gitignore (100%) rename test/build/{first => first_build}/build.nat (73%) create mode 100644 test/build/first_build/src/main.nat create mode 100644 test/tests/first_test/main.nat delete mode 100644 test/tests/main.nat diff --git a/bootstrap/Compilation.zig b/bootstrap/Compilation.zig index 2e3d94e..fb7dd11 100644 --- a/bootstrap/Compilation.zig +++ b/bootstrap/Compilation.zig @@ -36,14 +36,12 @@ fn reportUnterminatedArgumentError(string: []const u8) noreturn { std.debug.panic("Unterminated argument: {s}", .{string}); } -const Error = struct{ +const Error = struct { message: []const u8, node: Node.Index, }; - - -pub fn createContext(allocator: Allocator) !*const Context{ +pub fn createContext(allocator: Allocator) !*const Context { const context: *Context = try allocator.create(Context); const self_exe_path = try std.fs.selfExePathAlloc(allocator); @@ -92,7 +90,7 @@ pub fn compileBuildExecutable(context: *const Context, arguments: [][:0]u8) !voi else => false, }; if (!success) { - std.debug.print("The following command terminated with failure ({s}): {s}\n", .{@tagName(result.term), argv}); + std.debug.print("The following command terminated with failure ({s}): {s}\n", .{ @tagName(result.term), argv }); if (result.stdout.len > 0) { std.debug.print("STDOUT:\n{s}\n", .{result.stdout}); } @@ -103,7 +101,7 @@ pub fn compileBuildExecutable(context: *const Context, arguments: [][:0]u8) !voi } } -const ExecutableOptions = struct{ +const ExecutableOptions = struct { is_test: bool, }; @@ -413,6 +411,7 @@ fn getTypeBitSize(ty: *Type, unit: *Unit) u32 { break :b getTypeBitSize(backing_type, unit); }, .slice => 2 * @bitSizeOf(usize), + .void => 0, else => |t| @panic(@tagName(t)), }; } @@ -430,6 +429,10 @@ pub const Type = union(enum) { pointer: Type.Pointer, slice: Type.Slice, array: Type.Array, + @"union": Type.Union.Index, + @"error": Type.Error.Index, + error_union: Type.Error.Union, + error_set: Type.Error.Set.Index, pub fn getBitSize(ty: *Type, unit: *Unit) u32 { return getTypeBitSize(ty, unit); @@ -447,6 +450,7 @@ pub const Type = union(enum) { return switch (ty.*) { .@"struct" => |struct_index| &unit.structs.get(struct_index).scope.scope, .@"enum" => |enum_index| &unit.enums.get(enum_index).scope.scope, + .@"error" => |error_index| &unit.errors.get(error_index).scope.scope, else => |t| @panic(@tagName(t)), }; } @@ -462,6 +466,45 @@ pub const Type = union(enum) { }, }; + const Error = struct { + fields: ArrayList(Type.Error.Field.Index) = .{}, + scope: Debug.Scope.Global, + backing_type: Type.Index, + + pub const List = BlockList(@This(), enum {}); + pub usingnamespace @This().List.Index; + + const Field = struct { + name: u32, + type: Type.Index, + value: usize, + + pub const List = BlockList(@This(), enum {}); + pub usingnamespace @This().List.Index; + }; + + const Set = struct { + values: ArrayList(Type.Index) = .{}, // Empty means all errors + pub const List = BlockList(@This(), enum {}); + pub usingnamespace @This().List.Index; + }; + + const Union = struct { + @"error": Type.Index, + type: Type.Index, + pub const List = BlockList(@This(), enum {}); + pub usingnamespace @This().List.Index; + }; + }; + + const Union = struct { + fields: ArrayList(Struct.Field.Index) = .{}, + scope: Debug.Scope.Global, + is_tagged: bool, + pub const List = BlockList(@This(), enum {}); + pub usingnamespace @This().List.Index; + }; + const Integer = struct { bit_count: u16, signedness: Signedness, @@ -780,7 +823,7 @@ pub const Instruction = union(enum) { source: V, }; - const AddOverflow = struct{ + const AddOverflow = struct { left: V, right: V, type: Type.Index, @@ -892,6 +935,7 @@ pub const V = struct { constant_int: ConstantInt, function_declaration: Type.Index, enum_value: Enum.Field.Index, + error_value: Type.Error.Field.Index, function_definition: Function.Definition.Index, global: *Debug.Declaration.Global, constant_backed_struct: u64, @@ -902,8 +946,9 @@ pub const V = struct { null_pointer, pub const ConstantSlice = struct { - ptr: *Debug.Declaration.Global, - len: usize, + array: *Debug.Declaration.Global, + start: usize, + end: usize, type: Type.Index, pub const List = BlockList(@This(), enum {}); @@ -1092,10 +1137,12 @@ pub const Debug = struct { loaded_into_memory, lexed, parsed, + analyzing, + analyzed, }; pub fn getPath(file: *File, allocator: Allocator) ![]const u8 { - return try std.mem.concat(allocator, u8, &.{ file.package.directory.path, "/", file.relative_path}); + return try std.mem.concat(allocator, u8, &.{ file.package.directory.path, "/", file.relative_path }); } }; }; @@ -1188,10 +1235,16 @@ pub const Builder = struct { } } - fn processStringLiteral(builder: *Builder, unit: *Unit, context: *const Context, token_index: Token.Index) !*Debug.Declaration.Global { + fn processStringLiteralFromToken(builder: *Builder, unit: *Unit, context: *const Context, token_index: Token.Index) !*Debug.Declaration.Global { const string = try unit.fixupStringLiteral(context, token_index); + const token_debug_info = builder.getTokenDebugInfo(unit, token_index); + + return try builder.processStringLiteralFromStringAndDebugInfo(unit, context, string, token_debug_info); + } + + fn processStringLiteralFromStringAndDebugInfo(builder: *Builder, unit: *Unit, context: *const Context, string: [:0]const u8, debug_info: TokenDebugInfo) !*Debug.Declaration.Global { const possible_id = unit.string_literal_values.size; - const hash = data_structures.hash(string); + const hash = try unit.processIdentifier(context, string); const gop = try unit.string_literal_globals.getOrPut(context.allocator, hash); if (gop.found_existing) { @@ -1200,7 +1253,6 @@ pub const Builder = struct { const string_name = try std.fmt.allocPrint(context.allocator, "__anon_str_{}", .{possible_id}); const identifier = try unit.processIdentifier(context, string_name); try unit.string_literal_values.putNoClobber(context.allocator, hash, string); - const token_debug_info = builder.getTokenDebugInfo(unit, token_index); const string_global_index = try unit.global_declarations.append(context.allocator, .{ .declaration = .{ @@ -1224,8 +1276,8 @@ pub const Builder = struct { break :blk array_type; }, - .line = token_debug_info.line, - .column = token_debug_info.column, + .line = debug_info.line, + .column = debug_info.column, .mutability = .@"const", .kind = .global, }, @@ -1367,7 +1419,7 @@ pub const Builder = struct { if (destination_pointer.type == source_pointer.type) { if (destination_pointer.mutability == source_pointer.mutability) { if (destination_pointer.nullable != source_pointer.nullable) { - std.debug.print("Dst: {} Src: {}\n",.{destination_pointer.nullable, source_pointer.nullable}); + std.debug.print("Dst: {} Src: {}\n", .{ destination_pointer.nullable, source_pointer.nullable }); if (destination_pointer.nullable) { assert(destination_pointer.termination != source_pointer.termination); unreachable; @@ -1607,7 +1659,7 @@ pub const Builder = struct { assert(argument_node_list.len == 2); const left = try builder.resolveRuntimeValue(unit, context, type_expect, argument_node_list[0], .right); const right_type_expect = switch (type_expect) { - .none => Type.Expect { .type = left.type }, + .none => Type.Expect{ .type = left.type }, else => type_expect, }; const right = try builder.resolveRuntimeValue(unit, context, right_type_expect, argument_node_list[1], .right); @@ -1757,6 +1809,7 @@ pub const Builder = struct { const file = unit.files.get(file_index); assert(file.status == .parsed); + file.status = .analyzing; const previous_file = builder.current_file; builder.current_file = file_index; @@ -1769,6 +1822,7 @@ pub const Builder = struct { // File type already assigned _ = try builder.resolveContainerType(unit, context, main_node_index, .@"struct", null); + file.status = .analyzed; assert(file.type != .null); } @@ -1825,6 +1879,7 @@ pub const Builder = struct { } fn appendInstruction(builder: *Builder, unit: *Unit, context: *const Context, instruction_index: Instruction.Index) !void { + // if (@intFromEnum(instruction_index) == 366) @breakpoint(); switch (unit.instructions.get(instruction_index).*) { .extract_value => |extract_value| switch (unit.types.get(extract_value.expression.type).*) { .pointer => unreachable, @@ -1977,18 +2032,20 @@ pub const Builder = struct { const string_literal_bytes = try unit.fixupStringLiteral(context, argument_node.token); const import_file = try unit.importFile(context, builder.current_file, string_literal_bytes); + const file_index = import_file.file.index; + const file = unit.files.get(file_index); - if (import_file.file.is_new) { - const new_file_index = import_file.file.index; - try unit.generateAbstractSyntaxTreeForFile(context, new_file_index); - try builder.analyzeFile(unit, context, new_file_index); - logln(.compilation, .import, "Done analyzing {s}!", .{string_literal_bytes}); + if (file.status == .not_loaded) { + try unit.generateAbstractSyntaxTreeForFile(context, file_index); + } + + if (file.status == .parsed) { + try builder.analyzeFile(unit, context, file_index); } - const file = unit.files.get(import_file.file.index); assert(file.type != .null); - return import_file.file.index; + return file_index; } const ComptimeEvaluationError = error{ @@ -2083,6 +2140,7 @@ pub const Builder = struct { const function_prototype_index = unit.types.get(function_type_index).function; // Get argument declarations into scope const function_prototype_node = unit.getNode(function_prototype_node_index); + if (function_prototype_node.left != .null) { const argument_node_list = unit.getNodeList(function_prototype_node.left); const function_prototype = unit.function_prototypes.get(function_prototype_index); @@ -2129,12 +2187,13 @@ pub const Builder = struct { if (body_node.id == .block) { function.body = try builder.resolveBlock(unit, context, body_node_index); - if (builder.return_block == .null) { - const cbb = unit.basic_blocks.get(builder.current_basic_block); - const function_prototype = unit.function_prototypes.get(function_prototype_index); - const return_type = function_prototype.return_type; - if (!cbb.terminated) { + const cbb = unit.basic_blocks.get(builder.current_basic_block); + const function_prototype = unit.function_prototypes.get(function_prototype_index); + const return_type = function_prototype.return_type; + + if (!cbb.terminated) { + if (builder.return_block == .null) { switch (function_prototype.attributes.naked) { true => { assert(return_type == .noreturn); @@ -2152,7 +2211,93 @@ pub const Builder = struct { .noreturn => { try builder.buildTrap(unit, context); }, - else => unreachable, + else => switch (unit.types.get(return_type).*) { + .error_union => |error_union| switch (error_union.type) { + .void => { + const undefined_value = V{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = return_type, + }; + const insert = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = undefined_value, + .index = 1, + .new_value = .{ + .value = .{ + .@"comptime" = .{ + .bool = false, + }, + }, + .type = .bool, + }, + }, + }); + try builder.appendInstruction(unit, context, insert); + + try builder.buildRet(unit, context, .{ + .value = .{ + .runtime = insert, + }, + .type = return_type, + }); + }, + else => unreachable, + }, + else => |t| @panic(@tagName(t)), + }, + }, + } + } else { + assert(builder.return_phi != .null); + assert(builder.return_block != builder.current_basic_block); + + const phi = &unit.instructions.get(builder.return_phi).phi; + + switch (return_type) { + .void => unreachable, + .noreturn => unreachable, + else => switch (unit.types.get(return_type).*) { + .error_union => |error_union| { + if (error_union.type == .void) { + const return_value = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = .{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = return_type, + }, + .index = 1, + .new_value = .{ + .value = .{ + .@"comptime" = .{ + .bool = false, + }, + }, + .type = .bool, + }, + }, + }); + try builder.appendInstruction(unit, context, return_value); + + try phi.values.append(context.allocator, .{ + .value = .{ + .runtime = return_value, + }, + .type = return_type, + }); + try phi.basic_blocks.append(context.allocator, builder.current_basic_block); + + try builder.jump(unit, context, builder.return_block); + } else if (error_union.type == .noreturn) { + unreachable; + } else { + unreachable; + } + }, + else => {}, }, } } @@ -2297,6 +2442,51 @@ pub const Builder = struct { unreachable; } }, + .error_type => { + assert(node.left != .null); + assert(node.right == .null); + const nodes = unit.getNodeList(node.left); + if (nodes.len == 0) { + unreachable; + } + + const token_debug_info = builder.getTokenDebugInfo(unit, node.token); + + const error_index = try unit.errors.append(context.allocator, .{ + .fields = try ArrayList(Type.Error.Field.Index).initCapacity(context.allocator, nodes.len), + .scope = .{ + .scope = .{ + .file = builder.current_file, + .line = token_debug_info.line, + .column = token_debug_info.column, + .kind = .container, + .local = false, + .level = builder.current_scope.level + 1, + }, + }, + .backing_type = .u32, + }); + const new_error = unit.errors.get(error_index); + const error_type_index = try unit.types.append(context.allocator, .{ + .@"error" = error_index, + }); + + for (nodes, 0..) |field_node_index, index| { + const field_node = unit.getNode(field_node_index); + const identifier = unit.getExpectedTokenBytes(field_node.token, .identifier); + const hash = try unit.processIdentifier(context, identifier); + const error_field_index = try unit.error_fields.append(context.allocator, .{ + .name = hash, + .type = error_type_index, + .value = index, + }); + new_error.fields.appendAssumeCapacity(error_field_index); + } + + return .{ + .type = error_type_index, + }; + }, else => |t| @panic(@tagName(t)), } } @@ -2360,6 +2550,7 @@ pub const Builder = struct { optional_wrap, sign_extend, zero_extend, + error_to_error_union, }; const TypecheckError = error{}; @@ -2406,7 +2597,7 @@ pub const Builder = struct { } } - std.debug.panic("Pointer unknown typecheck:\nDst: {}\n Src: {}", .{destination_pointer, source_pointer}); + std.debug.panic("Pointer unknown typecheck:\nDst: {}\n Src: {}", .{ destination_pointer, source_pointer }); }, else => |t| @panic(@tagName(t)), } @@ -2543,13 +2734,34 @@ pub const Builder = struct { if (destination_array.termination == .none) { unreachable; } else { - std.debug.panic("Expected {s} array termination, got {s}", .{@tagName(destination_array.termination), @tagName(source_array.termination)}); + std.debug.panic("Expected {s} array termination, got {s}", .{ @tagName(destination_array.termination), @tagName(source_array.termination) }); } } else unreachable; }, else => |t| @panic(@tagName(t)), } }, + .error_union => |error_union| { + if (error_union.@"error" == source_type_index) { + return .error_to_error_union; + } else { + unreachable; + } + }, + .bool => { + switch (source.*) { + else => |t| @panic(@tagName(t)), + } + }, + .comptime_int => { + switch (source.*) { + .integer => |integer| { + _ = integer; // autofix + @panic("WTF"); + }, + else => |t| @panic(@tagName(t)), + } + }, else => |t| @panic(@tagName(t)), } } @@ -2910,6 +3122,9 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), } }, + .error_to_error_union => { + unreachable; + }, } }, .array => |expected_array_descriptor| { @@ -3215,6 +3430,7 @@ pub const Builder = struct { .usize_type, .pointer_type, .slice_type, + .field_access, => { if (element_type_index != .null) { unreachable; @@ -3279,6 +3495,28 @@ pub const Builder = struct { break :blk r; }, .function_prototype => try builder.resolveFunctionPrototype(unit, context, node_index, .{}), + .error_union => blk: { + assert(node.left != .null); + assert(node.right != .null); + + const err = try builder.resolveType(unit, context, node.left); + const ty = try builder.resolveType(unit, context, node.right); + + const error_union = try unit.types.append(context.allocator, .{ + .error_union = .{ + .@"error" = err, + .type = ty, + }, + }); + break :blk error_union; + }, + .all_errors => blk: { + const all_error_index = try unit.error_sets.append(context.allocator, .{}); + const all_errors = try unit.types.append(context.allocator, .{ + .error_set = all_error_index, + }); + break :blk all_errors; + }, else => |t| @panic(@tagName(t)), }; @@ -3701,7 +3939,180 @@ pub const Builder = struct { } if (unit.descriptor.is_test and count.test_declarations > 0) { - unreachable; + for (test_declarations.items) |test_declaration_node_index| { + const test_node = unit.getNode(test_declaration_node_index); + assert(test_node.id == .test_declaration); + + const test_block = unit.getNode(test_node.left); + assert(test_block.id == .block); + + const new_current_basic_block = builder.current_basic_block; + defer builder.current_basic_block = new_current_basic_block; + builder.current_basic_block = .null; + + const old_exit_blocks = builder.exit_blocks; + defer builder.exit_blocks = old_exit_blocks; + builder.exit_blocks = .{}; + + const old_phi_node = builder.return_phi; + defer builder.return_phi = old_phi_node; + builder.return_phi = .null; + + const old_return_block = builder.return_block; + defer builder.return_block = old_return_block; + builder.return_block = .null; + + const test_name_global = if (test_node.right != .null) b: { + const test_name_node = unit.getNode(test_node.right); + const named_global = try builder.processStringLiteralFromToken(unit, context, test_name_node.token); + break :b named_global; + } else b: { + const anon_name = try std.fmt.allocPrintZ(context.allocator, "anon_test_{}", .{unit.test_functions.entries.len}); + const anon_global = try builder.processStringLiteralFromStringAndDebugInfo(unit, context, anon_name, token_debug_info); + break :b anon_global; + }; + + const return_type = try unit.types.append(context.allocator, .{ + .error_union = .{ + .@"error" = try unit.types.append(context.allocator, .{ + // This means all errors + .error_set = try unit.error_sets.append(context.allocator, .{}), + }), + .type = .void, + }, + }); + + // TODO: make test function prototypes unique + const function_prototype_index = try unit.function_prototypes.append(context.allocator, .{ + .argument_types = &.{}, + .return_type = return_type, + .attributes = .{ + .naked = false, + }, + .calling_convention = .auto, + }); + const function_prototype = unit.function_prototypes.get(function_prototype_index); + const function_type = try unit.types.append(context.allocator, .{ + .function = function_prototype_index, + }); + builder.current_function = try unit.function_definitions.append(context.allocator, .{ + .scope = .{ + .scope = Debug.Scope{ + .line = token_debug_info.line, + .column = token_debug_info.column, + .kind = .function, + .local = true, + .level = builder.current_scope.level + 1, + .file = builder.current_file, + }, + }, + .type = function_type, + .body = undefined, + }); + const function = unit.function_definitions.get(builder.current_function); + + builder.last_check_point = .{}; + assert(builder.current_scope.kind == .file_container or builder.current_scope.kind == .file or builder.current_scope.kind == .container); + try builder.pushScope(unit, context, &function.scope.scope); + defer builder.popScope(unit, context) catch unreachable; + + const entry_basic_block = try builder.newBasicBlock(unit, context); + builder.current_basic_block = entry_basic_block; + defer builder.current_basic_block = .null; + + try builder.insertDebugCheckPoint(unit, context, test_block.token); + function.body = try builder.resolveBlock(unit, context, test_node.left); + + if (builder.return_block == .null) { + const cbb = unit.basic_blocks.get(builder.current_basic_block); + + if (!cbb.terminated) { + switch (function_prototype.attributes.naked) { + true => { + assert(return_type == .noreturn); + unreachable; + //try builder.buildTrap(unit, context); + }, + false => switch (return_type) { + .void => { + try builder.buildRet(unit, context, .{ + .value = .{ + .@"comptime" = .void, + }, + .type = .void, + }); + }, + .noreturn => { + try builder.buildTrap(unit, context); + }, + else => switch (unit.types.get(return_type).*) { + .error_union => |error_union| { + if (error_union.type == .void) { + const undef = V{ + .value = .{ .@"comptime" = .undefined }, + .type = return_type, + }; + const insert_value = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = undef, + .index = 1, + .new_value = .{ + .value = .{ + .@"comptime" = .{ + .bool = false, + }, + }, + .type = .bool, + }, + }, + }); + try builder.appendInstruction(unit, context, insert_value); + + try builder.buildRet(unit, context, .{ + .value = .{ + .runtime = insert_value, + }, + .type = return_type, + }); + } else if (error_union.type == .noreturn) { + unreachable; + // try builder.buildTrap(unit, context); + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + }, + }, + } + } + } + + const name_hash = test_name_global.initial_value.string_literal; + + const test_global_index = try unit.global_declarations.append(context.allocator, .{ + .declaration = .{ + .scope = &scope.scope, + .type = function_type, + .name = name_hash, + .line = token_debug_info.line, + .column = token_debug_info.column, + .mutability = .@"const", + .kind = .global, + }, + .initial_value = .{ + .function_definition = builder.current_function, + }, + .type_node_index = .null, + .attributes = .{}, + }); + const test_global = unit.global_declarations.get(test_global_index); + try scope.scope.declarations.putNoClobber(context.allocator, name_hash, &test_global.declaration); + + try unit.test_functions.putNoClobber(context.allocator, test_name_global, test_global); + + try unit.code_to_emit.putNoClobber(context.allocator, builder.current_function, test_global); + } } return type_index; @@ -3810,70 +4221,143 @@ pub const Builder = struct { const left_node_index = node.left; const right_node_index = node.right; const left_expect_type = Type.Expect.none; - const left_value = try builder.resolveRuntimeValue(unit, context, left_expect_type, left_node_index, .right); - const left_type = left_value.type; - const right_expect_type = Type.Expect{ .type = left_type }; - const right_value = try builder.resolveRuntimeValue(unit, context, right_expect_type, right_node_index, .right); - - break :block switch (unit.types.get(left_type).*) { - .integer => |integer| try builder.emitIntegerCompare(unit, context, left_value, right_value, integer, cmp_node_id), - .bool => try builder.emitIntegerCompare(unit, context, left_value, right_value, .{ - .bit_count = 1, - .signedness = .unsigned, - }, cmp_node_id), - .pointer => |pointer| b: { - const Pair = struct{ - left: V, - right: V, - }; - - const pair: Pair = if (left_value.type == right_value.type) .{ .left = left_value, .right = right_value } else switch (unit.types.get(right_value.type).*) { - .pointer => |right_pointer| blk: { - assert(pointer.type == right_pointer.type); - assert(pointer.mutability == right_pointer.mutability); - assert(pointer.termination == right_pointer.termination); - assert(pointer.many == right_pointer.many); - assert(pointer.nullable != right_pointer.nullable); - - if (pointer.nullable) { - // Left nullable - unreachable; - } else { - // Right nullable, then we cast the left side to optional - const cast = try unit.instructions.append(context.allocator, .{ - .cast = .{ - .id = .pointer_to_nullable, - .value = left_value, - .type = right_value.type, - }, - }); - try builder.appendInstruction(unit, context, cast); - - const new_left_value = V{ - .value = .{ - .runtime = cast, - }, - .type = right_value.type, - }; - - break :blk .{ - .left = new_left_value, - .right = right_value, - }; - } - }, - else => |t| @panic(@tagName(t)), - }; - - const compare = try builder.emitIntegerCompare(unit, context, pair.left, pair.right, .{ - .bit_count = 64, - .signedness = .unsigned, - }, cmp_node_id); - - break :b compare; - }, - else => |t| @panic(@tagName(t)), + var left_value = try builder.resolveRuntimeValue(unit, context, left_expect_type, left_node_index, .right); + const right_expect_type = switch (left_value.type) { + .comptime_int => Type.Expect.none, + else => Type.Expect{ .type = left_value.type }, }; + var right_value = try builder.resolveRuntimeValue(unit, context, right_expect_type, right_node_index, .right); + + if (left_value.value == .@"comptime" and right_value.value == .@"comptime") { + const left = switch (left_value.value.@"comptime") { + .comptime_int => |ct_int| b: { + assert(ct_int.signedness == .unsigned); + break :b ct_int.value; + }, + .constant_int => |constant_int| constant_int.value, + else => |t| @panic(@tagName(t)), + }; + + const right = switch (right_value.value.@"comptime") { + .comptime_int => |ct_int| b: { + assert(ct_int.signedness == .unsigned); + break :b ct_int.value; + }, + .constant_int => |constant_int| constant_int.value, + else => |t| @panic(@tagName(t)), + }; + + const result = switch (cmp_node_id) { + .compare_equal => left == right, + .compare_not_equal => left != right, + .compare_greater => left > right, + .compare_greater_equal => left >= right, + .compare_less => left < right, + .compare_less_equal => left <= right, + else => |t| @panic(@tagName(t)), + }; + + break :block V{ + .value = .{ + .@"comptime" = .{ + .bool = result, + }, + }, + .type = .bool, + }; + } else { + if (left_value.type != .comptime_int and right_value.type == .comptime_int) { + const ct_int = right_value.value.@"comptime".comptime_int; + right_value = .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = switch (ct_int.signedness) { + .unsigned => ct_int.value, + .signed => unreachable, + }, + }, + }, + }, + .type = left_value.type, + }; + } else if (left_value.type == .comptime_int and right_value.type != .comptime_int) { + const ct_int = left_value.value.@"comptime".comptime_int; + left_value = .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = switch (ct_int.signedness) { + .unsigned => ct_int.value, + .signed => unreachable, + }, + }, + }, + }, + .type = right_value.type, + }; + } + + break :block switch (unit.types.get(left_value.type).*) { + .integer => |integer| try builder.emitIntegerCompare(unit, context, left_value, right_value, integer, cmp_node_id), + .bool => try builder.emitIntegerCompare(unit, context, left_value, right_value, .{ + .bit_count = 1, + .signedness = .unsigned, + }, cmp_node_id), + .pointer => |pointer| b: { + const Pair = struct { + left: V, + right: V, + }; + + const pair: Pair = if (left_value.type == right_value.type) .{ .left = left_value, .right = right_value } else switch (unit.types.get(right_value.type).*) { + .pointer => |right_pointer| blk: { + assert(pointer.type == right_pointer.type); + assert(pointer.mutability == right_pointer.mutability); + assert(pointer.termination == right_pointer.termination); + assert(pointer.many == right_pointer.many); + assert(pointer.nullable != right_pointer.nullable); + + if (pointer.nullable) { + // Left nullable + unreachable; + } else { + // Right nullable, then we cast the left side to optional + const cast = try unit.instructions.append(context.allocator, .{ + .cast = .{ + .id = .pointer_to_nullable, + .value = left_value, + .type = right_value.type, + }, + }); + try builder.appendInstruction(unit, context, cast); + + const new_left_value = V{ + .value = .{ + .runtime = cast, + }, + .type = right_value.type, + }; + + break :blk .{ + .left = new_left_value, + .right = right_value, + }; + } + }, + else => |t| @panic(@tagName(t)), + }; + + const compare = try builder.emitIntegerCompare(unit, context, pair.left, pair.right, .{ + .bit_count = 64, + .signedness = .unsigned, + }, cmp_node_id); + + break :b compare; + }, + else => |t| @panic(@tagName(t)), + }; + } }, .add, .sub, .mul, .div, .mod, .bit_and, .bit_or, .bit_xor, .shift_left, .shift_right => block: { const left_node_index = node.left; @@ -3894,17 +4378,19 @@ pub const Builder = struct { const left_expect_type = type_expect; - const left_value = try builder.resolveRuntimeValue(unit, context, left_expect_type, left_node_index, .right); - const left_type = left_value.type; - switch (unit.types.get(left_type).*) { + var left_value = try builder.resolveRuntimeValue(unit, context, left_expect_type, left_node_index, .right); + switch (unit.types.get(left_value.type).*) { .integer => {}, .comptime_int => {}, else => |t| @panic(@tagName(t)), } const right_expect_type: Type.Expect = switch (type_expect) { - .none => Type.Expect{ - .type = left_type, + .none => switch (unit.types.get(left_value.type).*) { + .comptime_int => type_expect, + else => Type.Expect{ + .type = left_value.type, + }, }, .type => switch (binary_operation_id) { .add, @@ -3921,90 +4407,186 @@ pub const Builder = struct { }, else => unreachable, }; - const right_value = try builder.resolveRuntimeValue(unit, context, right_expect_type, right_node_index, .right); + var right_value = try builder.resolveRuntimeValue(unit, context, right_expect_type, right_node_index, .right); - const type_index = switch (type_expect) { - .none => switch (binary_operation_id) { - .bit_and, - .bit_or, - .shift_right, - .add, - .sub, - .mul, - .div, - .mod, - => left_type, + if (left_value.value == .@"comptime" and right_value.value == .@"comptime") { + const left = switch (left_value.value.@"comptime") { + .comptime_int => |ct_int| b: { + assert(ct_int.signedness == .unsigned); + break :b ct_int.value; + }, + .constant_int => |constant_int| constant_int.value, else => |t| @panic(@tagName(t)), - }, - .type => |type_index| type_index, - else => unreachable, - }; + }; + const right = switch (right_value.value.@"comptime") { + .comptime_int => |ct_int| b: { + assert(ct_int.signedness == .unsigned); + break :b ct_int.value; + }, + .constant_int => |constant_int| constant_int.value, + else => |t| @panic(@tagName(t)), + }; - assert(left_value.type == right_value.type); + const result = switch (binary_operation_id) { + .add => left + right, + .sub => left - right, + .mul => left * right, + .div => left / right, + .mod => left % right, + .bit_and => left & right, + .bit_or => left | right, + .bit_xor => left ^ right, + .shift_left => left << @as(u6, @intCast(right)), + .shift_right => left >> @as(u6, @intCast(right)), + }; - const instruction = switch (unit.types.get(left_type).*) { - .integer => |integer| b: { - const id: Instruction.IntegerBinaryOperation.Id = switch (binary_operation_id) { - .add => .add, - .div => .div, - .mod => .mod, - .mul => .mul, - .sub => .sub, - .bit_and => .bit_and, - .bit_or => .bit_or, - .bit_xor => .bit_xor, - .shift_left => .shift_left, - .shift_right => .shift_right, - }; - - const i = try unit.instructions.append(context.allocator, .{ - .integer_binary_operation = .{ - .left = left_value, - .right = right_value, - .id = id, - .signedness = integer.signedness, - }, - }); - break :b i; - }, - .comptime_int => { - const left = left_value.value.@"comptime".comptime_int; - const right = right_value.value.@"comptime".comptime_int; - switch (binary_operation_id) { - .add => { - assert(left.signedness == right.signedness); - assert(left.signedness == .unsigned); - if (true) unreachable; - const value = left.value + right.value; - break :block switch (type_expect) { - .none => V{ - .value = .{ - .@"comptime" = .{ - .comptime_int = .{ - .value = value, - .signedness = left.signedness, - }, - }, - }, - .type = .comptime_int, + break :block switch (type_expect) { + .type => |type_index| .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = result, }, - else => |t| @panic(@tagName(t)), - }; + }, }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - }; + .type = type_index, + }, + .none => .{ + .value = .{ + .@"comptime" = .{ + .comptime_int = .{ + .value = result, + .signedness = .unsigned, + }, + }, + }, + .type = .comptime_int, + }, + else => |t| @panic(@tagName(t)), + }; + } else { + if (left_value.type != .comptime_int and right_value.type == .comptime_int) { + const r_comptime_int = right_value.value.@"comptime".comptime_int; + right_value = .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = switch (r_comptime_int.signedness) { + .unsigned => r_comptime_int.value, + .signed => unreachable, + }, + }, + }, + }, + .type = left_value.type, + }; + } else if (left_value.type == .comptime_int and right_value.type != .comptime_int) { + const l_comptime_int = left_value.value.@"comptime".comptime_int; + left_value = .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = switch (l_comptime_int.signedness) { + .unsigned => l_comptime_int.value, + .signed => unreachable, + }, + }, + }, + }, + .type = right_value.type, + }; + } - try builder.appendInstruction(unit, context, instruction); + const result = try builder.typecheck(unit, context, left_value.type, right_value.type); + break :block switch (result) { + .success => { + assert(left_value.type == right_value.type); - break :block .{ - .value = .{ - .runtime = instruction, - }, - .type = type_index, - }; + const type_index = switch (type_expect) { + .none => switch (binary_operation_id) { + .bit_and, + .bit_or, + .bit_xor, + .shift_right, + .add, + .sub, + .mul, + .div, + .mod, + => left_value.type, + else => |t| @panic(@tagName(t)), + }, + .type => |type_index| type_index, + else => unreachable, + }; + + const instruction = switch (unit.types.get(left_value.type).*) { + .integer => |integer| b: { + const id: Instruction.IntegerBinaryOperation.Id = switch (binary_operation_id) { + .add => .add, + .div => .div, + .mod => .mod, + .mul => .mul, + .sub => .sub, + .bit_and => .bit_and, + .bit_or => .bit_or, + .bit_xor => .bit_xor, + .shift_left => .shift_left, + .shift_right => .shift_right, + }; + + const i = try unit.instructions.append(context.allocator, .{ + .integer_binary_operation = .{ + .left = left_value, + .right = right_value, + .id = id, + .signedness = integer.signedness, + }, + }); + break :b i; + }, + .comptime_int => { + const left = left_value.value.@"comptime".comptime_int; + const right = right_value.value.@"comptime".comptime_int; + switch (binary_operation_id) { + .add => { + assert(left.signedness == right.signedness); + assert(left.signedness == .unsigned); + if (true) unreachable; + const value = left.value + right.value; + break :block switch (type_expect) { + .none => V{ + .value = .{ + .@"comptime" = .{ + .comptime_int = .{ + .value = value, + .signedness = left.signedness, + }, + }, + }, + .type = .comptime_int, + }, + else => |t| @panic(@tagName(t)), + }; + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + }; + + try builder.appendInstruction(unit, context, instruction); + + break :block .{ + .value = .{ + .runtime = instruction, + }, + .type = type_index, + }; + }, + else => |t| @panic(@tagName(t)), + }; + } }, .call => try builder.resolveCall(unit, context, node_index), .field_access => try builder.resolveFieldAccess(unit, context, type_expect, node_index, side), @@ -4032,6 +4614,10 @@ pub const Builder = struct { }, .type = type_index, }, + .error_union => |error_union| { + _ = error_union; // autofix + unreachable; + }, else => |t| @panic(@tagName(t)), }, .none => V{ @@ -4285,7 +4871,6 @@ pub const Builder = struct { }, .type = .u32, }, - }, }); try builder.appendInstruction(unit, context, gep); @@ -4596,7 +5181,7 @@ pub const Builder = struct { .value = expression_to_slice, .type = pointer.type, }, - }); + }); try builder.appendInstruction(unit, context, load); const pointer_gep = try unit.instructions.append(context.allocator, .{ @@ -4606,7 +5191,7 @@ pub const Builder = struct { .is_struct = false, .index = range_start, }, - }); + }); try builder.appendInstruction(unit, context, pointer_gep); const pointer_type = try unit.getPointerType(context, .{ @@ -4636,7 +5221,7 @@ pub const Builder = struct { .none => slice_type, else => |t| @panic(@tagName(t)), }, - }, + }, .index = 0, .new_value = .{ .value = .{ @@ -4700,7 +5285,7 @@ pub const Builder = struct { .many = true, .nullable = false, }); - + const slice_type = try unit.getSliceType(context, .{ .child_type = array.type, .child_pointer_type = pointer_type, @@ -4861,7 +5446,7 @@ pub const Builder = struct { assert(!slice.nullable); assert(slice.mutability == .@"const"); - const string_global = try builder.processStringLiteral(unit, context, node.token); + const string_global = try builder.processStringLiteralFromToken(unit, context, node.token); const pointer_type = slice.child_pointer_type; @@ -4922,7 +5507,7 @@ pub const Builder = struct { }; }, .pointer => |pointer| blk: { - const string_global = try builder.processStringLiteral(unit, context, node.token); + const string_global = try builder.processStringLiteralFromToken(unit, context, node.token); switch (pointer.many) { true => { const pointer_type = try unit.getPointerType(context, .{ @@ -5365,7 +5950,7 @@ pub const Builder = struct { .value = array_like_expression, .type = pointer.type, }, - }); + }); try builder.appendInstruction(unit, context, load); const gep = try unit.instructions.append(context.allocator, .{ .get_element_pointer = .{ @@ -5399,7 +5984,7 @@ pub const Builder = struct { .value = array_like_expression, .type = pointer.type, }, - }); + }); try builder.appendInstruction(unit, context, load); const gep = try unit.instructions.append(context.allocator, .{ @@ -5409,7 +5994,7 @@ pub const Builder = struct { .is_struct = false, .index = index, }, - }); + }); try builder.appendInstruction(unit, context, gep); const gep_type = try unit.getPointerType(context, .{ @@ -5435,7 +6020,7 @@ pub const Builder = struct { .value = array_like_expression, .type = pointer.type, }, - }); + }); try builder.appendInstruction(unit, context, load); const gep = try unit.instructions.append(context.allocator, .{ @@ -5445,7 +6030,7 @@ pub const Builder = struct { .is_struct = false, .index = index, }, - }); + }); try builder.appendInstruction(unit, context, gep); const gep_type = try unit.getPointerType(context, .{ @@ -5560,7 +6145,7 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), } }, - .negation => { + .negation => block: { assert(node.left != .null); assert(node.right == .null); const value = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, node.left, .right); @@ -5578,7 +6163,7 @@ pub const Builder = struct { v = 0 - v; - return .{ + break :block .{ .value = .{ .@"comptime" = .{ .constant_int = .{ @@ -5592,7 +6177,7 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), }, .none => { - return .{ + break :block .{ .value = .{ .@"comptime" = .{ .comptime_int = .{ @@ -5614,6 +6199,11 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), } }, + .@"return" => block: { + try builder.emitReturn(unit, context, node_index); + // TODO: warning + break :block undefined; + }, else => |t| @panic(@tagName(t)), }; @@ -6606,53 +7196,7 @@ pub const Builder = struct { _ = try builder.emitLocalVariableDeclaration(unit, context, expected_identifier_token_index, mutability, declaration_type, initialization, emit, null); }, .@"return" => { - assert(statement_node.left != .null); - assert(statement_node.right == .null); - const return_value_node_index = statement_node.left; - const return_type = unit.getReturnType(builder.current_function); - const return_value = try builder.resolveRuntimeValue(unit, context, Type.Expect{ - .type = return_type, - }, return_value_node_index, .right); - - if (builder.return_block != .null) { - if (builder.return_phi != .null) { - const phi = &unit.instructions.get(builder.return_phi).phi; - try phi.values.append(context.allocator, return_value); - try phi.basic_blocks.append(context.allocator, builder.current_basic_block); - } - - assert(builder.current_basic_block != builder.return_block); - - try builder.jump(unit, context, builder.return_block); - } else if (builder.exit_blocks.items.len > 0) { - builder.return_phi = try unit.instructions.append(context.allocator, .{ - .phi = .{ - .type = return_type, - }, - }); - - builder.return_block = try builder.newBasicBlock(unit, context); - const current_basic_block = builder.current_basic_block; - builder.current_basic_block = builder.return_block; - - try builder.appendInstruction(unit, context, builder.return_phi); - - const phi = &unit.instructions.get(builder.return_phi).phi; - try phi.values.append(context.allocator, return_value); - try phi.basic_blocks.append(context.allocator, current_basic_block); - - try builder.buildRet(unit, context, .{ - .value = .{ - .runtime = builder.return_phi, - }, - .type = return_type, - }); - - builder.current_basic_block = current_basic_block; - try builder.jump(unit, context, builder.return_block); - } else { - try builder.buildRet(unit, context, return_value); - } + try builder.emitReturn(unit, context, statement_node_index); }, .call => { const result = try builder.resolveCall(unit, context, statement_node_index); @@ -7052,7 +7596,13 @@ pub const Builder = struct { const taken_expression_node_index = statement_node.right; const not_taken_expression_node_index = .null; switch (condition.value) { - .@"comptime" => unreachable, + .@"comptime" => |ct| switch (ct) { + .bool => |boolean| switch (boolean) { + true => _ = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .void }, taken_expression_node_index, .right), + false => {}, + }, + else => |t| @panic(@tagName(t)), + }, .runtime => |condition_instruction| { try builder.resolveBranch(unit, context, Type.Expect{ .type = .void }, condition_instruction, taken_expression_node_index, not_taken_expression_node_index, .null, null); }, @@ -7095,6 +7645,80 @@ pub const Builder = struct { .not_taken_expression_node_index = .null, }); }, + .catch_expression => { + assert(statement_node.left != .null); + assert(statement_node.right != .null); + + const expression = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, statement_node.left, .left); + const expression_type = unit.types.get(expression.type); + switch (expression_type.*) { + .error_union => |error_union| switch (unit.types.get(error_union.type).*) { + .void => { + const extract_value = try unit.instructions.append(context.allocator, .{ + .extract_value = .{ + .expression = expression, + .index = 1, + }, + }); + try builder.appendInstruction(unit, context, extract_value); + + const error_block = try builder.newBasicBlock(unit, context); + const clean_block = try builder.newBasicBlock(unit, context); + try builder.branch(unit, context, extract_value, error_block, clean_block); + builder.current_basic_block = error_block; + + const v = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .void }, statement_node.right, .left); + _ = v; // autofix + assert(unit.basic_blocks.get(builder.current_basic_block).terminated); + + builder.current_basic_block = clean_block; + // // try unit.instructions.append(context.allocator, .{ + // // .branch = .{ + // // .condition = extract_value, + // // .from = builder.current_basic_block, + // // }, + // // }); + // unreachable; + }, + else => |t| @panic(@tagName(t)), + }, + else => builder.reportCompileError(unit, context, .{ + .message = "expected error union expression", + .node = statement_node.left, + }), + } + }, + .try_expression => { + assert(statement_node.left != .null); + assert(statement_node.right == .null); + + const expression = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, statement_node.left, .left); + const expression_type = unit.types.get(expression.type); + switch (expression_type.*) { + .error_union => |error_union| switch (unit.types.get(error_union.type).*) { + .void => { + const extract_value = try unit.instructions.append(context.allocator, .{ + .extract_value = .{ + .expression = expression, + .index = 1, + }, + }); + try builder.appendInstruction(unit, context, extract_value); + const error_block = try builder.newBasicBlock(unit, context); + const clean_block = try builder.newBasicBlock(unit, context); + try builder.branch(unit, context, extract_value, error_block, clean_block); + builder.current_basic_block = error_block; + + try builder.buildRet(unit, context, expression); + assert(unit.basic_blocks.get(builder.current_basic_block).terminated); + + builder.current_basic_block = clean_block; + }, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + } + }, else => |t| @panic(@tagName(t)), } } @@ -7488,6 +8112,24 @@ pub const Builder = struct { // .type = type_index, // }; }, + .@"error" => |error_index| blk: { + const error_type = unit.errors.get(error_index); + const field_index = for (error_type.fields.items) |error_field_index| { + const error_field = unit.error_fields.get(error_field_index); + if (error_field.name == identifier_hash) { + break error_field_index; + } + } else std.debug.panic("Right identifier '{s}' not found", .{identifier}); + + break :blk V{ + .value = .{ + .@"comptime" = .{ + .error_value = field_index, + }, + }, + .type = type_index, + }; + }, else => |t| @panic(@tagName(t)), }; @@ -7929,6 +8571,59 @@ pub const Builder = struct { .type = ti, }; }, + .error_to_error_union => { + switch (result.value) { + .@"comptime" => |ct| switch (ct) { + .error_value => { + const v = V{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = ti, + }; + const error_union_builder = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = v, + .index = 0, + .new_value = result, + }, + }); + try builder.appendInstruction(unit, context, error_union_builder); + + const final_error_union = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = .{ + .value = .{ + .runtime = error_union_builder, + }, + .type = ti, + }, + .index = 1, + .new_value = .{ + .value = .{ + .@"comptime" = .{ + .bool = true, + }, + }, + .type = .bool, + }, + }, + }); + try builder.appendInstruction(unit, context, final_error_union); + + return .{ + .value = .{ + .runtime = final_error_union, + }, + .type = ti, + }; + }, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + } + unreachable; + }, else => |t| @panic(@tagName(t)), } }, @@ -7966,6 +8661,58 @@ pub const Builder = struct { return result; } + fn emitReturn(builder: *Builder, unit: *Unit, context: *const Context, return_node_index: Node.Index) !void { + const return_node = unit.getNode(return_node_index); + assert(return_node.id == .@"return"); + assert(return_node.left != .null); + assert(return_node.right == .null); + const return_value_node_index = return_node.left; + const return_type = unit.getReturnType(builder.current_function); + const return_value = try builder.resolveRuntimeValue(unit, context, Type.Expect{ + .type = return_type, + }, return_value_node_index, .right); + + if (builder.return_block != .null) { + if (builder.return_phi != .null) { + const phi = &unit.instructions.get(builder.return_phi).phi; + try phi.values.append(context.allocator, return_value); + try phi.basic_blocks.append(context.allocator, builder.current_basic_block); + } + + assert(builder.current_basic_block != builder.return_block); + + try builder.jump(unit, context, builder.return_block); + } else if (builder.exit_blocks.items.len > 0) { + builder.return_phi = try unit.instructions.append(context.allocator, .{ + .phi = .{ + .type = return_type, + }, + }); + + builder.return_block = try builder.newBasicBlock(unit, context); + const current_basic_block = builder.current_basic_block; + builder.current_basic_block = builder.return_block; + + try builder.appendInstruction(unit, context, builder.return_phi); + + const phi = &unit.instructions.get(builder.return_phi).phi; + try phi.values.append(context.allocator, return_value); + try phi.basic_blocks.append(context.allocator, current_basic_block); + + try builder.buildRet(unit, context, .{ + .value = .{ + .runtime = builder.return_phi, + }, + .type = return_type, + }); + + builder.current_basic_block = current_basic_block; + try builder.jump(unit, context, builder.return_block); + } else { + try builder.buildRet(unit, context, return_value); + } + } + fn buildUnreachable(builder: *Builder, unit: *Unit, context: *const Context) !void { const instruction = try unit.instructions.append(context.allocator, .@"unreachable"); try builder.appendInstruction(unit, context, instruction); @@ -7986,16 +8733,116 @@ pub const Builder = struct { try builder.appendInstruction(unit, context, ret); unit.basic_blocks.get(builder.current_basic_block).terminated = true; } - - fn reportCompileError(builder: *Builder, unit: *Unit, context: *const Context, err: Error) noreturn{ + + fn reportCompileError(builder: *Builder, unit: *Unit, context: *const Context, err: Error) noreturn { const err_node = unit.getNode(err.node); const file = unit.files.get(builder.current_file); const token_debug_info = builder.getTokenDebugInfo(unit, err_node.token); - std.debug.print("{s}:{}:{}: \x1b[31merror:\x1b[0m {s}\n", .{file.getPath(context.allocator) catch unreachable, token_debug_info.line + 1, token_debug_info.column + 1, err.message}); - // std.debug.print("[COMPILATION {s}] ", .{if (compilation_success) "\x1b[32mOK\x1b[0m" else "\x1b[31mFAILED\x1b[0m"}); - // file.relative_path + std.io.getStdOut().writer().print("{s}:{}:{}: \x1b[31merror:\x1b[0m ", .{ file.getPath(context.allocator) catch unreachable, token_debug_info.line + 1, token_debug_info.column + 1 }) catch unreachable; + std.io.getStdOut().writer().writeAll(err.message) catch unreachable; + std.io.getStdOut().writer().writeByte('\n') catch unreachable; std.os.abort(); } + + fn reportFormattedCompileError(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index, comptime format: []const u8, args: anytype) noreturn { + const err_node = unit.getNode(node_index); + const file = unit.files.get(builder.current_file); + const token_debug_info = builder.getTokenDebugInfo(unit, err_node.token); + std.io.getStdOut().writer().print("{s}:{}:{}: \x1b[31merror:\x1b[0m ", .{ file.getPath(context.allocator) catch unreachable, token_debug_info.line + 1, token_debug_info.column + 1 }) catch unreachable; + std.io.getStdOut().writer().print(format, args) catch unreachable; + std.io.getStdOut().writer().writeByte('\n') catch unreachable; + std.os.abort(); + } + + fn populateTestFunctions(builder: *Builder, unit: *Unit, context: *const Context) !void { + _ = builder; + const builtin_package = try unit.importPackage(context, unit.root_package.dependencies.get("builtin").?); + const builtin_file_index = builtin_package.file.index; + const builtin_file = unit.files.get(builtin_file_index); + const builtin_file_struct_index = unit.types.get(builtin_file.type).@"struct"; + const builtin_file_struct = unit.structs.get(builtin_file_struct_index); + const test_functions_name = "test_functions"; + const test_functions_name_hash = try unit.processIdentifier(context, test_functions_name); + const test_functions = builtin_file_struct.scope.scope.declarations.get(test_functions_name_hash).?; + const test_slice_type = test_functions.type; + const test_type = unit.types.get(test_slice_type).slice.child_type; + assert(test_functions.kind == .global); + const test_functions_global = @fieldParentPtr(Debug.Declaration.Global, "declaration", test_functions); + assert(test_functions_global.declaration.mutability == .@"var"); + const array_type = try unit.getArrayType(context, .{ + .type = test_type, + .count = unit.test_functions.values().len, + .termination = .none, + }); + + const struct_test_type = unit.types.get(test_type); + const test_type_struct = unit.structs.get(struct_test_type.@"struct"); + assert(test_type_struct.fields.items.len == 2); + const first_field = unit.struct_fields.get(test_type_struct.fields.items[0]); + // const second_field = unit.struct_fields.get(test_type_struct.fields.items[1]); + + var list = try ArrayList(V.Comptime).initCapacity(context.allocator, unit.test_functions.values().len); + for (unit.test_functions.keys(), unit.test_functions.values()) |test_function_name_global, test_function_global| { + var fields = try ArrayList(V.Comptime).initCapacity(context.allocator, 2); + const name = unit.getIdentifier(test_function_name_global.initial_value.string_literal); + const name_slice = try unit.constant_slices.append(context.allocator, .{ + .array = test_function_name_global, + .start = 0, + .end = name.len, + .type = first_field.type, + }); + fields.appendAssumeCapacity(.{ + .constant_slice = name_slice, + }); + fields.appendAssumeCapacity(.{ + .global = test_function_global, + }); + const constant_struct = try unit.constant_structs.append(context.allocator, .{ + .fields = fields.items, + .type = test_type, + }); + + list.appendAssumeCapacity(.{ + .constant_struct = constant_struct, + }); + } + + const constant_array = try unit.constant_arrays.append(context.allocator, .{ + .type = array_type, + .values = list.items, + }); + + const array_name = "_anon_test_function_array"; + const array_name_hash = try unit.processIdentifier(context, array_name); + const test_function_array_global_index = try unit.global_declarations.append(context.allocator, .{ + .declaration = .{ + .scope = test_functions_global.declaration.scope, + .type = array_type, + .name = array_name_hash, + .line = test_functions_global.declaration.line, + .column = test_functions_global.declaration.column, + .mutability = .@"const", + .kind = .global, + }, + .initial_value = .{ + .constant_array = constant_array, + }, + .type_node_index = .null, + .attributes = .{}, + }); + const test_function_array_global = unit.global_declarations.get(test_function_array_global_index); + try unit.data_to_emit.append(context.allocator, test_function_array_global); + const constant_slice = try unit.constant_slices.append(context.allocator, .{ + .array = test_function_array_global, + .start = 0, + .end = list.items.len, + .type = test_functions_global.declaration.type, + }); + + test_functions_global.initial_value = .{ + .constant_slice = constant_slice, + }; + } }; pub const Enum = struct { @@ -8021,6 +8868,7 @@ pub const Unit = struct { files: Debug.File.List = .{}, types: Type.List = .{}, structs: Struct.List = .{}, + unions: Type.Union.List = .{}, struct_fields: Struct.Field.List = .{}, enums: Enum.List = .{}, enum_fields: Enum.Field.List = .{}, @@ -8037,6 +8885,9 @@ pub const Unit = struct { constant_structs: V.Comptime.ConstantStruct.List = .{}, constant_arrays: V.Comptime.ConstantArray.List = .{}, constant_slices: V.Comptime.ConstantSlice.List = .{}, + errors: Type.Error.List = .{}, + error_sets: Type.Error.Set.List = .{}, + error_fields: Type.Error.Field.List = .{}, token_buffer: Token.Buffer = .{}, node_lists: ArrayList(ArrayList(Node.Index)) = .{}, file_token_offsets: AutoArrayHashMap(Token.Range, Debug.File.Index) = .{}, @@ -8057,6 +8908,7 @@ pub const Unit = struct { external_functions: AutoArrayHashMap(Type.Index, *Debug.Declaration.Global) = .{}, type_declarations: AutoHashMap(Type.Index, *Debug.Declaration.Global) = .{}, struct_type_map: AutoHashMap(Struct.Index, Type.Index) = .{}, + test_functions: AutoArrayHashMap(*Debug.Declaration.Global, *Debug.Declaration.Global) = .{}, scope: Debug.Scope.Global = .{ .scope = .{ .file = .null, @@ -8067,7 +8919,8 @@ pub const Unit = struct { .local = false, }, }, - main_package: *Package = undefined, + root_package: *Package = undefined, + main_package: ?*Package = null, descriptor: Descriptor, discard_identifiers: usize = 0, anon_i: usize = 0, @@ -8498,7 +9351,7 @@ pub const Unit = struct { return unit.identifiers.getValue(hash).?; } - pub fn analyze(unit: *Unit, context: *const Context, main_package: *Package) !void { + pub fn analyze(unit: *Unit, context: *const Context) !void { const builder = try context.allocator.create(Builder); builder.* = .{ .generate_debug_info = unit.descriptor.generate_debug_information, @@ -8512,9 +9365,17 @@ pub const Unit = struct { _ = try unit.types.append(context.allocator, type_value); } - try builder.analyzePackage(unit, context, main_package); + try builder.analyzePackage(unit, context, unit.root_package.dependencies.get("std").?); + if (unit.descriptor.is_test) { + try builder.analyzePackage(unit, context, unit.main_package.?); + const test_function_count = unit.test_functions.keys().len; + if (test_function_count > 0) { + try builder.populateTestFunctions(unit, context); + } + } + for (unit.external_functions.values()) |function_declaration| { - logln(.compilation, .ir, "External function: {s}", .{ unit.getIdentifier(function_declaration.declaration.name) }); + logln(.compilation, .ir, "External function: {s}", .{unit.getIdentifier(function_declaration.declaration.name)}); } for (unit.code_to_emit.values()) |function_declaration| { @@ -8573,15 +9434,15 @@ pub const Unit = struct { logln(.compilation, .import, "import: '{s}'\n", .{import_name}); if (equal(u8, import_name, "std")) { - return unit.importPackage(context, unit.main_package.dependencies.get("std").?); + return unit.importPackage(context, unit.root_package.dependencies.get("std").?); } if (equal(u8, import_name, "builtin")) { - return unit.importPackage(context, unit.main_package.dependencies.get("builtin").?); + return unit.importPackage(context, unit.root_package.dependencies.get("builtin").?); } - if (equal(u8, import_name, "main")) { - return unit.importPackage(context, unit.main_package); + if (equal(u8, import_name, "root")) { + return unit.importPackage(context, unit.root_package); } const current_file = unit.files.get(current_file_index); @@ -8652,17 +9513,22 @@ pub const Unit = struct { \\const os = builtin.Os.{s}; \\const abi = builtin.Abi.{s}; \\const link_libc = {}; - \\ , .{ @tagName(unit.descriptor.target.cpu.arch), @tagName(unit.descriptor.target.os.tag), @tagName(unit.descriptor.target.abi), unit.descriptor.link_libc, }); + if (unit.descriptor.is_test) { + try builtin_file.writer().writeAll( + \\var test_functions: []const builtin.TestFunction = undefined; + ); + } + try builtin_file.writer().writeByte('\n'); builtin_file.close(); } - unit.main_package = blk: { + const main_package = blk: { const result = try context.allocator.create(Package); const main_package_absolute_directory_path = b: { const relative_path = if (std.fs.path.dirname(unit.descriptor.main_package_path)) |dirname| dirname else "."; @@ -8677,6 +9543,21 @@ pub const Unit = struct { }; break :blk result; }; + + unit.root_package = if (unit.descriptor.is_test) blk: { + const package = try context.allocator.create(Package); + const directory_path = try context.pathFromCompiler("lib"); + package.* = .{ + .directory = .{ + .handle = try std.fs.openDirAbsolute(directory_path, .{}), + .path = directory_path, + }, + .source_path = "test_runner.nat", + }; + unit.main_package = main_package; + + break :blk package; + } else main_package; const std_package_dir = "lib/std"; const package_descriptors = [2]struct { @@ -8708,17 +9589,20 @@ pub const Unit = struct { .source_path = try std.mem.concat(context.allocator, u8, &.{ package_descriptor.name, ".nat" }), }; - try unit.main_package.addDependency(context.allocator, package_descriptor.name, package); + try unit.root_package.addDependency(context.allocator, package_descriptor.name, package); package_ptr.* = package; } - assert(unit.main_package.dependencies.size == 2); + assert(unit.root_package.dependencies.size == 2); if (!unit.descriptor.only_parse) { - _ = try unit.importPackage(context, unit.main_package.dependencies.get("std").?); + _ = try unit.importPackage(context, unit.root_package.dependencies.get("std").?); + if (unit.descriptor.is_test) { + _ = try unit.importPackage(context, unit.main_package.?); + } } else { - _ = try unit.importPackage(context, unit.main_package); + _ = try unit.importPackage(context, unit.root_package); } for (unit.file_map.values()) |import| { @@ -8726,7 +9610,7 @@ pub const Unit = struct { } if (!unit.descriptor.only_parse) { - try unit.analyze(context, packages[0]); + try unit.analyze(context); try llvm.codegen(unit, context); } @@ -8760,6 +9644,10 @@ pub const FixedKeyword = enum { undefined, @"break", @"test", + @"catch", + @"try", + @"orelse", + @"error", }; pub const Descriptor = struct { @@ -8898,6 +9786,10 @@ pub const Token = struct { fixed_keyword_undefined, fixed_keyword_break, fixed_keyword_test, + fixed_keyword_try, + fixed_keyword_catch, + fixed_keyword_orelse, + fixed_keyword_error, unused1, unused2, unused3, diff --git a/bootstrap/backend/llvm.zig b/bootstrap/backend/llvm.zig index 798dec1..047012e 100644 --- a/bootstrap/backend/llvm.zig +++ b/bootstrap/backend/llvm.zig @@ -1180,7 +1180,13 @@ pub const LLVM = struct { for (sema_function_prototype.argument_types) |argument_type_index| { switch (unit.types.get(argument_type_index).*) { // TODO: ABI - .integer, .pointer, .@"enum", .@"struct", .slice, .bool, => try parameter_types.append(context.allocator, try llvm.getType(unit, context, argument_type_index)), + .integer, + .pointer, + .@"enum", + .@"struct", + .slice, + .bool, + => try parameter_types.append(context.allocator, try llvm.getType(unit, context, argument_type_index)), else => |t| @panic(@tagName(t)), } // arg_types.appendAssumeCapacity(llvm_argument_type); @@ -1262,6 +1268,39 @@ pub const LLVM = struct { const array_type = LLVM.Type.Array.get(element_type, array.count + @intFromBool(extra_element)) orelse return Type.Error.array; break :blk array_type.toType(); }, + .error_union => |error_union| { + const error_type = try llvm.getType(unit, context, error_union.@"error"); + const payload_type = try llvm.getType(unit, context, error_union.type); + const payload_type_size = unit.types.get(error_union.type).getBitSize(unit); + + switch (payload_type_size) { + 0 => { + const integer_type = llvm.context.getIntegerType(31) orelse unreachable; + const boolean_type = try llvm.getType(unit, context, .bool); + const types = [2]*LLVM.Type{ integer_type.toType(), boolean_type }; + const is_packed = false; + const struct_type = llvm.context.getStructType(&types, types.len, is_packed) orelse return Type.Error.@"struct"; + return struct_type.toType(); + }, + else => unreachable, + } + _ = error_type; + _ = payload_type; + unreachable; + }, + .error_set => |error_set_index| b: { + const error_set = unit.error_sets.get(error_set_index); + if (error_set.values.items.len > 0) { + unreachable; + } else { + const integer_type = llvm.context.getIntegerType(32) orelse unreachable; + break :b integer_type.toType(); + } + }, + .@"error" => b: { + const integer_type = llvm.context.getIntegerType(31) orelse unreachable; + break :b integer_type.toType(); + }, else => |t| @panic(@tagName(t)), }; @@ -1327,7 +1366,13 @@ pub const LLVM = struct { break :b name.items; } else { - unreachable; + if (unit.type_declarations.get(sema_type_index)) |type_declaration| { + _ = type_declaration; // autofix + unreachable; + } else { + // TODO: fix + break :b "anon_struct"; + } } }, // TODO: termination @@ -2031,7 +2076,7 @@ pub const LLVM = struct { } } - fn emitComptimeRightValue(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, ct: Compilation.V.Comptime, type_index: Compilation.Type.Index) !*LLVM.Value.Constant { + fn emitComptimeRightValue(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, ct: Compilation.V.Comptime, type_index: Compilation.Type.Index) anyerror!*LLVM.Value.Constant { switch (ct) { .constant_int => |integer| { const integer_type = unit.types.get(type_index); @@ -2083,21 +2128,7 @@ pub const LLVM = struct { const constant_int = llvm.context.getConstantInt(backing_integer_type.bit_count, value, signed) orelse unreachable; return constant_int.toConstant(); }, - .constant_struct => |constant_struct_index| { - const constant_struct = unit.constant_structs.get(constant_struct_index); - var field_values = try ArrayList(*LLVM.Value.Constant).initCapacity(context.allocator, constant_struct.fields.len); - const sema_struct_type = unit.structs.get(unit.types.get(constant_struct.type).@"struct"); - for (constant_struct.fields, sema_struct_type.fields.items) |field_value, field_index| { - const field = unit.struct_fields.get(field_index); - const constant = try llvm.emitComptimeRightValue(unit, context, field_value, field.type); - field_values.appendAssumeCapacity(constant); - } - - const llvm_type = try llvm.getType(unit, context, constant_struct.type); - const struct_type = llvm_type.toStruct() orelse unreachable; - const const_struct = struct_type.getConstant(field_values.items.ptr, field_values.items.len) orelse unreachable; - return const_struct; - }, + .constant_struct => |constant_struct_index| return try llvm.getConstantStruct(unit, context, constant_struct_index), .undefined => { const undefined_type = try llvm.getType(unit, context, type_index); const poison = undefined_type.getPoison() orelse unreachable; @@ -2130,6 +2161,13 @@ pub const LLVM = struct { const constant_null_pointer = pointer_type.getNull(); return constant_null_pointer.toConstant(); }, + .error_value => |error_field_index| { + const error_field = unit.error_fields.get(error_field_index); + const signed = false; + const bit_count = 31; + const constant_int = llvm.context.getConstantInt(bit_count, error_field.value, signed) orelse unreachable; + return constant_int.toConstant(); + }, else => |t| @panic(@tagName(t)), } } @@ -2192,9 +2230,10 @@ pub const LLVM = struct { const const_slice = unit.constant_slices.get(constant_slice_index); const const_slice_type = try llvm.getType(unit, context, const_slice.type); const slice_struct_type = const_slice_type.toStruct() orelse unreachable; - const ptr = llvm.global_variable_map.get(const_slice.ptr).?; + const ptr = llvm.global_variable_map.get(const_slice.array).?; const signed = false; - const len = llvm.context.getConstantInt(@bitSizeOf(usize), const_slice.len, signed) orelse unreachable; + assert(const_slice.start == 0); + const len = llvm.context.getConstantInt(@bitSizeOf(usize), const_slice.end, signed) orelse unreachable; const slice_fields = [2]*LLVM.Value.Constant{ ptr.toConstant(), len.toConstant(), @@ -2223,6 +2262,7 @@ pub const LLVM = struct { break :b constant_int.toConstant(); }, .constant_slice => |constant_slice_index| try llvm.getConstantSlice(unit, context, constant_slice_index), + .constant_struct => |constant_struct_index| try llvm.getConstantStruct(unit, context, constant_struct_index), else => |t| @panic(@tagName(t)), }; list.appendAssumeCapacity(value); @@ -2232,6 +2272,22 @@ pub const LLVM = struct { return result; } + fn getConstantStruct(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, constant_struct_index: Compilation.V.Comptime.ConstantStruct.Index) !*LLVM.Value.Constant { + const constant_struct = unit.constant_structs.get(constant_struct_index); + var field_values = try ArrayList(*LLVM.Value.Constant).initCapacity(context.allocator, constant_struct.fields.len); + const sema_struct_type = unit.structs.get(unit.types.get(constant_struct.type).@"struct"); + for (constant_struct.fields, sema_struct_type.fields.items) |field_value, field_index| { + const field = unit.struct_fields.get(field_index); + const constant = try llvm.emitComptimeRightValue(unit, context, field_value, field.type); + field_values.appendAssumeCapacity(constant); + } + + const llvm_type = try llvm.getType(unit, context, constant_struct.type); + const struct_type = llvm_type.toStruct() orelse unreachable; + const const_struct = struct_type.getConstant(field_values.items.ptr, field_values.items.len) orelse unreachable; + return const_struct; + } + fn callIntrinsic(llvm: *LLVM, intrinsic_name: []const u8, intrinsic_parameter_types: []const *LLVM.Type, intrinsic_arguments: []const *LLVM.Value) !*LLVM.Value { const intrinsic_id = LLVM.lookupIntrinsic(intrinsic_name.ptr, intrinsic_name.len); assert(intrinsic_id != .none); @@ -2341,7 +2397,7 @@ pub const LLVM = struct { const function_name = unit.getIdentifier(declaration.declaration.name); const subprogram = llvm.debug_info_builder.createFunction(debug_file.toScope(), function_name.ptr, function_name.len, function_name.ptr, function_name.len, debug_file, declaration.declaration.line + 1, subroutine_type, scope_line, subroutine_type_flags, subprogram_flags, subprogram_declaration) orelse unreachable; function.setSubprogram(subprogram); - + switch (declaration.initial_value) { .function_declaration => {}, .function_definition => |function_definition_index| { @@ -2354,7 +2410,6 @@ pub const LLVM = struct { } } } - }; fn getCallingConvention(calling_convention: Compilation.Function.CallingConvention) LLVM.Value.Constant.Function.CallingConvention { @@ -2508,6 +2563,8 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo const entry_block_node = try llvm.createBasicBlock(context, function_definition.basic_blocks.items[0], "fn_entry"); block_command_list.append(entry_block_node); + var phis = AutoArrayHashMap(Compilation.Instruction.Index, *LLVM.Value.Instruction.PhiNode){}; + while (block_command_list.len != 0) { const block_node = block_command_list.first orelse unreachable; const basic_block_index = block_node.data; @@ -2625,14 +2682,8 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo switch (unit.types.get(stack_slot.type).*) { .void, .noreturn, .type => unreachable, .comptime_int => unreachable, - .bool => {}, - .@"struct" => {}, - .@"enum" => {}, .function => unreachable, - .integer => {}, - .pointer => {}, - .slice => {}, - .array => {}, + else => {}, } const declaration_type = try llvm.getType(unit, context, stack_slot.type); @@ -2769,7 +2820,6 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo else => |t| @panic(@tagName(t)), }; - for (call.arguments, arguments) |argument_value, *argument| { argument.* = try llvm.emitRightValue(unit, context, argument_value); } @@ -2883,15 +2933,8 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo switch (unit.types.get(argument_type_index).*) { .void, .noreturn, .type => unreachable, .comptime_int => unreachable, - .bool => {}, - // .bool => unreachable, - .@"struct" => {}, - .@"enum" => {}, .function => unreachable, - .integer => {}, - .pointer => {}, - .slice => {}, - .array => {}, + else => {}, } const argument_type = argument.toValue().getType(); const alloca_array_size: ?*LLVM.Value = null; @@ -3047,6 +3090,8 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo const not_taken_node = try llvm.createBasicBlock(context, branch.not_taken, "not_taken_block"); block_command_list.insertAfter(block_node, taken_node); block_command_list.insertAfter(taken_node, not_taken_node); + // block_command_list.append(taken_node); + // block_command_list.append(taken_node); // TODO: make this fast const taken_block = llvm.llvm_block_map.get(taken_node.data).?; @@ -3064,11 +3109,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo const phi_name = "phi"; const phi_node = llvm.builder.createPhi(phi_type, reserved_value_count, phi_name, phi_name.len) orelse unreachable; - for (phi.values.items, phi.basic_blocks.items) |sema_value, sema_block| { - const value = llvm.llvm_value_map.get(sema_value) orelse try llvm.emitRightValue(unit, context, sema_value); - const value_basic_block = llvm.llvm_block_map.get(sema_block).?; - phi_node.addIncoming(value, value_basic_block); - } + try phis.putNoClobber(context.allocator, instruction_index, phi_node); try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, phi_node.toValue()); }, @@ -3106,6 +3147,16 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo _ = block_command_list.popFirst(); } + for (phis.keys(), phis.values()) |instruction_index, phi| { + const instruction = unit.instructions.get(instruction_index); + const sema_phi = instruction.phi; + for (sema_phi.values.items, sema_phi.basic_blocks.items) |sema_value, sema_block| { + const value_basic_block = llvm.llvm_block_map.get(sema_block).?; + const value = llvm.llvm_value_map.get(sema_value) orelse try llvm.emitRightValue(unit, context, sema_value); + phi.addIncoming(value, value_basic_block); + } + } + if (!builder.isCurrentBlockTerminated()) { var message_len: usize = 0; const function_str = llvm.function.toString(&message_len); @@ -3228,7 +3279,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo try arguments.append(context.allocator, "-lSystem"); }, .linux => { - try arguments.appendSlice(context.allocator, &.{"--entry", "_start"}); + try arguments.appendSlice(context.allocator, &.{ "--entry", "_start" }); try arguments.append(context.allocator, "-m"); try arguments.append(context.allocator, switch (unit.descriptor.target.cpu.arch) { .x86_64 => "elf_x86_64", @@ -3238,8 +3289,8 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo if (unit.descriptor.link_libc) { try arguments.append(context.allocator, "/usr/lib/crt1.o"); try arguments.append(context.allocator, "/usr/lib/crti.o"); - try arguments.appendSlice(context.allocator, &.{"-L", "/usr/lib"}); - try arguments.appendSlice(context.allocator, &.{"-dynamic-linker", "/lib64/ld-linux-x86-64.so.2"}); + try arguments.appendSlice(context.allocator, &.{ "-L", "/usr/lib" }); + try arguments.appendSlice(context.allocator, &.{ "-dynamic-linker", "/lib64/ld-linux-x86-64.so.2" }); try arguments.append(context.allocator, "--as-needed"); try arguments.append(context.allocator, "-lm"); try arguments.append(context.allocator, "-lpthread"); diff --git a/bootstrap/data_structures.zig b/bootstrap/data_structures.zig index e762e9e..eec9637 100644 --- a/bootstrap/data_structures.zig +++ b/bootstrap/data_structures.zig @@ -100,7 +100,7 @@ pub fn getIndexForType(comptime T: type, comptime E: type) type { const EnumField = std.builtin.Type.EnumField; comptime var fields: []const EnumField = &.{}; // comptime var enum_value: comptime_int = 0; - fields = fields ++ @typeInfo(E).Enum.fields; + fields = fields ++ @typeInfo(E).Enum.fields; // for (names) |name| { // fields = fields ++ [1]EnumField{.{ @@ -137,32 +137,31 @@ pub fn getIndexForType(comptime T: type, comptime E: type) type { return @enumFromInt(value); } - pub fn addInt(this: Index, value: IndexType) Index{ + pub fn addInt(this: Index, value: IndexType) Index { const this_int = @intFromEnum(this); return @enumFromInt(this_int + value); } - pub fn subInt(this: Index, value: IndexType) IndexType{ + pub fn subInt(this: Index, value: IndexType) IndexType { const this_int = @intFromEnum(this); return this_int - value; } - pub fn add(a: Index, b: Index) Index{ + pub fn add(a: Index, b: Index) Index { return @enumFromInt(@intFromEnum(a) + @intFromEnum(b)); } - pub fn sub(a: Index, b: Index) IndexType{ + pub fn sub(a: Index, b: Index) IndexType { return @intFromEnum(a) - @intFromEnum(b); } }; } -pub const ListType = enum{ +pub const ListType = enum { index, pointer, }; - pub fn enumFromString(comptime E: type, string: []const u8) ?E { return inline for (@typeInfo(E).Enum.fields) |enum_field| { if (std.mem.eql(u8, string, enum_field.name)) { diff --git a/bootstrap/frontend/lexer.zig b/bootstrap/frontend/lexer.zig index 7cb0663..27df5a1 100644 --- a/bootstrap/frontend/lexer.zig +++ b/bootstrap/frontend/lexer.zig @@ -15,7 +15,6 @@ const logln = Compilation.logln; const Token = Compilation.Token; const fs = @import("../fs.zig"); - // Needed information // Token: u8 // line: u32 @@ -76,7 +75,7 @@ pub fn analyze(allocator: Allocator, text: []const u8, token_buffer: *Token.Buff try token_buffer.tokens.ensureUnusedCapacity(allocator, text.len / 4); - logln(.lexer, .end, "START LEXER - TOKEN OFFSET: {} - LINE OFFSET: {}", .{Token.unwrap(lexer.offset), lexer.line_offset}); + logln(.lexer, .end, "START LEXER - TOKEN OFFSET: {} - LINE OFFSET: {}", .{ Token.unwrap(lexer.offset), lexer.line_offset }); while (index < len) { const start_index = index; @@ -229,7 +228,7 @@ pub fn analyze(allocator: Allocator, text: []const u8, token_buffer: *Token.Buff index += 1; break :blk .operator_compare_less_equal; }, - else =>break :blk .operator_compare_less, + else => break :blk .operator_compare_less, } }, '>' => blk: { @@ -249,7 +248,7 @@ pub fn analyze(allocator: Allocator, text: []const u8, token_buffer: *Token.Buff index += 1; break :blk .operator_compare_greater_equal; }, - else =>break :blk .operator_compare_greater, + else => break :blk .operator_compare_greater, } }, ';' => blk: { @@ -281,7 +280,6 @@ pub fn analyze(allocator: Allocator, text: []const u8, token_buffer: *Token.Buff }, else => break :blk .operator_bang, } - }, '=' => blk: { index += 1; @@ -421,10 +419,10 @@ pub fn analyze(allocator: Allocator, text: []const u8, token_buffer: *Token.Buff }); const line_offset = token_buffer.line_offsets.items[line_index]; const column = start_index - line_offset; - logln(.lexer, .new_token, "T at line {}, column {}, byte offset {}, with length {} -line offset: {}- ({s})", .{line_index, column, start_index, token_length, line_offset, @tagName(token_id)}); + logln(.lexer, .new_token, "T at line {}, column {}, byte offset {}, with length {} -line offset: {}- ({s})", .{ line_index, column, start_index, token_length, line_offset, @tagName(token_id) }); } - logln(.lexer, .end, "END LEXER - TOKEN OFFSET: {} - LINE OFFSET: {}", .{Token.unwrap(lexer.offset), lexer.line_offset}); + logln(.lexer, .end, "END LEXER - TOKEN OFFSET: {} - LINE OFFSET: {}", .{ Token.unwrap(lexer.offset), lexer.line_offset }); lexer.count = Token.sub(token_buffer.getOffset(), lexer.offset); lexer.line_count = token_buffer.getLineOffset() - lexer.line_offset; diff --git a/bootstrap/frontend/parser.zig b/bootstrap/frontend/parser.zig index c6fabb4..e8a6482 100644 --- a/bootstrap/frontend/parser.zig +++ b/bootstrap/frontend/parser.zig @@ -65,7 +65,7 @@ pub const Node = struct { token: Token.Index, id: Id, - pub const List = BlockList(@This(), enum{}); + pub const List = BlockList(@This(), enum {}); pub usingnamespace List.Index; pub const Range = struct { @@ -187,6 +187,9 @@ pub const Node = struct { test_declaration, all_errors, error_union, + catch_expression, + try_expression, + error_type, }; }; @@ -305,9 +308,11 @@ const Analyzer = struct { const attribute_node = inline for (@typeInfo(Compilation.Debug.Declaration.Global.Attribute).Enum.fields) |enum_field| { if (equal(u8, identifier_name, enum_field.name)) { - const attribute = @field(Compilation.Debug.Declaration.Global.Attribute, enum_field.name); + const attribute = @field(Compilation.Debug.Declaration.Global.Attribute, enum_field.name); const attribute_node = switch (attribute) { - .@"export", .@"extern", => try analyzer.addNode(.{ + .@"export", + .@"extern", + => try analyzer.addNode(.{ .id = @field(Node.Id, "symbol_attribute_" ++ @tagName(attribute)), .token = identifier, .left = .null, @@ -391,9 +396,10 @@ const Analyzer = struct { const attribute_node = inline for (@typeInfo(Compilation.Function.Attribute).Enum.fields) |enum_field| { if (equal(u8, identifier_name, enum_field.name)) { - const attribute = @field(Compilation.Function.Attribute, enum_field.name); + const attribute = @field(Compilation.Function.Attribute, enum_field.name); const attribute_node = switch (attribute) { - .naked, => try analyzer.addNode(.{ + .naked, + => try analyzer.addNode(.{ .id = @field(Node.Id, "function_attribute_" ++ @tagName(attribute)), .token = identifier, .left = .null, @@ -415,12 +421,12 @@ const Analyzer = struct { const arguments = try analyzer.argumentList(.operator_left_parenthesis, .operator_right_parenthesis); const return_type = try analyzer.typeExpression(); try attribute_and_return_type_node_list.append(analyzer.allocator, return_type); - + const function_prototype = try analyzer.addNode(.{ .id = .function_prototype, .token = token, .left = arguments, - .right = try analyzer.nodeList( attribute_and_return_type_node_list), + .right = try analyzer.nodeList(attribute_and_return_type_node_list), }); return function_prototype; @@ -473,16 +479,17 @@ const Analyzer = struct { const first_statement_token = analyzer.peekToken(); logln(.parser, .block, "First statement token: {s}", .{@tagName(first_statement_token)}); const statement_index = switch (first_statement_token) { - .identifier => switch (analyzer.peekTokenAhead(1)) { - .operator_colon => { - unreachable; - }, - else => try analyzer.assignExpressionStatement(), - }, - .fixed_keyword_unreachable, - .fixed_keyword_return, - .discard, - => try analyzer.assignExpressionStatement(), + else => try analyzer.assignExpressionStatement(), + // .identifier => switch (analyzer.peekTokenAhead(1)) { + // .operator_colon => { + // unreachable; + // }, + // else => try analyzer.assignExpressionStatement(), + // }, + // .fixed_keyword_unreachable, + // .fixed_keyword_return, + // .discard, + // => try analyzer.assignExpressionStatement(), .fixed_keyword_while => try analyzer.whileExpression(), .fixed_keyword_switch => try analyzer.switchExpression(), @@ -491,17 +498,12 @@ const Analyzer = struct { .fixed_keyword_const, .fixed_keyword_var, => try analyzer.symbolDeclaration(), - .fixed_keyword_break => b: { - const node_index = try analyzer.breakExpression(); - _ = try analyzer.expectToken(.operator_semicolon); - break :b node_index; - }, - .intrinsic => blk: { - const intrinsic = try analyzer.compilerIntrinsic(); - _ = try analyzer.expectToken(.operator_semicolon); - break :blk intrinsic; - }, - else => |t| @panic(@tagName(t)), + // .intrinsic => blk: { + // const intrinsic = try analyzer.compilerIntrinsic(); + // _ = try analyzer.expectToken(.operator_semicolon); + // break :blk intrinsic; + // }, + // else => |t| @panic(@tagName(t)), }; const node = analyzer.nodes.get(statement_index); @@ -783,7 +785,7 @@ const Analyzer = struct { const left = try analyzer.expression(); const expression_token = analyzer.token_i; const expression_id: Node.Id = switch (analyzer.peekToken()) { - .operator_semicolon, .operator_comma => return left, + .operator_semicolon, .operator_comma, .operator_right_brace, .identifier => return left, .operator_assign => .assign, .operator_add_assign => .add_assign, .operator_sub_assign => .sub_assign, @@ -951,6 +953,7 @@ const Analyzer = struct { bit_or, shift_left, shift_right, + @"catch", }; const operator_precedence = std.EnumArray(PrecedenceOperator, i32).init(.{ @@ -970,6 +973,7 @@ const Analyzer = struct { .bit_or = 40, .shift_left = 50, .shift_right = 50, + .@"catch" = 40, }); const operator_associativity = std.EnumArray(PrecedenceOperator, Associativity).init(.{ @@ -989,6 +993,7 @@ const Analyzer = struct { .mod = .left, .shift_left = .left, .shift_right = .left, + .@"catch" = .left, }); const operator_node_id = std.EnumArray(PrecedenceOperator, Node.Id).init(.{ @@ -1008,6 +1013,7 @@ const Analyzer = struct { .mod = .mod, .shift_left = .shift_left, .shift_right = .shift_right, + .@"catch" = .catch_expression, }); fn expressionPrecedence(analyzer: *Analyzer, minimum_precedence: i32) !Node.Index { @@ -1062,6 +1068,7 @@ const Analyzer = struct { .operator_xor => .bit_xor, .operator_shift_left => .shift_left, .operator_shift_right => .shift_right, + .fixed_keyword_catch => .@"catch", else => |t| @panic(@tagName(t)), }; @@ -1114,6 +1121,7 @@ const Analyzer = struct { }, .operator_bang => .boolean_not, .operator_minus => .negation, + .fixed_keyword_try => .try_expression, // .tilde => |t| @panic(@tagName(t)), }; @@ -1155,6 +1163,9 @@ const Analyzer = struct { .discard, .fixed_keyword_undefined, .operator_left_bracket, + .fixed_keyword_const, + .fixed_keyword_var, + .fixed_keyword_error, => try analyzer.curlySuffixExpression(), .fixed_keyword_fn => try analyzer.function(), .fixed_keyword_return => try analyzer.addNode(.{ @@ -1167,6 +1178,7 @@ const Analyzer = struct { .left = try analyzer.expression(), .right = Node.Index.null, }), + .fixed_keyword_break => try analyzer.breakExpression(), // todo:? .operator_left_brace => try analyzer.block(), .fixed_keyword_if => try analyzer.ifExpression(), @@ -1384,34 +1396,40 @@ const Analyzer = struct { } fn errorUnionExpression(analyzer: *Analyzer) !Node.Index { - if (analyzer.peekToken() == .operator_bang) { - const error_union_token = try analyzer.expectToken(.operator_bang); - if (analyzer.peekToken() == .operator_asterisk) { - analyzer.consumeToken(); - // All errors - const all_errors_node = try analyzer.addNode(.{ - .id = .all_errors, - .token = error_union_token, - .left = .null, - .right = .null, - }); - const type_node = try analyzer.suffixExpression(); + const initial = analyzer.token_i; + if (analyzer.peekToken() == .operator_asterisk and analyzer.peekTokenAhead(1) == .operator_bang) { + const asterisk = try analyzer.expectToken(.operator_asterisk); + analyzer.consumeToken(); + const type_node = try analyzer.suffixExpression(); - const error_union = try analyzer.addNode(.{ - .id = .error_union, - .token = error_union_token, - .left = all_errors_node, - .right = type_node, - }); - return error_union; - } else { - unreachable; - } + const all_errors_node = try analyzer.addNode(.{ + .id = .all_errors, + .token = asterisk, + .left = .null, + .right = .null, + }); + + const error_union = try analyzer.addNode(.{ + .id = .error_union, + .token = asterisk, + // All errors + .left = all_errors_node, + .right = type_node, + }); + return error_union; } else { const suffix_expression = try analyzer.suffixExpression(); return switch (analyzer.peekToken()) { - .operator_bang => unreachable, + .operator_bang => try analyzer.addNode(.{ + .id = .error_union, + .token = blk: { + analyzer.consumeToken(); + break :blk initial; + }, + .left = suffix_expression, + .right = try analyzer.typeExpression(), + }), else => suffix_expression, }; } @@ -1598,8 +1616,9 @@ const Analyzer = struct { }); } - fn processContainerType(analyzer: *Analyzer, maybe_token_id: ?Token.Id) !Node.Index{ + fn processContainerType(analyzer: *Analyzer, maybe_token_id: ?Token.Id) !Node.Index { const token_i = if (maybe_token_id) |tid| try analyzer.expectToken(tid) else analyzer.token_i; + assert(Token.unwrap(analyzer.token_i) < analyzer.token_buffer.tokens.len); const token_id = maybe_token_id orelse .fixed_keyword_struct; const container_type: Compilation.ContainerType = switch (token_id) { .fixed_keyword_struct => .@"struct", @@ -1608,8 +1627,8 @@ const Analyzer = struct { }; const node_id: Node.Id = switch (token_id) { - .fixed_keyword_struct => .@"struct_type", - .fixed_keyword_enum => .@"enum_type", + .fixed_keyword_struct => .struct_type, + .fixed_keyword_enum => .enum_type, else => unreachable, }; @@ -1710,7 +1729,7 @@ const Analyzer = struct { const member_node = analyzer.nodes.get(member_node_index); if (member_node.id == .identifier) { const token_offset = analyzer.getTokenOffset(member_node.token); - std.debug.print("Node index #{} (list index {}):\n```\n{s}\n```\n", .{Node.unwrap(member_node_index), index, analyzer.source_file[token_offset..]}); + std.debug.print("Node index #{} (list index {}):\n```\n{s}\n```\n", .{ Node.unwrap(member_node_index), index, analyzer.source_file[token_offset..] }); // std.debug.print("ID: {s}\n", .{analyzer.bytes(member_node.token)}); unreachable; } @@ -1726,7 +1745,16 @@ const Analyzer = struct { fn testDeclaration(analyzer: *Analyzer) !Node.Index { const test_token = try analyzer.expectToken(.fixed_keyword_test); - const name_node: Node.Index = if (analyzer.peekToken() == .string_literal) try analyzer.identifierNode() else .null; + const name_node: Node.Index = if (analyzer.peekToken() == .string_literal) try analyzer.addNode(.{ + .id = .string_literal, + .token = b: { + const index = analyzer.token_i; + analyzer.consumeToken(); + break :b index; + }, + .left = .null, + .right = .null, + }) else .null; const test_block = try analyzer.block(); return try analyzer.addNode(.{ .token = test_token, @@ -1861,6 +1889,33 @@ const Analyzer = struct { _ = try analyzer.expectToken(.operator_right_parenthesis); break :blk expr; }, + .fixed_keyword_error => blk: { + analyzer.consumeToken(); + if (analyzer.peekToken() == .operator_left_brace) { + analyzer.consumeToken(); + var list = ArrayList(Node.Index){}; + + while (analyzer.peekToken() != .operator_right_brace) { + const identifier = try analyzer.identifierNode(); + try list.append(analyzer.allocator, identifier); + const comma = try analyzer.expectToken(.operator_comma); + _ = comma; // autofix + } + + analyzer.consumeToken(); + + break :blk try analyzer.addNode(.{ + .id = .error_type, + .token = token_i, + .left = try analyzer.nodeList(list), + .right = .null, + }); + } else { + const t = analyzer.peekToken(); + _ = t; // autofix + unreachable; + } + }, else => |t| switch (t) { .identifier => std.debug.panic("{s}: {s}", .{ @tagName(t), analyzer.bytes(token_i) }), else => @panic(@tagName(t)), @@ -2031,7 +2086,6 @@ const Analyzer = struct { } }; - // Here it is assumed that left brace is consumed pub fn analyze(allocator: Allocator, lexer_result: lexer.Result, source_file: []const u8, token_buffer: *Token.Buffer, node_list: *Node.List, node_lists: *ArrayList(ArrayList(Node.Index))) !Result { const start = std.time.Instant.now() catch unreachable; @@ -2065,5 +2119,3 @@ const Associativity = enum { none, left, }; - - diff --git a/lib/std/builtin.nat b/lib/std/builtin.nat index abfc005..b127fea 100644 --- a/lib/std/builtin.nat +++ b/lib/std/builtin.nat @@ -30,5 +30,5 @@ const panic = fn (reason: PanicReason) noreturn{ const TestFunction = struct{ name: []const u8, - function: &const fn () !*void, + function: &const fn () *!void, }; diff --git a/lib/std/start.nat b/lib/std/start.nat index 2dc8834..9028025 100644 --- a/lib/std/start.nat +++ b/lib/std/start.nat @@ -31,8 +31,8 @@ const start :: export = fn (argc_argv_address: usize) noreturn { argument_address_iterator += #size(usize) * (argument_count + 1); environment_values = #cast(argument_address_iterator); const env = environment_values; - const result = #import("main").main(); - std.os.exit(exit_code = result); + #import("root").main() catch std.os.exit(1); + std.os.exit(0); } const main :: export = fn (argc: s32, argv: [&]const [&:0]const u8, env: [&:null]const ?[&:null]const u8) s32 { @@ -40,5 +40,6 @@ const main :: export = fn (argc: s32, argv: [&]const [&:0]const u8, env: [&:null argument_count = argc_u; argument_values = argv; environment_values = env; - return #import("main").main(); + #import("root").main() catch return 1; + return 0; } diff --git a/lib/test_runner.nat b/lib/test_runner.nat new file mode 100644 index 0000000..e16290f --- /dev/null +++ b/lib/test_runner.nat @@ -0,0 +1,6 @@ +const builtin = #import("builtin"); +const main = fn () *!void { + for (builtin.test_functions) |test_function| { + try test_function.function(); + } +} diff --git a/test/build/first/src/main.nat b/test/build/first/src/main.nat deleted file mode 100644 index 48de37a..0000000 --- a/test/build/first/src/main.nat +++ /dev/null @@ -1,3 +0,0 @@ -const main = fn () s32 { - return 0; -} diff --git a/test/build/first/.gitignore b/test/build/first_build/.gitignore similarity index 100% rename from test/build/first/.gitignore rename to test/build/first_build/.gitignore diff --git a/test/build/first/build.nat b/test/build/first_build/build.nat similarity index 73% rename from test/build/first/build.nat rename to test/build/first_build/build.nat index 74ae495..d44f931 100644 --- a/test/build/first/build.nat +++ b/test/build/first_build/build.nat @@ -2,7 +2,11 @@ const std = #import("std"); const assert = std.assert; const Executable = std.build.Executable; -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { const executable = Executable{ .target = .{ .cpu = .x86_64, @@ -10,13 +14,12 @@ const main = fn () s32 { .abi = .gnu, }, .main_source_path = "src/main.nat", - .name = "first", + .name = "first_build", }; if (executable.compile()) { - return 0; } else { std.print(bytes = "Executable failed to compile!\n"); - return 1; + return Error.unexpected_result; } } diff --git a/test/build/first_build/src/main.nat b/test/build/first_build/src/main.nat new file mode 100644 index 0000000..3b1da1f --- /dev/null +++ b/test/build/first_build/src/main.nat @@ -0,0 +1,2 @@ +const main = fn () *!void { +} diff --git a/test/build/link_libc/build.nat b/test/build/link_libc/build.nat index f2bd063..f94e8e2 100644 --- a/test/build/link_libc/build.nat +++ b/test/build/link_libc/build.nat @@ -2,7 +2,11 @@ const std = #import("std"); const assert = std.assert; const Executable = std.build.Executable; -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { const executable = Executable{ .target = .{ .cpu = .x86_64, @@ -15,9 +19,8 @@ const main = fn () s32 { }; if (executable.compile()) { - return 0; } else { std.print(bytes = "Executable failed to compile!\n"); - return 1; + return Error.unexpected_result; } } diff --git a/test/build/link_libc/src/main.nat b/test/build/link_libc/src/main.nat index 48de37a..3b1da1f 100644 --- a/test/build/link_libc/src/main.nat +++ b/test/build/link_libc/src/main.nat @@ -1,3 +1,2 @@ -const main = fn () s32 { - return 0; +const main = fn () *!void { } diff --git a/test/standalone/add_sub/main.nat b/test/standalone/add_sub/main.nat index 001fd3e..08e7c63 100644 --- a/test/standalone/add_sub/main.nat +++ b/test/standalone/add_sub/main.nat @@ -1,8 +1,14 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { const a: s32 = 1; const b: s32 = 2; const c: s32 = a + b; const d: s32 = 3; const e: s32 = d - c; - return e; + if (e != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/and/main.nat b/test/standalone/and/main.nat index 264cab0..cd36d20 100644 --- a/test/standalone/and/main.nat +++ b/test/standalone/and/main.nat @@ -1,6 +1,11 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; +const main = fn() Error!void { var a: s32 = 5; var b: s32 = 4; var result = a & b; - return result - b; + if (result - b != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/array_pointer_store/main.nat b/test/standalone/array_pointer_store/main.nat index 9eb4251..a9ac465 100644 --- a/test/standalone/array_pointer_store/main.nat +++ b/test/standalone/array_pointer_store/main.nat @@ -1,10 +1,15 @@ -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { const ch = 'a'; var buffer: [1]u8 = undefined; var ptr: &[1]u8 = buffer.&; var index: usize = 0; ptr[index] = ch; const sub: u8 = ptr[index] - ch; - const result: u32 = sub; - return #cast(result); + if (sub != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/array_pointer_store_function/main.nat b/test/standalone/array_pointer_store_function/main.nat index 00c7cc7..3cce882 100644 --- a/test/standalone/array_pointer_store_function/main.nat +++ b/test/standalone/array_pointer_store_function/main.nat @@ -1,4 +1,8 @@ -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { var buffer: [2]u8 = undefined; const expected = 'a'; const index: usize = 1; @@ -6,7 +10,9 @@ const main = fn () s32 { const ch = buffer[index]; const sub = expected - ch; const result: u32 = sub; - return #cast(result); + if (result != 0) { + return Error.unexpected_result; + } } const foo = fn (buffer: &[2]u8, index: usize, ch: u8) void { diff --git a/test/standalone/array_store/main.nat b/test/standalone/array_store/main.nat index d21022d..01dec7d 100644 --- a/test/standalone/array_store/main.nat +++ b/test/standalone/array_store/main.nat @@ -1,9 +1,13 @@ -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { const ch = 'a'; var buffer: [1]u8 = undefined; var index: usize = 0; buffer[index] = ch; - const sub: u8 = buffer[index] - ch; - const result: u32 = sub; - return #cast(result); + if (buffer[index] - ch != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/assert/main.nat b/test/standalone/assert/main.nat index 93f3035..63babe3 100644 --- a/test/standalone/assert/main.nat +++ b/test/standalone/assert/main.nat @@ -1,10 +1,9 @@ const std = #import("std"); const assert = std.assert; -const main = fn() s32 { +const main = fn() *!void { assert(true); var a: s32 = 1; const is_not_one = a != 1; assert(!is_not_one); - return 0; } diff --git a/test/standalone/bit_struct/main.nat b/test/standalone/bit_struct/main.nat index dfa8c7f..04b1132 100644 --- a/test/standalone/bit_struct/main.nat +++ b/test/standalone/bit_struct/main.nat @@ -8,7 +8,7 @@ const BitStruct = struct(u8) { d: u5, }; -const main = fn () s32 { +const main = fn () *!void { var bs = BitStruct{ .a = false, .b = true, @@ -26,5 +26,4 @@ const main = fn () s32 { }; const bitcast_const_bs: u8 = #cast(const_bs); assert(bitcast_const_bs == 5); - return 0; } diff --git a/test/standalone/bit_struct_call/main.nat b/test/standalone/bit_struct_call/main.nat index fdec759..5de6134 100644 --- a/test/standalone/bit_struct_call/main.nat +++ b/test/standalone/bit_struct_call/main.nat @@ -17,7 +17,7 @@ const transform = fn (a: A) B { }; } -const main = fn () s32 { +const main = fn () *!void { var a = A{ .a = 3, .b = 8, @@ -26,6 +26,4 @@ const main = fn () s32 { const b = transform(a); assert(a.a == b.a); assert(a.b == b.b); - - return 0; } diff --git a/test/standalone/break/main.nat b/test/standalone/break/main.nat index 16281d1..9db2594 100644 --- a/test/standalone/break/main.nat +++ b/test/standalone/break/main.nat @@ -1,4 +1,7 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; +const main = fn() Error!void { var i: s32 = 0; const j: s32 = 5; for (0..10) |_| { @@ -8,5 +11,7 @@ const main = fn() s32 { i += 1; } - return i - j; + if (i - j != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/conditional_compilation_basic/main.nat b/test/standalone/conditional_compilation_basic/main.nat index 379fbbe..d5de24d 100644 --- a/test/standalone/conditional_compilation_basic/main.nat +++ b/test/standalone/conditional_compilation_basic/main.nat @@ -1,7 +1,10 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { if (true) { - return 0; } else { - return 1; + return Error.unexpected_result; } } diff --git a/test/standalone/div/main.nat b/test/standalone/div/main.nat index a319afc..0e0827a 100644 --- a/test/standalone/div/main.nat +++ b/test/standalone/div/main.nat @@ -1,8 +1,13 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; +const main = fn() Error!void { const dividend: s32 = 30; const divisor: s32 = 6; const div: s32 = dividend / divisor; const n: s32 = 5; - return n - div; + if (n - div != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/else_if/main.nat b/test/standalone/else_if/main.nat index 07ad19e..a073302 100644 --- a/test/standalone/else_if/main.nat +++ b/test/standalone/else_if/main.nat @@ -1,13 +1,20 @@ -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { const a = foo(5); if (a != 123) { - return 1; + return Error.unexpected_result; } const b = foo(5); if (b != 123) { - return 1; + return Error.unexpected_result; + } + + if (a - b != 0) { + return Error.unexpected_result; } - return a - b; } const foo = fn (arg: s32) s32 { diff --git a/test/standalone/first/main.nat b/test/standalone/first/main.nat index 34ec86b..d2e61c2 100644 --- a/test/standalone/first/main.nat +++ b/test/standalone/first/main.nat @@ -1,3 +1 @@ -const main = fn() s32 { - return 0; -} +const main = fn() *!void { } diff --git a/test/standalone/foreach/main.nat b/test/standalone/foreach/main.nat index cf91cd7..c32ce7a 100644 --- a/test/standalone/foreach/main.nat +++ b/test/standalone/foreach/main.nat @@ -1,4 +1,8 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { var counter: s32 = 0; const loop = 10; @@ -6,5 +10,7 @@ const main = fn() s32 { counter += 1; } - return loop - counter; + if (loop - counter != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/foreach_slice/main.nat b/test/standalone/foreach_slice/main.nat index e1d34cd..de1d761 100644 --- a/test/standalone/foreach_slice/main.nat +++ b/test/standalone/foreach_slice/main.nat @@ -20,12 +20,17 @@ const print_values = fn(slice: []const u8) void { } } -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { const a = [_]u8{1, 1, 4, 5, 6}; const b = [_]u8{1, 4, 6}; const expected_result: usize = a.len + b.len; const result = count_slice_byte_count(slices = .{a.&, b.&}.&); print_values(slice = a.&); - const main_result: u32 = #cast(expected_result - result); - return #cast(main_result); + if (expected_result - result != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/fork/main.nat b/test/standalone/fork/main.nat index dfa54be..2236d85 100644 --- a/test/standalone/fork/main.nat +++ b/test/standalone/fork/main.nat @@ -1,16 +1,18 @@ +const Error = error{ + unexpected_result, +}; const std = #import("std"); -const main = fn() s32 { +const main = fn() Error!void { if (std.os.duplicate_process()) |pid| { if (pid == 0) { std.print(bytes = "Hello from child\n"); std.os.exit(exit_code = 0); } else { std.print(bytes = "Hello from parent\n"); - return 0; } } else { std.print(bytes = "Unable to create child process\n"); - return 1; + return Error.unexpected_result; } } diff --git a/test/standalone/fork_exec/main.nat b/test/standalone/fork_exec/main.nat index 8f52b08..1c02fef 100644 --- a/test/standalone/fork_exec/main.nat +++ b/test/standalone/fork_exec/main.nat @@ -1,18 +1,21 @@ const std = #import("std"); -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { if (std.os.duplicate_process()) |pid| { if (pid == 0) { std.print(bytes = "Hello from child\n"); const argv = [_:null] ?[&:0]const u8{"/usr/bin/ls"}; _ = std.os.execute(path = "/usr/bin/ls", argv = argv.&, env = std.start.environment_values); - return 1; + return Error.unexpected_result; } else { std.print(bytes = "Hello from parent\n"); - return 0; } } else { std.print(bytes = "Unable to create child process\n"); - return 1; + return Error.unexpected_result; } } diff --git a/test/standalone/function_pointer/main.nat b/test/standalone/function_pointer/main.nat index 43f0eb8..e0137d7 100644 --- a/test/standalone/function_pointer/main.nat +++ b/test/standalone/function_pointer/main.nat @@ -4,8 +4,14 @@ const foo = fn () s32 { return expected_number; } -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { var function_pointer = foo.&; const result = function_pointer(); - return result - expected_number; + if (result - expected_number != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/function_pointer_struct/main.nat b/test/standalone/function_pointer_struct/main.nat index af2ce36..88a0d01 100644 --- a/test/standalone/function_pointer_struct/main.nat +++ b/test/standalone/function_pointer_struct/main.nat @@ -13,11 +13,17 @@ const Struct = struct{ } }; -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { var s = Struct{ .a = expected_number, .handler = Struct.handler_function.&, }; - return s.handler(s.&) - expected_number; + if (s.handler(s.&) - expected_number != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/hello_world/main.nat b/test/standalone/hello_world/main.nat index 6392359..f541a19 100644 --- a/test/standalone/hello_world/main.nat +++ b/test/standalone/hello_world/main.nat @@ -1,6 +1,5 @@ const std = #import("std"); -const main = fn() s32 { +const main = fn() *!void { std.print(bytes = "Hello world!\n"); - return 0; } diff --git a/test/standalone/if/main.nat b/test/standalone/if/main.nat index 0042318..47e7dcb 100644 --- a/test/standalone/if/main.nat +++ b/test/standalone/if/main.nat @@ -1,13 +1,18 @@ -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; +const main = fn () Error!void { const a = foo(5); if (a != 6) { - return 1; + return Error.unexpected_result; } const b = foo(5); if (b != 6) { - return 1; + return Error.unexpected_result; + } + if (a - b != 0) { + return Error.unexpected_result; } - return a - b; } const foo = fn (arg: s32) s32 { diff --git a/test/standalone/if_not_else/main.nat b/test/standalone/if_not_else/main.nat index d538365..d761c4b 100644 --- a/test/standalone/if_not_else/main.nat +++ b/test/standalone/if_not_else/main.nat @@ -1,13 +1,20 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { const a = foo(5); if (a != 12412) { - return 1; + return Error.unexpected_result; } const b = foo(5); if (b != 12412) { - return 1; + return Error.unexpected_result; + } + + if (a - b != 0) { + return Error.unexpected_result; } - return a - b; } const foo = fn(arg: s32) s32 { diff --git a/test/standalone/imul/main.nat b/test/standalone/imul/main.nat index 8033281..d49696f 100644 --- a/test/standalone/imul/main.nat +++ b/test/standalone/imul/main.nat @@ -1,5 +1,10 @@ -const main = fn() s32 { - const a: s32 = 5; - const b: s32 = 4; - return a * b - a * b; +const Error = error{ + unexpected_result, +}; +const main = fn() Error!void { + var a: s32 = 5; + var b: s32 = 4; + if (a * b - a * b != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/loop_break/main.nat b/test/standalone/loop_break/main.nat index ec8220a..e25df21 100644 --- a/test/standalone/loop_break/main.nat +++ b/test/standalone/loop_break/main.nat @@ -1,4 +1,8 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { var i: s32 = 0; while (i < 10) { i += 1; @@ -7,5 +11,7 @@ const main = fn() s32 { } } - return i - 5; + if (i - 5 != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/loop_return/main.nat b/test/standalone/loop_return/main.nat index 96066a4..8057030 100644 --- a/test/standalone/loop_return/main.nat +++ b/test/standalone/loop_return/main.nat @@ -1,13 +1,19 @@ -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { const a = foo(5142); if (a != 2501) { - return 1; + return Error.unexpected_result; } const b = foo(5142); if (b != 2501) { - return 1; + return Error.unexpected_result; + } + if (a - b != 0) { + return Error.unexpected_result; } - return #cast(a - b); } const foo = fn (arg: u32) u32 { diff --git a/test/standalone/loop_return_only_else/main.nat b/test/standalone/loop_return_only_else/main.nat index eb42d29..16079fe 100644 --- a/test/standalone/loop_return_only_else/main.nat +++ b/test/standalone/loop_return_only_else/main.nat @@ -1,7 +1,12 @@ -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; +const main = fn () Error!void { const a = foo(5142); const b = foo(5142); - return #cast(a - b); + if (a - b != 0) { + return Error.unexpected_result; + } } const foo = fn (arg: u32) u32 { diff --git a/test/standalone/nested_if/main.nat b/test/standalone/nested_if/main.nat index 98b6427..8565cbd 100644 --- a/test/standalone/nested_if/main.nat +++ b/test/standalone/nested_if/main.nat @@ -1,7 +1,13 @@ -const main = fn () s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { const a = foo(5); const b = foo(5); - return a - b; + if (a - b != 0) { + return Error.unexpected_result; + } } const foo = fn (arg: s32) s32 { diff --git a/test/standalone/null_terminated/main.nat b/test/standalone/null_terminated/main.nat index faeab4e..378a830 100644 --- a/test/standalone/null_terminated/main.nat +++ b/test/standalone/null_terminated/main.nat @@ -1,7 +1,6 @@ const std = #import("std"); const assert = std.assert; -const main = fn() s32 { +const main = fn() *!void { var foo = [2:null] ?[&:0]const u8 {"Hi", "Ho"}; assert(foo[2] == null); - return 0; } diff --git a/test/standalone/optional_wrap/main.nat b/test/standalone/optional_wrap/main.nat index d6dbe76..0feecba 100644 --- a/test/standalone/optional_wrap/main.nat +++ b/test/standalone/optional_wrap/main.nat @@ -1,8 +1,6 @@ const foo = fn(slice: []u8) ?[]u8 { return slice[0..1]; } -const main = fn() s32 { +const main = fn() *!void { _ = foo(slice = .{ 0, 1, 2, 3 }.&); - - return 0; } diff --git a/test/standalone/or/main.nat b/test/standalone/or/main.nat index 5b9b4ea..3681d5d 100644 --- a/test/standalone/or/main.nat +++ b/test/standalone/or/main.nat @@ -1,7 +1,13 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { const a: u32 = 0xffff; const b: u32 = 0xffff0000; const c: u32 = 0xffffffff; const result = c - (a | b); - return #cast(result); + if (result != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/self_exe_path/main.nat b/test/standalone/self_exe_path/main.nat index a0f0def..525852d 100644 --- a/test/standalone/self_exe_path/main.nat +++ b/test/standalone/self_exe_path/main.nat @@ -1,14 +1,16 @@ const std = #import("std"); const print = std.print; -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { var buffer: [std.os.max_path_byte_count:0]u8 = undefined; if (std.os.current_executable_path(buffer = buffer.&)) |bytes| { print(bytes); print(bytes = "\n"); - return 0; } else { - print(bytes = "Failed\n"); - return 1; + return Error.unexpected_result; } } diff --git a/test/standalone/shifts/main.nat b/test/standalone/shifts/main.nat index bd19287..ffcd804 100644 --- a/test/standalone/shifts/main.nat +++ b/test/standalone/shifts/main.nat @@ -1,7 +1,13 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { var x: u32 = 1; x = x << 5; x = x >> 5; const b: u32 = 1; - return #cast(x - b); + if (x - b != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/simple_bool/main.nat b/test/standalone/simple_bool/main.nat index 65c1549..963bcfa 100644 --- a/test/standalone/simple_bool/main.nat +++ b/test/standalone/simple_bool/main.nat @@ -1,8 +1,10 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { var false_boolean: bool = false; if (false_boolean) { - return 1; - } else { - return 0; + return Error.unexpected_result; } } diff --git a/test/standalone/size/main.nat b/test/standalone/size/main.nat index a73cfc8..52e6713 100644 --- a/test/standalone/size/main.nat +++ b/test/standalone/size/main.nat @@ -1,6 +1,9 @@ -const std = #import("std"); -const assert = std.assert; -const main = fn () s32 { - assert(#size(usize) == 8); - return 0; +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { + if (#size(usize) != 8) { + return Error.unexpected_result; + } } diff --git a/test/standalone/slice/main.nat b/test/standalone/slice/main.nat index 43e53d4..cde7c7c 100644 --- a/test/standalone/slice/main.nat +++ b/test/standalone/slice/main.nat @@ -1,4 +1,8 @@ -const main = fn () s32{ +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void{ const a: [4]u8 = .{1, 2, 3, 4}; const b: []const []const u8 = .{a.&}.&; @@ -10,5 +14,7 @@ const main = fn () s32{ } } - return #cast(sum - 10); + if (sum - 10 != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/slice2/main.nat b/test/standalone/slice2/main.nat index 1cd2718..1c94624 100644 --- a/test/standalone/slice2/main.nat +++ b/test/standalone/slice2/main.nat @@ -1,7 +1,12 @@ -const main = fn () s32{ +const Error = error{ + unexpected_result, +}; + +const main = fn () Error!void { const a: [4]u8 = .{1, 2, 3, 4}; const b: []const []const u8 = .{a.&}.&; const c: u8 = b[0][0] - 1; - const d: u32 = c; - return #cast(c); + if (c != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/slice_expression/main.nat b/test/standalone/slice_expression/main.nat index 5fd368d..3bb6658 100644 --- a/test/standalone/slice_expression/main.nat +++ b/test/standalone/slice_expression/main.nat @@ -1,6 +1,5 @@ -const main = fn () s32 { +const main = fn () *!void { _ = foo(); - return 0; } const foo = fn () []u8 { diff --git a/test/standalone/slice_len/main.nat b/test/standalone/slice_len/main.nat index 4cae0c7..4ab85ee 100644 --- a/test/standalone/slice_len/main.nat +++ b/test/standalone/slice_len/main.nat @@ -1,12 +1,18 @@ const std = #import("std"); const assert = std.assert; -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { var buffer: [65]u8 = undefined; const slice = foo(5, buffer.&); assert(slice.len + 5 == buffer.len); const result: u32 = #cast(slice.len + 5 - buffer.len); - return #cast(result); + if (result != 0) { + return Error.unexpected_result; + } } const foo = fn(n: usize, buffer: &[65]u8) []u8 { return buffer[n..]; diff --git a/test/standalone/stack/main.nat b/test/standalone/stack/main.nat index 4574781..2ab9f44 100644 --- a/test/standalone/stack/main.nat +++ b/test/standalone/stack/main.nat @@ -1,4 +1,9 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; +const main = fn() Error!void { var a : s32 = 0; - return a; + if (a != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/virtual_memory/main.nat b/test/standalone/virtual_memory/main.nat index 6fcbf13..12ae696 100644 --- a/test/standalone/virtual_memory/main.nat +++ b/test/standalone/virtual_memory/main.nat @@ -1,6 +1,10 @@ const std = #import("std"); -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { const size = 0x1000; if (std.page_allocator.allocate(size, alignment = 12)) |result| { @@ -8,13 +12,12 @@ const main = fn() s32 { std.print(bytes = "Allocation succeeded. Freeing...\n"); if (std.page_allocator.free(bytes_ptr = result.ptr, bytes_len = result.len)) { std.print(bytes = "Memory freed successfully\n"); - return 0; } else { std.print(bytes = "Memory freed with errors\n"); - return 1; + return Error.unexpected_result; } } else { std.print(bytes = "Allocation failed!\n"); - return 1; + return Error.unexpected_result; } } diff --git a/test/standalone/xor/main.nat b/test/standalone/xor/main.nat index dbf8f52..1aabb34 100644 --- a/test/standalone/xor/main.nat +++ b/test/standalone/xor/main.nat @@ -1,7 +1,13 @@ -const main = fn() s32 { +const Error = error{ + unexpected_result, +}; + +const main = fn() Error!void { var a: s32 = 561; var b: s32 = 124; var c: s32 = a ^ b; var d: s32 = a ^ b; - return c ^ d; + if (c ^ d != 0) { + return Error.unexpected_result; + } } diff --git a/test/standalone/zero_terminated/main.nat b/test/standalone/zero_terminated/main.nat index 02520e6..e36850a 100644 --- a/test/standalone/zero_terminated/main.nat +++ b/test/standalone/zero_terminated/main.nat @@ -1,7 +1,6 @@ const std = #import("std"); const assert = std.assert; -const main = fn () s32 { +const main = fn () *!void { var a = [2:0]u8 {1, 2}; assert(a[2] == 0); - return 0; } diff --git a/test/tests/first_test/main.nat b/test/tests/first_test/main.nat new file mode 100644 index 0000000..6bf031f --- /dev/null +++ b/test/tests/first_test/main.nat @@ -0,0 +1,2 @@ +test "First test" { +} diff --git a/test/tests/main.nat b/test/tests/main.nat deleted file mode 100644 index 071e8c6..0000000 --- a/test/tests/main.nat +++ /dev/null @@ -1,2 +0,0 @@ -test { -}