From 203bee684caf62256c6ae0a1f0b73f215b2e3833 Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Thu, 11 Apr 2024 08:38:00 -0600 Subject: [PATCH] Write initial self-hosted code --- bootstrap/Compilation.zig | 285 ++++++++++++++++++++++++---- bootstrap/backend/llvm.zig | 30 ++- bootstrap/frontend/parser.zig | 61 +++++- build.zig | 6 +- lib/std/c.nat | 15 +- lib/std/std.nat | 24 ++- src/main.nat | 94 ++++++++- test/standalone/byte_equal/main.nat | 26 +++ test/standalone/c_string/main.nat | 8 + test/standalone/orelse/main.nat | 14 ++ 10 files changed, 499 insertions(+), 64 deletions(-) create mode 100644 test/standalone/byte_equal/main.nat create mode 100644 test/standalone/c_string/main.nat create mode 100644 test/standalone/orelse/main.nat diff --git a/bootstrap/Compilation.zig b/bootstrap/Compilation.zig index 2d99062..288afb1 100644 --- a/bootstrap/Compilation.zig +++ b/bootstrap/Compilation.zig @@ -3791,6 +3791,7 @@ pub const Instruction = union(enum) { pointer_const_to_var, pointer_to_nullable, pointer_source_type_to_destination_type, + pointer_to_not_nullable, slice_var_to_const, slice_to_nullable, slice_to_not_null, @@ -6267,6 +6268,7 @@ pub const Builder = struct { }, else => |t| @panic(@tagName(t)), }; + assert(expected_right_type != .null); const right = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = expected_right_type }, node.right, .right); const value_to_store = switch (node.id) { .assign => right, @@ -8691,11 +8693,12 @@ pub const Builder = struct { } const cbb = unit.basic_blocks.get(builder.current_basic_block); - const return_type = function_prototype.return_type; + const return_type_index = function_prototype.return_type; + const return_type = unit.types.get(return_type_index); if (!cbb.terminated and (cbb.instructions.length > 0 or cbb.predecessors.length > 0)) { if (builder.return_block == .null) { - switch (unit.types.get(return_type).*) { + switch (return_type.*) { .void => try builder.buildRet(unit, context, .{ .value = .{ .@"comptime" = .void, @@ -8713,7 +8716,7 @@ pub const Builder = struct { .value = .{ .@"comptime" = .undefined, }, - .type = return_type, + .type = return_type_index, }; const insert = try unit.instructions.append(context.my_allocator, .{ .insert_value = .{ @@ -8735,7 +8738,7 @@ pub const Builder = struct { .value = .{ .runtime = insert, }, - .type = return_type, + .type = return_type_index, }); }, else => unreachable, @@ -8752,7 +8755,8 @@ pub const Builder = struct { const phi = &unit.instructions.get(builder.return_phi).phi; - switch (unit.types.get(return_type).*) { + + switch (return_type.*) { .void => unreachable, .noreturn => unreachable, .@"struct" => |struct_index| switch (unit.structs.get(struct_index).kind) { @@ -8767,7 +8771,7 @@ pub const Builder = struct { .value = .{ .@"comptime" = .undefined, }, - .type = return_type, + .type = return_type_index, }, .index = 1, .new_value = .{ @@ -8786,7 +8790,7 @@ pub const Builder = struct { .value = .{ .runtime = return_value, }, - .type = return_type, + .type = return_type_index, }, builder.current_basic_block); try builder.jump(unit, context, builder.return_block); @@ -9169,9 +9173,6 @@ pub const Builder = struct { .none => b: { const pointer_type = unit.types.get(pointer_value.type); const pointer_element_type = pointer_type.pointer.type; - switch (unit.types.get(pointer_element_type).*) { - else => |t| @panic(@tagName(t)), - } break :b pointer_element_type; }, .type => |type_index| type_index, @@ -9358,6 +9359,7 @@ pub const Builder = struct { .shift_left => .shift_left, .shift_right => .shift_right, .bool_and => .bit_and, + .bool_or => .bit_or, else => |t| @panic(@tagName(t)), }; @@ -9536,6 +9538,7 @@ pub const Builder = struct { .bool => b: { const id: Instruction.IntegerBinaryOperation.Id = switch (binary_operation_id) { .bit_and => .bit_and, + .bit_or => .bit_or, else => |t| @panic(@tagName(t)), }; const i = try unit.instructions.append(context.my_allocator, .{ @@ -9607,17 +9610,18 @@ pub const Builder = struct { break :block result; }, .block => block: { - assert(type_expect == .none or type_expect.type == .void); const block = try builder.resolveBlock(unit, context, node_index); const block_i = try unit.instructions.append(context.my_allocator, .{ .block = block, }); - break :block .{ .value = .{ .runtime = block_i, }, - .type = .void, + .type = if (type_expect == .none or type_expect.type == .void) .void else b: { + assert(unit.basic_blocks.get( builder.current_basic_block).terminated); + break :b .noreturn; + }, }; }, .container_literal => block: { @@ -9688,8 +9692,23 @@ pub const Builder = struct { break :blk nullable_pointer; }, }, - .@"struct" => |struct_index| switch (unit.structs.get(struct_index).kind) { - else => |t| @panic(@tagName(t)), + .slice => |slice| if (slice.nullable) b: { + const constant_slice = try unit.constant_slices.append(context.my_allocator, .{ + .array = null, + .start = 0, + .end = 0, + .type = type_index, + }); + break :b V{ + .value = .{ + .@"comptime" = .{ + .constant_slice = constant_slice, + }, + }, + .type = type_index, + }; + } else { + unreachable; }, else => |t| @panic(@tagName(t)), }, @@ -9698,7 +9717,8 @@ pub const Builder = struct { .slice => block: { const expression_to_slice = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, node.left, .left); - const range_node = unit.getNode(node.right); + const slice_metadata_node = unit.getNode(node.right); + const range_node = unit.getNode(slice_metadata_node.left); assert(range_node.id == .range); const range_start: V = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = Type.usize }, range_node.left, .right); const range_end: V = switch (range_node.right) { @@ -11182,14 +11202,174 @@ pub const Builder = struct { else => unreachable, }, unreachable, &.{}), }, - .empty_container_literal_guess => { + .empty_container_literal_guess => block: { assert(node.left != .null); assert(node.right != .null); const container_type = try builder.resolveType(unit, context, node.left, &.{}); const node_list = unit.getNodeList(node.right); assert(node_list.len == 0); const result = try builder.resolveContainerLiteral(unit, context, node_list, container_type); - return result; + break :block result; + }, + .if_else_payload => block: { + assert(node.left != .null); + assert(node.right != .null); + + const if_else_node = unit.getNode(node.left); + assert(if_else_node.id == .if_else); + assert(if_else_node.left != .null); + assert(if_else_node.right != .null); + + const if_node = unit.getNode(if_else_node.left); + assert(if_node.id == .@"if"); + assert(if_node.left != .null); + assert(if_node.right != .null); + + try builder.resolveBranchPayload(unit, context, .{ + .payload_node_index = node.right, + .optional_node_index = if_node.left, + .taken_expression_node_index = if_node.right, + .not_taken_expression_node_index = if_else_node.right, + }); + + break :block .{ + .value = .{ + .@"comptime" = .void, + }, + .type = .void, + }; + }, + .orelse_expression => block: { + const v = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, node.left, .right); + switch (unit.types.get(v.type).*) { + .pointer => |pointer| if (pointer.nullable) { + const type_to_expect = switch (type_expect) { + .none => b: { + var p = pointer; + p.nullable = false; + const non_null_pointer = try unit.getPointerType(context, p); + break :b non_null_pointer; + }, + else => |t| @panic(@tagName(t)), + }; + const new_type_expect = Type.Expect{ .type = type_to_expect }; + const null_pointer = V{ + .type = v.type, + .value = .{ + .@"comptime" = .null_pointer, + }, + }; + const cmp = try unit.instructions.append(context.my_allocator, .{ + .integer_compare = .{ + .left = v, + .right = null_pointer, + .type = v.type, + .id = .equal, + }, + }); + try builder.appendInstruction(unit, context, cmp); + const is_null_block = try builder.newBasicBlock(unit, context); + const is_not_null_block = try builder.newBasicBlock(unit, context); + try builder.branch(unit, context, cmp, is_null_block, is_not_null_block); + + builder.current_basic_block = is_null_block; + + const else_expr = try builder.resolveRuntimeValue(unit, context, new_type_expect, node.right, .right); + _ = else_expr; // autofix + const is_block_terminated = unit.basic_blocks.get(builder.current_basic_block).terminated; + if (!is_block_terminated) { + unreachable; + } else { + builder.current_basic_block = is_not_null_block; + const cast = try unit.instructions.append(context.my_allocator, .{ + .cast = .{ + .id = .pointer_to_not_nullable, + .value = v, + .type = type_to_expect, + }, + }); + try builder.appendInstruction(unit, context, cast); + + break :block .{ + .value = .{ + .runtime = cast, + }, + .type = type_to_expect, + }; + } + } else unreachable, + .slice => |slice| if (slice.nullable) { + const type_to_expect = switch (type_expect) { + .none => b: { + var s = slice; + s.nullable = false; + const non_null_slice = try unit.getSliceType(context, s); + break :b non_null_slice; + }, + else => |t| @panic(@tagName(t)), + }; + const new_type_expect = Type.Expect{ .type = type_to_expect }; + const null_pointer = V{ + .type = slice.child_pointer_type, + .value = .{ + .@"comptime" = .null_pointer, + }, + }; + + const get_pointer = try unit.instructions.append(context.my_allocator, .{ + .extract_value = .{ + .expression = v, + .index = 0, + }, + }); + try builder.appendInstruction(unit, context, get_pointer); + const cmp = try unit.instructions.append(context.my_allocator, .{ + .integer_compare = .{ + .left = .{ + .value = .{ + .runtime = get_pointer, + }, + .type = slice.child_pointer_type, + }, + .right = null_pointer, + .type = v.type, + .id = .equal, + }, + }); + try builder.appendInstruction(unit, context, cmp); + const is_null_block = try builder.newBasicBlock(unit, context); + const is_not_null_block = try builder.newBasicBlock(unit, context); + try builder.branch(unit, context, cmp, is_null_block, is_not_null_block); + + builder.current_basic_block = is_null_block; + + const else_expr = try builder.resolveRuntimeValue(unit, context, new_type_expect, node.right, .right); + _ = else_expr; // autofix + const is_block_terminated = unit.basic_blocks.get(builder.current_basic_block).terminated; + if (!is_block_terminated) { + unreachable; + } else { + builder.current_basic_block = is_not_null_block; + const cast = try unit.instructions.append(context.my_allocator, .{ + .cast = .{ + .id = .slice_to_not_null, + .value = v, + .type = type_to_expect, + }, + }); + try builder.appendInstruction(unit, context, cast); + + break :block .{ + .value = .{ + .runtime = cast, + }, + .type = type_to_expect, + }; + } + } else unreachable, + else => |t| @panic(@tagName(t)), + } + @panic("todo"); }, else => |t| @panic(@tagName(t)), }; @@ -13239,6 +13419,7 @@ pub const Builder = struct { const is_last_element_range = last_element_node.id == .range; const not_range_len = payloads.len - @intFromBool(is_last_element_range); + if (slices.length > 0) { const load_i = try unit.instructions.append(context.my_allocator, .{ .load = .{ @@ -13316,9 +13497,16 @@ pub const Builder = struct { } } - const body_node_index = statement_node.right; + const for_expressions = unit.getNode(statement_node.right); + assert(for_expressions.id == .for_expressions); + const body_node_index = for_expressions.left; _ = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .void }, body_node_index, .right); + const else_node_index = for_expressions.right; + if (else_node_index != .null) { + unreachable; + } + const load_iterator = try unit.instructions.append(context.my_allocator, .{ .load = .{ .value = .{ @@ -13506,7 +13694,9 @@ pub const Builder = struct { }, else => node.right, }; + const v = try builder.resolveRuntimeValue(unit, context, catch_type_expect, catch_expression_node_index, side); + switch (error_union.type) { .void, .noreturn => { assert(unit.basic_blocks.get(builder.current_basic_block).terminated); @@ -13519,7 +13709,7 @@ pub const Builder = struct { phi: Instruction.Index, exit_block: BasicBlock.Index, }; - const catch_info: ?CatchInfo = if (!is_block_terminated) blk: { + const maybe_catch_info: ?CatchInfo = if (!is_block_terminated) blk: { const expected_type = error_union.type; assert(v.type == expected_type); const phi_index = try unit.instructions.append(context.my_allocator, .{ @@ -13555,12 +13745,12 @@ pub const Builder = struct { }, .type = error_union.type, }; - if (is_block_terminated) { - return value; - } else { - const phi_index = catch_info.?.phi; + + if (maybe_catch_info) |catch_info| { + assert(!is_block_terminated); + const phi_index = catch_info.phi; const phi = &unit.instructions.get(phi_index).phi; - const exit_block = catch_info.?.exit_block; + const exit_block = catch_info.exit_block; try phi.addIncoming(context, value, builder.current_basic_block); @@ -13575,6 +13765,9 @@ pub const Builder = struct { }, .type = error_union.type, }; + } else { + assert(is_block_terminated); + return value; } }, } @@ -14755,15 +14948,43 @@ pub const Builder = struct { } fn emitReturn(builder: *Builder, unit: *Unit, context: *const Context, return_node_index: Node.Index) !void { + const return_type_index = unit.getReturnType(builder.current_function); 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); + const return_value = if (return_node.left != .null) b: { + const return_value_node_index = return_node.left; + const return_value = try builder.resolveRuntimeValue(unit, context, Type.Expect{ + .type = return_type_index, + }, return_value_node_index, .right); + break :b return_value; + } else switch (unit.types.get(return_type_index).*) { + .@"struct" => |struct_index| switch (unit.structs.get(struct_index).kind) { + .error_union => |error_union| if (error_union.type == .void) b: { + const fields = &[_]V.Comptime{ + .undefined, + .{ + .bool = false, + }, + }; + const constant_struct = try unit.constant_structs.append(context.my_allocator, .{ + .fields = fields, + .type = return_type_index, + }); + + break :b V{ + .type = return_type_index, + .value = .{ + .@"comptime" = .{ + .constant_struct = constant_struct, + }, + }, + }; + } else unreachable, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + }; if (builder.return_block != .null) { if (builder.return_phi != .null) { @@ -14777,7 +14998,7 @@ pub const Builder = struct { } else if (builder.exit_blocks.length > 0) { builder.return_phi = try unit.instructions.append(context.my_allocator, .{ .phi = .{ - .type = return_type, + .type = return_type_index, }, }); diff --git a/bootstrap/backend/llvm.zig b/bootstrap/backend/llvm.zig index 6f9233b..d0c15b2 100644 --- a/bootstrap/backend/llvm.zig +++ b/bootstrap/backend/llvm.zig @@ -2051,7 +2051,7 @@ pub const LLVM = struct { break :b slice_fields; } else b: { - const ptr = llvm.pointer_type.?.toType().getPoison() orelse unreachable; + const ptr = llvm.pointer_type.?.getNull(); const len = llvm.context.getConstantInt(64, 0, false) orelse unreachable; break :b .{ ptr.toConstant(), len.toConstant() }; }; @@ -2094,6 +2094,9 @@ pub const LLVM = struct { var field_values = try UnpinnedArray(*LLVM.Value.Constant).initialize_with_capacity(context.my_allocator, @intCast(constant_struct.fields.len)); const sema_struct_index = unit.types.get(constant_struct.type).@"struct"; const sema_struct = unit.structs.get(sema_struct_index); + const llvm_type = try llvm.getType(unit, context, constant_struct.type); + const struct_type = llvm_type.toStruct() orelse unreachable; + switch (sema_struct.kind) { .@"struct" => |*sema_struct_type| { for (constant_struct.fields, sema_struct_type.fields.slice()) |field_value, field_index| { @@ -2101,14 +2104,28 @@ pub const LLVM = struct { const constant = try llvm.emitComptimeRightValue(unit, context, field_value, field.type); field_values.append_with_capacity(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.pointer, field_values.length) orelse unreachable; - return const_struct; + }, + .error_union => |error_union| { + const abi_ty = unit.types.get(error_union.abi); + switch (abi_ty.*) { + .@"struct" => |struct_index| switch (unit.structs.get(struct_index).kind) { + .raw_error_union => |err_union_base_type| { + const field_types = [2]Compilation.Type.Index{err_union_base_type, .bool }; + for (field_types, constant_struct.fields) |field_type_index, field_value| { + const constant = try llvm.emitComptimeRightValue(unit, context, field_value, field_type_index); + field_values.append_with_capacity(constant); + } + }, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + } }, else => |t| @panic(@tagName(t)), } + + const const_struct = struct_type.getConstant(field_values.pointer, field_values.length) 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 { @@ -2674,6 +2691,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo .slice_coerce_to_zero_termination, .slice_zero_to_no_termination, .pointer_to_nullable, + .pointer_to_not_nullable, .pointer_const_to_var, .pointer_to_array_to_pointer_to_many, .pointer_source_type_to_destination_type, diff --git a/bootstrap/frontend/parser.zig b/bootstrap/frontend/parser.zig index 68bcfa4..b8fb055 100644 --- a/bootstrap/frontend/parser.zig +++ b/bootstrap/frontend/parser.zig @@ -199,6 +199,9 @@ pub const Node = struct { comptime_expression, self, any, + for_expressions, + slice_metadata, + orelse_expression, }; }; @@ -552,6 +555,11 @@ const Analyzer = struct { const while_block = try analyzer.block(); + if (analyzer.peekToken() == .fixed_keyword_else) { + analyzer.consumeToken(); + unreachable; + } + return analyzer.addNode(.{ .id = .@"while", .token = while_identifier_index, @@ -782,7 +790,7 @@ const Analyzer = struct { .right = try analyzer.nodeList(payload_nodes), }); - const for_content_node = switch (analyzer.peekToken()) { + const true_expression = switch (analyzer.peekToken()) { .operator_left_brace => try analyzer.block(), else => blk: { const for_content_expression = try analyzer.expression(); @@ -791,11 +799,22 @@ const Analyzer = struct { }, }; + const else_expression: Node.Index = if (analyzer.peekToken() == .fixed_keyword_else) b: { + analyzer.consumeToken(); + const else_expression = if (analyzer.peekToken() == .operator_left_brace) try analyzer.block() else try analyzer.expression(); + break :b else_expression; + } else .null; + const for_node = try analyzer.addNode(.{ .id = .for_loop, .token = token, .left = for_condition_node, - .right = for_content_node, + .right = try analyzer.addNode(.{ + .id = .for_expressions, + .token = .null, + .left = true_expression, + .right = else_expression, + }) }); return for_node; @@ -1016,6 +1035,7 @@ const Analyzer = struct { shift_left, shift_right, @"catch", + @"orelse", }; const operator_precedence = std.EnumArray(PrecedenceOperator, i32).init(.{ @@ -1038,6 +1058,7 @@ const Analyzer = struct { .shift_left = 50, .shift_right = 50, .@"catch" = 40, + .@"orelse" = 40, }); const operator_associativity = std.EnumArray(PrecedenceOperator, Associativity).init(.{ @@ -1060,6 +1081,7 @@ const Analyzer = struct { .shift_left = .left, .shift_right = .left, .@"catch" = .left, + .@"orelse" = .left, }); const operator_node_id = std.EnumArray(PrecedenceOperator, Node.Id).init(.{ @@ -1082,6 +1104,7 @@ const Analyzer = struct { .shift_left = .shift_left, .shift_right = .shift_right, .@"catch" = .catch_expression, + .@"orelse" = .orelse_expression, }); fn expressionPrecedence(analyzer: *Analyzer, minimum_precedence: i32) !Node.Index { @@ -1139,6 +1162,7 @@ const Analyzer = struct { .operator_shift_left => .shift_left, .operator_shift_right => .shift_right, .fixed_keyword_catch => .@"catch", + .fixed_keyword_orelse => .@"orelse", else => |t| @panic(@tagName(t)), }; @@ -1251,7 +1275,13 @@ const Analyzer = struct { analyzer.consumeToken(); break :blk token; }, - .left = try analyzer.expression(), + .left = switch (analyzer.peekToken()) { + .operator_comma, + .operator_semicolon, + .operator_compare_equal, + => Node.Index.null, + else => try analyzer.expression(), + }, .right = Node.Index.null, }), .fixed_keyword_break => try analyzer.breakExpression(), @@ -2102,19 +2132,34 @@ const Analyzer = struct { else => try analyzer.expression(), }; - _ = try analyzer.expectToken(.operator_right_bracket); + const slice_termination: Node.Index = if (analyzer.peekToken() == .operator_colon) b: { + analyzer.consumeToken(); + const result = try analyzer.expression(); + break :b result; + } else .null; - break :blk try analyzer.addNode(.{ - .id = .slice, + const slice_metadata = try analyzer.addNode(.{ + .id = .slice_metadata, .token = token, - .left = left, - .right = try analyzer.addNode(.{ + .left = try analyzer.addNode(.{ .id = .range, .token = token, .left = index_expression, .right = range_end_expression, }), + .right = slice_termination, }); + + _ = try analyzer.expectToken(.operator_right_bracket); + + const slice = try analyzer.addNode(.{ + .id = .slice, + .token = token, + .left = left, + .right = slice_metadata, + }); + + break :blk slice; } else { _ = try analyzer.expectToken(.operator_right_bracket); break :blk try analyzer.addNode(.{ diff --git a/build.zig b/build.zig index 19581bf..894de4d 100644 --- a/build.zig +++ b/build.zig @@ -397,11 +397,11 @@ pub fn build(b: *std.Build) !void { }; const llvm_include_dir = try std.mem.concat(b.allocator, u8, &.{ llvm_path, "/include" }); - compiler.addIncludePath(std.Build.LazyPath.relative(llvm_include_dir)); + compiler.addIncludePath(std.Build.path(b, llvm_include_dir)); const llvm_lib_dir = try std.mem.concat(b.allocator, u8, &.{ llvm_path, "/lib" }); for (static_llvm_libraries) |llvm_library| { - compiler.addObjectFile(std.Build.LazyPath.relative(try std.mem.concat(b.allocator, u8, &.{ llvm_lib_dir, "/", llvm_library }))); + compiler.addObjectFile(std.Build.path(b, try std.mem.concat(b.allocator, u8, &.{ llvm_lib_dir, "/", llvm_library }))); } } else { compiler.linkSystemLibrary("LLVM-17"); @@ -463,7 +463,7 @@ pub fn build(b: *std.Build) !void { const install_exe = b.addInstallArtifact(compiler, .{}); b.getInstallStep().dependOn(&install_exe.step); b.installDirectory(.{ - .source_dir = std.Build.LazyPath.relative("lib"), + .source_dir = std.Build.path(b, "lib"), .install_dir = .bin, .install_subdir = "lib", }); diff --git a/lib/std/c.nat b/lib/std/c.nat index 382dfba..5bc676c 100644 --- a/lib/std/c.nat +++ b/lib/std/c.nat @@ -24,17 +24,17 @@ const unwrap_syscall = fn(syscall_result: ssize) Error!usize { const MapFlags = switch (os) { .macos => bitfield(u32){ - shared: bool, - private: bool, + shared: bool = false, + private: bool = false, reserved: u2 = 0, - fixed: bool, + fixed: bool = false, reserved0: bool = 0, - noreserve: bool, + noreserve: bool = false, reserved1: u2 = 0, - has_semaphore: bool, - no_cache: bool, + has_semaphore: bool = false, + no_cache: bool = false, reserved2: u1 = 0, - anonymous: bool, + anonymous: bool = false, reserved3: u19 = 0, }, .linux => linux.MapFlags, @@ -79,5 +79,6 @@ const munmap :: extern = fn cc(.c) (address: [&]const u8, length: usize) s32; const execve :: extern = fn cc(.c) (path: [&:0]const u8, argv: [&:null]const ?[&:0]const u8, env: [&:null]const ?[&:null]const u8) s32; const realpath :: extern = fn cc(.c) (path: [&:0]const u8, resolved_path: [&:0]u8) ?[&:0]u8; const waitpid :: extern = fn cc(.c) (pid: ProcessId, status: &s32, flags: s32) s32; +const mprotect :: extern = fn cc(.c) (address: &any, size: usize, flags: ProtectionFlags) s32; const _NSGetExecutablePath :: extern = fn cc(.c) (buffer: [&:0]u8, buffer_size: &u32) s32; diff --git a/lib/std/std.nat b/lib/std/std.nat index 4aa62fd..239ab29 100644 --- a/lib/std/std.nat +++ b/lib/std/std.nat @@ -27,6 +27,21 @@ const assert = fn(ok: bool) void { } } +const byte_equal = fn (a: []const u8, b: []const u8) bool { + var result = false; + if (a.length == b.length) { + result = true; + for (a, b) |a_byte, b_byte| { + if (a_byte != b_byte) { + result = false; + break; + } + } + } + + return result; +} + const print = fn(bytes: []const u8) void { const file_descriptor = os.StdFileDescriptor.get(descriptor = .stdout); const file_writer = FileWriter{ @@ -156,15 +171,20 @@ const copy_bytes = fn(destination: []u8, source: []const u8) void { } } -const c_len = fn (string: [&:0]const u8) usize { +const c_len = fn (pointer_to_string: [&:0]const u8) usize { var i: usize = 0; - while (string[i] != 0) { + while (pointer_to_string[i] != 0) { i += 1; } return i; } +const c_slice = fn (pointer_to_string: [&:0]const u8) [:0]const u8 { + const len = c_len(pointer_to_string); + return pointer_to_string[0..len:0]; +} + const Target = struct { cpu: builtin.Cpu, os: builtin.Os, diff --git a/src/main.nat b/src/main.nat index a41df8d..f8eebeb 100644 --- a/src/main.nat +++ b/src/main.nat @@ -1,17 +1,99 @@ const std = #import("std"); const Arena = std.Arena; +const c_slice = std.c_slice; +const byte_equal = std.byte_equal; +const print = std.print; +const exit = std.os.exit; + +const ArgumentProcessingError = error{ + no_arguments, +}; + +const lex = fn () void { + +} + +const get_argument = fn (real_argument: []const u8, wanted_argument: []const u8, command_arguments: []const [&:0]const u8, i_ptr: &usize) ?[]const u8 { + const i = i_ptr.@; + + const are_equal = byte_equal(real_argument, wanted_argument); + if (are_equal) { + if (i < command_arguments.length) { + const new_i = i + 1; + const argument = c_slice(command_arguments[new_i]); + i_ptr.@ = new_i; + return argument; + } else { + print("error: unterminated argument: '"); + print(real_argument); + print("'\n"); + exit(1); + } + } else { + return null; + } +} + +const command_exe = fn (arena: &Arena, command_arguments: []const [&:0]const u8) void { + var i: usize = 0; + + var maybe_output_argument: ?[]const u8 = null; + var maybe_main_source_file: ?[]const u8 = null; + + while (i < command_arguments.length) { + const command_argument = c_slice(command_arguments[i]); + + if (get_argument(command_argument, "-o", command_arguments, i.&)) |out_arg| { + maybe_output_argument = out_arg; + } else if (get_argument(command_argument, "-main_source_file", command_arguments, i.&)) |src_arg| { + maybe_main_source_file = src_arg; + } else { + print("error: unhandled argument: '"); + print(command_argument); + print("'\n"); + exit(1); + } + + i += 1; + } + + const main_source_file = maybe_main_source_file orelse { + print("error: no main source file specified\n"); + exit(1); + }; +} const main = fn() *!void { const arena = try Arena.allocate(std.megabytes(64)); const argument_count = std.start.argument_count; + + if (argument_count <= 1) { + return ArgumentProcessingError.no_arguments; + } + const argument_values = std.start.argument_values; const arguments = argument_values[0..argument_count]; - for (arguments) |c_argument| { - const argument_len = std.c_len(c_argument); - const argument = c_argument[0..argument_len]; - std.print("Argument: "); - std.print(argument); - std.print("\n"); + const command = c_slice(arguments[1]); + const command_arguments = arguments[2..]; + + if (byte_equal(command, "build")) { + print("TODO: build"); + } else if (byte_equal(command, "clang") or byte_equal(command, "-cc1") or byte_equal(command, "-cc1as")) { + unreachable; + } else if (byte_equal(command, "cc")) { + unreachable; + } else if (byte_equal(command, "c++")) { + unreachable; + } else if (byte_equal(command, "exe")) { + command_exe(arena, command_arguments); + } else if (byte_equal(command, "lib")) { + unreachable; + } else if (byte_equal(command, "obj")) { + unreachable; + } else if (byte_equal(command, "test")) { + print("TODO: test"); + } else { + unreachable; } } diff --git a/test/standalone/byte_equal/main.nat b/test/standalone/byte_equal/main.nat new file mode 100644 index 0000000..ca765b0 --- /dev/null +++ b/test/standalone/byte_equal/main.nat @@ -0,0 +1,26 @@ +const std = #import("std"); +const print = std.print; +const exit = std.os.exit; +const byte_equal = std.byte_equal; + +const foo = fn (string: []const u8, wanted: []const u8) ?[]const u8 { + if (byte_equal(string, wanted)) { + return string; + } else { + return null; + } +} + +const main = fn () *!void { + const original: []const u8 = "fasdasD"; + var f = original; + f = "foooo"; + + if (foo(f, original)) |string| { + print("string: "); + print(string); + print("\n"); + exit(1); + } else { + } +} diff --git a/test/standalone/c_string/main.nat b/test/standalone/c_string/main.nat new file mode 100644 index 0000000..02f645b --- /dev/null +++ b/test/standalone/c_string/main.nat @@ -0,0 +1,8 @@ +const std = #import("std"); +const expect = std.testing.expect; + +const main = fn() *!void { + const foo: [&:0]const u8 = "Foo"; + const foo_string = std.c_slice(foo); + try expect(foo_string.length == 3); +} diff --git a/test/standalone/orelse/main.nat b/test/standalone/orelse/main.nat new file mode 100644 index 0000000..c078b8c --- /dev/null +++ b/test/standalone/orelse/main.nat @@ -0,0 +1,14 @@ +const std = #import("std"); +const expect = std.testing.expect; + +const main = fn () *!void { + var expected_a: s32 = 5; + var a: ?&s32 = null; + a = expected_a.&; + try expect(a orelse unreachable == expected_a.&); + + var not_expected_b: s32 = 6; + var b: ?&s32 = not_expected_b.&; + b = null; + try expect(b orelse return == not_expected_b.&); +}