diff --git a/bootstrap/Compilation.zig b/bootstrap/Compilation.zig index b19ebea..9598ca2 100644 --- a/bootstrap/Compilation.zig +++ b/bootstrap/Compilation.zig @@ -17,6 +17,7 @@ const StringHashMap = data_structures.StringHashMap; const StringArrayHashMap = data_structures.StringArrayHashMap; const lexical_analyzer = @import("frontend/lexical_analyzer.zig"); +const Token = lexical_analyzer.Token; const syntactic_analyzer = @import("frontend/syntactic_analyzer.zig"); const Node = syntactic_analyzer.Node; const semantic_analyzer = @import("frontend/semantic_analyzer.zig"); @@ -177,7 +178,28 @@ pub fn init(allocator: Allocator) !void { pub const Struct = struct { scope: Scope.Index, - fields: ArrayList(Field.Index) = .{}, + fields: ArrayList(ContainerField.Index) = .{}, + backing_type: Type.Index, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const ContainerField = struct { + name: u32, + type: Type.Index, + default_value: Value.Index, + parent: Type.Index, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const ContainerInitialization = struct { + field_initializations: ArrayList(Value.Index), + type: Type.Index, pub const List = BlockList(@This()); pub const Index = List.Index; @@ -214,27 +236,36 @@ pub const Array = struct { }; pub const Type = union(enum) { + any, void, noreturn, bool, type, comptime_int, integer: Type.Integer, - slice: Slice, + slice: Type.Slice, pointer: Pointer, @"struct": Struct.Index, @"enum": Enum.Index, function: Function.Prototype.Index, array: Array, + optional: Optional, - const Slice = struct { + const Optional = struct { element_type: Type.Index, }; - const Pointer = struct { + + pub const Slice = struct { + element_type: Type.Index, + @"const": bool, + }; + + pub const Pointer = struct { element_type: Type.Index, many: bool, @"const": bool, }; + pub const List = BlockList(@This()); pub const Index = List.Index; pub const Allocation = List.Allocation; @@ -282,6 +313,16 @@ pub const Type = union(enum) { }; } + pub fn getBitSize(type_info: Type) u64 { + return switch (type_info) { + .integer => |integer| integer.bit_count, + .pointer => 8, + .bool => 1, + .comptime_int => @panic("This call should never happen"), + else => |t| @panic(@tagName(t)), + }; + } + pub fn getAlignment(type_info: Type) u64 { return switch (type_info) { .integer => |integer| @min(16, integer.getSize()), @@ -290,6 +331,7 @@ pub const Type = union(enum) { }; } + pub const any = FixedTypeKeyword.any.toType(); pub const @"void" = FixedTypeKeyword.void.toType(); pub const boolean = FixedTypeKeyword.bool.toType(); pub const ssize = FixedTypeKeyword.ssize.toType(); @@ -318,6 +360,7 @@ pub const Type = union(enum) { // Each time an enum is added here, a corresponding insertion in the initialization must be made pub const Intrinsic = enum { + //@"asm", this is processed separately as it need special parsing @"error", import, syscall, @@ -332,6 +375,7 @@ pub const FixedTypeKeyword = enum { ssize, type, comptime_int, + any, const offset = 0; @@ -382,9 +426,10 @@ pub const extra_common_type_data = blk: { /// A scope contains a bunch of declarations pub const Scope = struct { - declarations: AutoHashMap(u32, Declaration.Index) = .{}, + declarations: data_structures.AutoArrayHashMap(u32, Declaration.Index) = .{}, parent: Scope.Index, file: File.Index, + token: Token.Index, type: Type.Index = Type.Index.invalid, pub const List = BlockList(@This()); @@ -409,6 +454,7 @@ pub const Declaration = struct { name: u32, argument_index: ?u32, type: Type.Index, + scope: Scope.Index, pub const Reference = struct { value: Declaration.Index, @@ -465,15 +511,6 @@ pub const Block = struct { pub const Allocation = List.Allocation; }; -pub const Field = struct { - name: u32, - type: Type.Index, - - pub const List = BlockList(@This()); - pub const Index = List.Index; - pub const Allocation = List.Allocation; -}; - pub const Loop = struct { condition: Value.Index, body: Value.Index, @@ -484,10 +521,6 @@ pub const Loop = struct { pub const Allocation = List.Allocation; }; -const Runtime = struct { - foo: u32 = 0, -}; - const Unresolved = struct { node_index: Node.Index, }; @@ -519,6 +552,7 @@ pub const Call = struct { value: Value.Index, arguments: ArgumentList.Index, type: Type.Index, + pub const List = BlockList(@This()); pub const Index = List.Index; pub const Allocation = List.Allocation; @@ -560,25 +594,47 @@ pub const BinaryOperation = struct { pub const Id = enum { add, sub, - logical_and, - logical_xor, - logical_or, + bit_and, + bit_xor, + bit_or, multiply, divide, shift_left, shift_right, compare_equal, + compare_greater_than, + compare_greater_or_equal, + compare_less_than, + compare_less_or_equal, + }; +}; + +pub const UnaryOperation = struct { + value: Value.Index, + type: Type.Index, + id: Id, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; + + pub const Id = enum { + boolean_not, + negation, + address_of, + pointer_dereference, }; }; pub const CallingConvention = enum { system_v, + naked, }; pub const Branch = struct { - condition: Value.Index, - true_expression: Value.Index, - false_expression: Value.Index, + expression: Value.Index, + taken_expression: Value.Index, + not_taken_expression: Value.Index, reaches_end: bool, pub const List = BlockList(@This()); @@ -586,19 +642,118 @@ pub const Branch = struct { pub const Allocation = List.Allocation; }; +pub const FieldAccess = struct { + declaration_reference: Value.Index, + field: ContainerField.Index, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const Slice = struct { + sliceable: Value.Index, + start: Value.Index, + end: Value.Index, + type: Type.Index, + + pub const Access = struct { + value: Value.Index, + field: Field, + type: Type.Index, + + pub const List = BlockList(@This()); + pub const Index = Slice.Access.List.Index; + pub const Allocation = Slice.Access.List.Allocation; + }; + + pub const Field = enum { + ptr, + len, + }; + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const IndexedAccess = struct { + indexed_expression: Value.Index, + index_expression: Value.Index, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const OptionalCheck = struct { + value: Value.Index, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const OptionalUnwrap = struct { + value: Value.Index, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; +}; + +pub const Assembly = struct { + pub const Instruction = struct { + id: u32, + operands: []const Operand, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; + }; + + pub const Operand = union(enum) { + register: u32, + number_literal: u64, + value_index: Value.Index, + }; + + pub const Block = struct { + instructions: []const Assembly.Instruction.Index, + + pub const List = BlockList(@This()); + pub const Index = List.Index; + pub const Allocation = List.Allocation; + }; + + pub const x86_64 = struct { + pub const Instruction = enum { + @"and", + call, + xor, + }; + + pub const Register = enum { + ebp, + rsp, + }; + }; +}; + pub const Value = union(enum) { - unresolved: Unresolved, - declaration: Declaration.Index, - declaration_reference: Declaration.Reference, void, bool: bool, undefined, @"unreachable", + pointer_null_literal, + optional_null_literal, + unresolved: Unresolved, + declaration: Declaration.Index, + declaration_reference: Declaration.Reference, loop: Loop.Index, function_definition: Function.Index, function_declaration: Function.Index, block: Block.Index, - runtime: Runtime, assign: Assignment.Index, type: Type.Index, integer: Integer, @@ -613,8 +768,17 @@ pub const Value = union(enum) { sign_extend: Cast.Index, zero_extend: Cast.Index, binary_operation: BinaryOperation.Index, + unary_operation: UnaryOperation.Index, branch: Branch.Index, cast: Cast.Index, + container_initialization: ContainerInitialization.Index, + field_access: FieldAccess.Index, + slice_access: Slice.Access.Index, + indexed_access: IndexedAccess.Index, + optional_check: OptionalCheck.Index, + optional_unwrap: OptionalUnwrap.Index, + slice: Slice.Index, + assembly_block: Assembly.Block.Index, pub const List = BlockList(@This()); pub const Index = List.Index; @@ -632,11 +796,20 @@ pub const Value = union(enum) { pub fn isComptime(value: *Value, module: *Module) bool { return switch (value.*) { - .bool, .void, .undefined, .function_definition, .type, .enum_field => true, .integer => |integer| integer.type.eq(Type.comptime_int), - .call => false, - .binary_operation => false, .declaration_reference => |declaration_reference| module.declarations.get(declaration_reference.value).mutability == .@"const" and isComptime(module.values.get(module.declarations.get(declaration_reference.value).init_value), module), + .bool, .void, .undefined, .function_definition, .type, .enum_field => true, + // TODO: + .call, + .syscall, + .binary_operation, + .container_initialization, + .cast, + .optional_unwrap, + .pointer_null_literal, + .indexed_access, + => false, + // TODO: else => |t| @panic(@tagName(t)), }; } @@ -654,73 +827,36 @@ pub const Value = union(enum) { .binary_operation => |binary_operation| module.binary_operations.get(binary_operation).type, .bool => Type.boolean, .declaration => Type.void, + .container_initialization => |container_initialization| module.container_initializations.get(container_initialization).type, + .syscall => Type.usize, + .unary_operation => |unary_operation_index| module.unary_operations.get(unary_operation_index).type, + .pointer_null_literal => semantic_analyzer.optional_pointer_to_any_type, + .optional_null_literal => semantic_analyzer.optional_any, + .field_access => |field_access_index| module.container_fields.get(module.field_accesses.get(field_access_index).field).type, + .cast => |cast_index| module.casts.get(cast_index).type, + .slice => |slice_index| module.slices.get(slice_index).type, + .slice_access => |slice_access_index| module.slice_accesses.get(slice_access_index).type, + .optional_check => Type.boolean, + .indexed_access => |indexed_access_index| blk: { + const indexed_expression = module.values.get(module.indexed_accesses.get(indexed_access_index).indexed_expression); + const indexed_expression_type_index = indexed_expression.getType(module); + const indexed_expression_type = module.types.get(indexed_expression_type_index); + break :blk switch (indexed_expression_type.*) { + .slice => |slice| slice.element_type, + else => |t| @panic(@tagName(t)), + }; + }, else => |t| @panic(@tagName(t)), }; return result; } - // pub fn setType(value: *Value, new_type: Type.Index) void { - // switch (value.*) { - // .integer => value.integer.type = new_type, - // else => |t| @panic(@tagName(t)), - // } - // } const TypeCheckError = error{ integer_size, pointer_many_differ, pointer_element_type_differ, }; - - pub fn typeCheck(value: *Value, module: *Module, type_to_check_index: Type.Index) TypeCheckError!void { - const value_type_index = value.getType(module); - - if (!value_type_index.eq(type_to_check_index)) { - const value_type = module.types.get(value_type_index); - const check_type = module.types.get(type_to_check_index); - if (std.meta.activeTag(value_type.*) == std.meta.activeTag(check_type.*)) { - switch (value_type.*) { - .integer => |coercee_int| { - if (check_type.integer.getSize() < coercee_int.getSize()) { - return error.integer_size; - } - }, - .pointer => |coercee_pointer| { - if (coercee_pointer.many != check_type.pointer.many) { - return error.pointer_many_differ; - } - - if (!coercee_pointer.element_type.eq(check_type.pointer.element_type)) { - if (check_type.pointer.many) { - const coercee_element_type = module.types.get(coercee_pointer.element_type); - switch (coercee_element_type.*) { - .array => |array| if (!array.element_type.eq(check_type.pointer.element_type)) { - return error.pointer_element_type_differ; - }, - else => |t| @panic(@tagName(t)), - } - } - } - }, - else => |t| @panic(@tagName(t)), - } - } else { - switch (check_type.*) { - .integer => { - switch (value_type.*) { - .comptime_int => switch (value.*) { - .integer => value.integer.type = type_to_check_index, - .declaration_reference => value.declaration_reference.type = type_to_check_index, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - } - }, - else => |t| @panic(@tagName(t)), - } - } - } - } }; pub const Module = struct { @@ -735,7 +871,6 @@ pub const Module = struct { function_definitions: BlockList(Function) = .{}, function_declarations: BlockList(Function) = .{}, function_prototypes: BlockList(Function.Prototype) = .{}, - fields: BlockList(Field) = .{}, types: BlockList(Type) = .{}, blocks: BlockList(Block) = .{}, loops: BlockList(Loop) = .{}, @@ -747,12 +882,28 @@ pub const Module = struct { string_literals: StringKeyMap([]const u8) = .{}, enums: BlockList(Enum) = .{}, enum_fields: BlockList(Enum.Field) = .{}, - function_name_map: data_structures.AutoArrayHashMap(Function.Index, u32) = .{}, + container_fields: BlockList(ContainerField) = .{}, + container_initializations: BlockList(ContainerInitialization) = .{}, + function_map: data_structures.AutoArrayHashMap(Function.Index, Declaration.Index) = .{}, + type_map: data_structures.AutoArrayHashMap(Type.Index, Declaration.Index) = .{}, arrays: BlockList(Array) = .{}, casts: BlockList(Cast) = .{}, binary_operations: BlockList(BinaryOperation) = .{}, + unary_operations: BlockList(UnaryOperation) = .{}, branches: BlockList(Branch) = .{}, + field_accesses: BlockList(FieldAccess) = .{}, + slices: BlockList(Slice) = .{}, + slice_accesses: BlockList(Slice.Access) = .{}, + indexed_accesses: BlockList(IndexedAccess) = .{}, + optional_checks: BlockList(OptionalCheck) = .{}, + optional_unwraps: BlockList(OptionalUnwrap) = .{}, + assembly_blocks: BlockList(Assembly.Block) = .{}, + assembly_instructions: BlockList(Assembly.Instruction) = .{}, + non_primitive_integer_types: data_structures.AutoArrayHashMap(Type.Integer, Type.Index) = .{}, string_literal_types: data_structures.AutoArrayHashMap(u32, Type.Index) = .{}, + slice_types: data_structures.AutoArrayHashMap(Type.Slice, Type.Index) = .{}, + pointer_types: data_structures.AutoArrayHashMap(Type.Pointer, Type.Index) = .{}, + optional_types: data_structures.AutoArrayHashMap(Type.Index, Type.Index) = .{}, array_types: data_structures.AutoArrayHashMap(Array, Type.Index) = .{}, entry_point: Function.Index = Function.Index.invalid, descriptor: Descriptor, @@ -1074,8 +1225,27 @@ pub fn compileModule(compilation: *Compilation, descriptor: Module.Descriptor) ! for (extra_common_type_data) |type_data| { _ = try module.types.append(compilation.base_allocator, type_data); } + semantic_analyzer.pointer_to_any_type = (try module.types.append(compilation.base_allocator, .{ + .pointer = .{ + .element_type = Type.any, + .many = false, + .@"const" = true, + }, + })).index; + semantic_analyzer.optional_pointer_to_any_type = (try module.types.append(compilation.base_allocator, .{ + .optional = .{ + .element_type = semantic_analyzer.pointer_to_any_type, + }, + })).index; + semantic_analyzer.optional_any = (try module.types.append(compilation.base_allocator, .{ + .optional = .{ + .element_type = Type.any, + }, + })).index; semantic_analyzer.unreachable_index = (try module.values.append(compilation.base_allocator, .@"unreachable")).index; + semantic_analyzer.pointer_null_index = (try module.values.append(compilation.base_allocator, .pointer_null_literal)).index; + semantic_analyzer.optional_null_index = (try module.values.append(compilation.base_allocator, .optional_null_literal)).index; const value_allocation = try module.values.append(compilation.base_allocator, .{ .unresolved = .{ diff --git a/bootstrap/backend/c_transpiler.zig b/bootstrap/backend/c_transpiler.zig index 916aa56..2ebcd7f 100644 --- a/bootstrap/backend/c_transpiler.zig +++ b/bootstrap/backend/c_transpiler.zig @@ -1,8 +1,10 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const equal = std.mem.eql; const Compilation = @import("../Compilation.zig"); +const logln = Compilation.logln; const Module = Compilation.Module; const data_structures = @import("../data_structures.zig"); const ArrayList = data_structures.ArrayList; @@ -17,17 +19,29 @@ pub const Logger = enum { }); }; +const margin_width = 4; + pub const TranslationUnit = struct { string_literals: ArrayList(u8) = .{}, + primitive_type_declarations: ArrayList(u8) = .{}, + type_forward_declarations: ArrayList(u8) = .{}, type_declarations: ArrayList(u8) = .{}, function_declarations: ArrayList(u8) = .{}, + global_variable_declarations: ArrayList(u8) = .{}, function_definitions: ArrayList(u8) = .{}, syscall_bitset: SyscallBitset = SyscallBitset.initEmpty(), + struct_type_set: AutoArrayHashMap(Compilation.Type.Index, []const u8) = .{}, + optional_type_set: AutoArrayHashMap(Compilation.Type.Index, []const u8) = .{}, + function_set: AutoArrayHashMap(Compilation.Function.Index, []const u8) = .{}, + slice_type_set: AutoArrayHashMap(Compilation.Type.Index, []const u8) = .{}, + declaration_set: AutoArrayHashMap(Compilation.Declaration.Index, []const u8) = .{}, + const SyscallBitset = std.StaticBitSet(6); - fn create(module: *Module, allocator: Allocator) !TranslationUnit { - var unit = TranslationUnit{}; - try unit.type_declarations.appendSlice(allocator, + fn create(module: *Module, allocator: Allocator) !*TranslationUnit { + var unit = try allocator.create(TranslationUnit); + unit.* = .{}; + try unit.primitive_type_declarations.appendSlice(allocator, \\typedef unsigned char u8; \\typedef unsigned short u16; \\typedef unsigned int u32; @@ -53,59 +67,140 @@ pub const TranslationUnit = struct { { var function_definitions = module.function_definitions.iterator(); while (function_definitions.nextIndex()) |function_definition_index| { - const function_definition = module.function_definitions.get(function_definition_index); - try unit.writeFunctionHeader(module, &unit.function_declarations, allocator, function_definition_index); - try unit.writeFunctionHeader(module, &unit.function_definitions, allocator, function_definition_index); - try unit.function_declarations.appendSlice(allocator, ";\n\n"); - try unit.function_definitions.append(allocator, ' '); - try unit.writeBlock(module, &unit.function_definitions, allocator, function_definition.body, 1); - try unit.function_definitions.append(allocator, '\n'); + _ = try unit.writeFunctionDefinition(module, allocator, function_definition_index); } } return unit; } - fn writeBlock(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, block_index: Compilation.Block.Index, indentation: usize) !void { + fn writeFunctionDefinition(unit: *TranslationUnit, module: *Module, allocator: Allocator, function_definition_index: Compilation.Function.Index) ![]const u8 { + const gop = try unit.function_set.getOrPut(allocator, function_definition_index); + + if (!gop.found_existing) { + const function_definition = module.function_definitions.get(function_definition_index); + const function_prototype_type = function_definition.prototype; + const function_prototype = module.function_prototypes.get(module.types.get(function_prototype_type).function); + + const function_name = try unit.writeFunctionHeader(module, &unit.function_declarations, allocator, function_definition_index); + gop.value_ptr.* = function_name; + + _ = try unit.writeFunctionHeader(module, &unit.function_definitions, allocator, function_definition_index); + try unit.function_declarations.appendSlice(allocator, ";\n\n"); + + try unit.function_definitions.append(allocator, ' '); + try unit.writeBlock(module, &unit.function_definitions, allocator, function_definition.body, function_prototype.return_type, 0); + try unit.function_definitions.append(allocator, '\n'); + } + + return gop.value_ptr.*; + } + + fn writeDeclaration(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, declaration_index: Compilation.Declaration.Index, indentation: usize) !void { + const declaration = module.declarations.get(declaration_index); + const mangle = false; + const name = try unit.renderDeclarationName(module, allocator, declaration_index, mangle); + + if (declaration.mutability == .@"const") { + switch (module.types.get(declaration.type).*) { + .optional => |optional| switch (module.types.get(optional.element_type).*) { + .pointer => {}, + else => try list.appendSlice(allocator, "const "), + }, + .pointer => {}, + .integer, + .@"struct", + .slice, + .bool, + => try list.appendSlice(allocator, "const "), + else => |t| @panic(@tagName(t)), + //else => try list.appendSlice(allocator, "const "), + } + } + + try unit.writeType(module, list, allocator, declaration.type); + + try list.append(allocator, ' '); + + try list.appendSlice(allocator, name); + + try list.appendSlice(allocator, " = "); + + try unit.writeValue(module, list, allocator, Compilation.Type.Index.invalid, indentation, .{ + .value_index = declaration.init_value, + .type_index = declaration.type, + }); + try list.append(allocator, ';'); + } + + fn writeBlock(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, block_index: Compilation.Block.Index, function_return_type: Compilation.Type.Index, old_indentation: usize) !void { try list.appendSlice(allocator, "{\n"); const block = module.blocks.get(block_index); + + const indentation = old_indentation + 1; + for (block.statements.items) |statement_index| { - try list.appendNTimes(allocator, ' ', indentation * 4); + try list.appendNTimes(allocator, ' ', indentation * margin_width); const statement = module.values.get(statement_index); switch (statement.*) { .declaration => |declaration_index| { - const declaration = module.declarations.get(declaration_index); - if (declaration.mutability == .@"const") { - try list.appendSlice(allocator, "const "); - } - try unit.writeType(module, list, allocator, declaration.type); - - try list.append(allocator, ' '); - - const declaration_name = module.getName(declaration.name).?; - try list.appendSlice(allocator, declaration_name); - - try list.appendSlice(allocator, " = "); - - try unit.writeValue(module, list, allocator, declaration.init_value, indentation); - try list.append(allocator, ';'); + try unit.writeDeclaration(module, list, allocator, declaration_index, indentation); }, .assign => |assignment_index| { const assignment = module.assignments.get(assignment_index); - try unit.writeValue(module, list, allocator, assignment.destination, indentation); + const left_type = module.values.get(assignment.source).getType(module); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = assignment.destination, + .type_index = left_type, + }); try list.appendSlice(allocator, " = "); - try unit.writeValue(module, list, allocator, assignment.source, indentation); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = assignment.source, + .type_index = Compilation.Type.Index.invalid, + }); try list.append(allocator, ';'); }, .@"return" => |return_index| { const return_expr = module.returns.get(return_index); try list.appendSlice(allocator, "return "); - try unit.writeValue(module, list, allocator, return_expr.value, indentation); + const return_value = module.values.get(return_expr.value); + const return_value_type_index = return_value.getType(module); + // _ = return_value_type_index; + switch (module.types.get(function_return_type).*) { + .optional => switch (module.types.get(return_value_type_index).*) { + .optional => try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = return_expr.value, + .type_index = function_return_type, + }), + else => { + try list.append(allocator, '('); + try unit.writeType(module, list, allocator, function_return_type); + try list.appendSlice(allocator, ") {\n"); + + try list.appendNTimes(allocator, ' ', indentation * margin_width); + try list.appendSlice(allocator, ".value = "); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = return_expr.value, + .type_index = return_value_type_index, + }); + try list.appendSlice(allocator, ",\n"); + try list.appendNTimes(allocator, ' ', indentation * margin_width); + try list.appendSlice(allocator, ".is_null = false,\n"); + try list.appendNTimes(allocator, ' ', indentation * margin_width); + try list.append(allocator, '}'); + }, + }, + else => try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = return_expr.value, + .type_index = function_return_type, + }), + } + try list.append(allocator, ';'); }, .syscall => |syscall_index| { - try unit.writeSyscall(module, list, allocator, syscall_index, indentation); + try unit.writeSyscall(module, list, allocator, syscall_index, function_return_type, indentation); try list.append(allocator, ';'); }, .@"unreachable" => { @@ -113,40 +208,153 @@ pub const TranslationUnit = struct { try list.append(allocator, ';'); }, .call => |call_index| { - try unit.writeCall(module, list, allocator, call_index, indentation); + try unit.writeCall(module, list, allocator, call_index, function_return_type, indentation); try list.append(allocator, ';'); }, .branch => |branch_index| { const branch = module.branches.get(branch_index); try list.appendSlice(allocator, "if ("); - try unit.writeValue(module, list, allocator, branch.condition, indentation); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = branch.expression, + .type_index = Compilation.Type.Index.invalid, + }); try list.appendSlice(allocator, ") "); - try unit.writeValue(module, list, allocator, branch.true_expression, indentation); - if (!branch.false_expression.invalid) { - try list.appendSlice(allocator, " else "); - try unit.writeValue(module, list, allocator, branch.false_expression, indentation); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = branch.taken_expression, + .type_index = function_return_type, + }); + + if (!branch.not_taken_expression.invalid) { + if (module.values.get(branch.taken_expression).* == .block) { + _ = list.pop(); + try list.appendSlice(allocator, " else "); + } else { + unreachable; + } + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = branch.not_taken_expression, + .type_index = function_return_type, + }); + + if (module.values.get(branch.not_taken_expression).* == .block) { + continue; + } } }, + .assembly_block => |assembly_block_index| { + try unit.writeAssembly(module, list, allocator, assembly_block_index, indentation); + try list.append(allocator, ';'); + }, else => |t| @panic(@tagName(t)), } try list.append(allocator, '\n'); } + try list.appendNTimes(allocator, ' ', old_indentation * margin_width); try list.appendSlice(allocator, "}\n"); } - fn writeFunctionHeader(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, function_index: Compilation.Function.Index) !void { + const FunctionHeaderType = enum { + pointer, + header, + }; + + fn renderTypeName(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Compilation.Type.Index) ![]const u8 { + const declaration_index = module.type_map.get(type_index).?; + const mangle = true; + const result = try unit.renderDeclarationName(module, allocator, declaration_index, mangle); + return result; + } + + fn renderFunctionName(unit: *TranslationUnit, module: *Module, allocator: Allocator, function_index: Compilation.Function.Index) ![]const u8 { const function_definition = module.function_definitions.get(function_index); const function_prototype_type = module.types.get(function_definition.prototype); - const function_prototype = module.function_prototypes.get(function_prototype_type.function); + const function_prototype_index = function_prototype_type.function; + const function_prototype = module.function_prototypes.get(function_prototype_index); + const mangle = !(function_prototype.attributes.@"export" or function_prototype.attributes.@"extern"); + const function_declaration_index = module.function_map.get(function_index).?; + const name = try unit.renderDeclarationName(module, allocator, function_declaration_index, mangle); + return name; + } + + fn renderDeclarationName(unit: *TranslationUnit, module: *Module, allocator: Allocator, declaration_index: Compilation.Declaration.Index, mangle: bool) anyerror![]const u8 { + const gop = try unit.declaration_set.getOrPut(allocator, declaration_index); + + if (!gop.found_existing) { + const declaration = module.declarations.get(declaration_index); + const base_declaration_name = module.getName(declaration.name).?; + var list = ArrayList(u8){}; + + try list.insertSlice(allocator, 0, base_declaration_name); + + if (mangle) { + var scope_index = declaration.scope; + + var iterations: usize = 0; + switch (declaration.scope_type) { + .global => { + while (!scope_index.invalid) { + const scope = module.scopes.get(scope_index); + + if (module.type_map.get(scope.type)) |type_declaration| { + const scope_type_declaration = module.declarations.get(type_declaration); + const scope_type_declaration_name = module.getName(scope_type_declaration.name).?; + try list.insert(allocator, 0, '_'); + try list.insertSlice(allocator, 0, scope_type_declaration_name); + + scope_index = scope.parent; + } else { + break; + } + + iterations += 1; + } + }, + .local => {}, + } + } + + // TODO: enhance declaration name rendering with file scope name + // const scope = declaration.scope; + gop.value_ptr.* = list.items; + + switch (declaration.scope_type) { + .global => switch (module.types.get(declaration.type).*) { + .function, + .type, + => {}, + .@"struct" => { + try unit.writeDeclaration(module, &unit.global_variable_declarations, allocator, declaration_index, 0); + try unit.global_variable_declarations.appendNTimes(allocator, '\n', 2); + }, + else => |t| @panic(@tagName(t)), + }, + .local => {}, + } + } + + assert(@intFromPtr(gop.value_ptr.*.ptr) != 0xaaaa_aaaa_aaaa_aaaa); + logln(.c, .g, "Rendering name: {s}", .{gop.value_ptr.*}); + + return gop.value_ptr.*; + } + + fn writeFunctionPrototype(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, function_prototype_index: Compilation.Function.Prototype.Index, name: []const u8) !void { + const function_prototype = module.function_prototypes.get(function_prototype_index); + switch (function_prototype.attributes.calling_convention) { + .system_v => {}, + .naked => try list.appendSlice(allocator, "[[gnu::naked]] "), + } + try unit.writeType(module, list, allocator, function_prototype.return_type); + try list.append(allocator, ' '); - const function_name_hash = module.function_name_map.get(function_index).?; - const function_name = module.getName(function_name_hash).?; - try list.appendSlice(allocator, function_name); + + try list.appendSlice(allocator, name); try list.append(allocator, '('); + if (function_prototype.arguments) |function_arguments| { for (function_arguments) |argument_index| { const arg_declaration = module.declarations.get(argument_index); @@ -154,15 +362,28 @@ pub const TranslationUnit = struct { try list.append(allocator, ' '); const arg_name = module.getName(arg_declaration.name).?; try list.appendSlice(allocator, arg_name); - try list.append(allocator, ','); + try list.appendSlice(allocator, ", "); } _ = list.pop(); + _ = list.pop(); } + try list.appendSlice(allocator, ")"); } - fn writeType(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, type_index: Compilation.Type.Index) !void { + fn writeFunctionHeader(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, function_index: Compilation.Function.Index) ![]const u8 { + const name = try unit.renderFunctionName(module, allocator, function_index); + const function_definition = module.function_definitions.get(function_index); + const function_prototype_type = module.types.get(function_definition.prototype); + const function_prototype_index = function_prototype_type.function; + try unit.writeFunctionPrototype(module, list, allocator, function_prototype_index, name); + + return name; + } + + fn writeType(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, type_index: Compilation.Type.Index) anyerror!void { const sema_type = module.types.get(type_index); + switch (sema_type.*) { .void => try list.appendSlice(allocator, "void"), .noreturn => try list.appendSlice(allocator, "[[noreturn]] void"), @@ -175,20 +396,260 @@ pub const TranslationUnit = struct { try list.writer(allocator).print("{}", .{integer.bit_count}); }, .pointer => |pointer| { - if (pointer.@"const") { - try list.appendSlice(allocator, "const "); + switch (module.types.get(pointer.element_type).*) { + .function => { + @panic("This should be unreachable"); + }, + else => { + if (pointer.@"const") { + try list.appendSlice(allocator, "const "); + } + try unit.writeType(module, list, allocator, pointer.element_type); + try list.append(allocator, '*'); + }, } - try unit.writeType(module, list, allocator, pointer.element_type); - try list.append(allocator, '*'); }, + .@"struct" => { + const name = try unit.cacheStructType(module, allocator, type_index); + try list.appendSlice(allocator, name); + }, + .optional => { + const name = try unit.cacheOptionalType(module, allocator, type_index); + try list.appendSlice(allocator, name); + }, + .slice => { + const name = try unit.cacheSliceType(module, allocator, type_index); + try list.appendSlice(allocator, name); + }, + .any => @panic("Internal compiler error: 'any' made it to the backend"), else => |t| @panic(@tagName(t)), } } - fn writeSyscall(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, syscall_index: Compilation.Syscall.Index, indentation: usize) !void { + fn writeCDeclaration(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, name: []const u8, type_index: Compilation.Type.Index) !void { + const declaration_type = module.types.get(type_index); + switch (declaration_type.*) { + .pointer => |pointer| { + switch (module.types.get(pointer.element_type).*) { + .function => |function| return try unit.writeFunctionPrototype(module, list, allocator, function, try std.mem.concat(allocator, u8, &.{ "(*", name, ")" })), + else => |t| @panic(@tagName(t)), + } + }, + else => {}, + } + + try unit.writeType(module, list, allocator, type_index); + try list.append(allocator, ' '); + try list.appendSlice(allocator, name); + } + + fn writeAssembly(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, assembly_block_index: Compilation.Assembly.Block.Index, indentation: usize) !void { + const assembly_block = module.assembly_blocks.get(assembly_block_index); + try list.appendSlice(allocator, "__asm__ __volatile__(\n"); + for (assembly_block.instructions) |instruction_index| { + const generic_instruction = module.assembly_instructions.get(instruction_index); + try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); + try list.append(allocator, '"'); + switch (module.descriptor.target.cpu.arch) { + .x86_64 => { + const architecture = @field(Compilation.Assembly, "x86_64"); + + const instruction: architecture.Instruction = @enumFromInt(generic_instruction.id); + const instruction_name = switch (instruction) { + .@"and" => "andq", + .xor => @tagName(instruction), + .call => "callq", + }; + try list.appendSlice(allocator, instruction_name); + + assert(generic_instruction.operands.len <= 2); + if (generic_instruction.operands.len > 0) { + try list.append(allocator, ' '); + var operand_i: usize = generic_instruction.operands.len; + + while (operand_i > 0) { + operand_i -= 1; + + const operand = generic_instruction.operands[operand_i]; + switch (operand) { + .register => |generic_register| { + const register: architecture.Register = @enumFromInt(generic_register); + try list.append(allocator, '%'); + try list.appendSlice(allocator, @tagName(register)); + }, + .number_literal => |number_literal| { + try list.writer(allocator).print("$0x{x}", .{number_literal}); + }, + .value_index => |value_index| { + try unit.writeValue(module, list, allocator, Compilation.Type.Index.invalid, indentation + 1, .{ + .value_index = value_index, + .type_index = Compilation.Type.Index.invalid, + }); + }, + } + + try list.appendSlice(allocator, ", "); + } + + _ = list.pop(); + _ = list.pop(); + } + }, + else => unreachable, + } + try list.appendSlice(allocator, "\\n\\t\"\n"); + } + + try list.appendNTimes(allocator, ' ', indentation * margin_width); + try list.append(allocator, ')'); + } + + fn cacheStructType(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Compilation.Type.Index) ![]const u8 { + const t = module.types.get(type_index); + assert(t.* == .@"struct"); + const gop = try unit.struct_type_set.getOrPut(allocator, type_index); + + if (!gop.found_existing) { + const struct_type = module.structs.get(t.@"struct"); + const type_name = try unit.renderTypeName(module, allocator, type_index); + gop.value_ptr.* = type_name; + // Forward declare the struct + { + try unit.type_forward_declarations.appendSlice(allocator, "typedef struct "); + try unit.type_forward_declarations.appendSlice(allocator, type_name); + try unit.type_forward_declarations.append(allocator, ' '); + try unit.type_forward_declarations.appendSlice(allocator, type_name); + try unit.type_forward_declarations.appendSlice(allocator, ";\n"); + } + + // Actually declare the struct + { + try unit.type_declarations.appendSlice(allocator, "typedef struct "); + try unit.type_declarations.appendSlice(allocator, type_name); + try unit.type_declarations.appendSlice(allocator, " {\n"); + + for (struct_type.fields.items) |struct_field_index| { + try unit.type_declarations.appendNTimes(allocator, ' ', margin_width); + + const struct_field = module.container_fields.get(struct_field_index); + const struct_field_name = module.getName(struct_field.name).?; + + switch (struct_type.backing_type.invalid) { + false => { + try unit.writeType(module, &unit.type_declarations, allocator, struct_type.backing_type); + try unit.type_declarations.append(allocator, ' '); + try unit.type_declarations.appendSlice(allocator, struct_field_name); + try unit.type_declarations.appendSlice(allocator, " : "); + try unit.type_declarations.writer(allocator).print("{}", .{module.types.get(struct_field.type).getBitSize()}); + }, + true => try unit.writeCDeclaration(module, &unit.type_declarations, allocator, struct_field_name, struct_field.type), + } + + try unit.type_declarations.appendSlice(allocator, ";\n"); + } + + try unit.type_declarations.appendSlice(allocator, "} "); + try unit.type_declarations.appendSlice(allocator, type_name); + try unit.type_declarations.appendSlice(allocator, ";\n\n"); + } + } + + assert(@intFromPtr(gop.value_ptr.*.ptr) != 0xaaaa_aaaa_aaaa_aaaa); + + return gop.value_ptr.*; + } + + fn cacheOptionalType(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Compilation.Type.Index) ![]const u8 { + const optional_type = module.types.get(type_index); + assert(optional_type.* == .optional); + const optional = optional_type.optional; + + const gop = try unit.optional_type_set.getOrPut(allocator, optional.element_type); + + if (!gop.found_existing) { + var type_name = ArrayList(u8){}; + const optional_element_type = module.types.get(optional.element_type); + + switch (optional_element_type.*) { + .pointer => { + try unit.writeType(module, &type_name, allocator, optional.element_type); + }, + else => { + try type_name.appendSlice(allocator, "Optional_"); + try unit.writeType(module, &type_name, allocator, optional.element_type); + + if (!gop.found_existing) { + try unit.type_forward_declarations.appendSlice(allocator, "typedef struct "); + try unit.type_forward_declarations.appendSlice(allocator, type_name.items); + try unit.type_forward_declarations.append(allocator, ' '); + try unit.type_forward_declarations.appendSlice(allocator, type_name.items); + try unit.type_forward_declarations.appendSlice(allocator, ";\n"); + + try unit.type_declarations.appendSlice(allocator, "typedef struct "); + try unit.type_declarations.appendSlice(allocator, type_name.items); + try unit.type_declarations.appendSlice(allocator, " {\n"); + + try unit.type_declarations.appendNTimes(allocator, ' ', margin_width); + try unit.writeCDeclaration(module, &unit.type_declarations, allocator, "value", optional.element_type); + try unit.type_declarations.appendSlice(allocator, ";\n"); + + try unit.type_declarations.appendNTimes(allocator, ' ', margin_width); + try unit.writeCDeclaration(module, &unit.type_declarations, allocator, "is_null", Compilation.Type.boolean); + try unit.type_declarations.appendSlice(allocator, ";\n"); + + try unit.type_declarations.appendSlice(allocator, "} "); + try unit.type_declarations.appendSlice(allocator, type_name.items); + try unit.type_declarations.appendSlice(allocator, ";\n\n"); + } + }, + } + gop.value_ptr.* = type_name.items; + } + + return gop.value_ptr.*; + } + + fn cacheSliceType(unit: *TranslationUnit, module: *Module, allocator: Allocator, type_index: Compilation.Type.Index) ![]const u8 { + const slice = module.types.get(type_index).slice; + + const gop = try unit.slice_type_set.getOrPut(allocator, slice.element_type); + + if (!gop.found_existing) { + var type_name = ArrayList(u8){}; + try type_name.appendSlice(allocator, "Slice_"); + try unit.writeType(module, &type_name, allocator, slice.element_type); + gop.value_ptr.* = type_name.items; + + try unit.type_forward_declarations.appendSlice(allocator, "typedef struct "); + try unit.type_forward_declarations.appendSlice(allocator, type_name.items); + try unit.type_forward_declarations.append(allocator, ' '); + try unit.type_forward_declarations.appendSlice(allocator, type_name.items); + try unit.type_forward_declarations.appendSlice(allocator, ";\n"); + + try unit.type_declarations.appendSlice(allocator, "typedef struct "); + try unit.type_declarations.appendSlice(allocator, type_name.items); + try unit.type_declarations.appendSlice(allocator, " {\n"); + + try unit.type_declarations.appendNTimes(allocator, ' ', margin_width); + try unit.writeType(module, &unit.type_declarations, allocator, slice.element_type); + try unit.type_declarations.appendSlice(allocator, "* ptr;\n"); + + try unit.type_declarations.appendNTimes(allocator, ' ', margin_width); + try unit.type_declarations.appendSlice(allocator, "usize len;\n"); + + try unit.type_declarations.appendSlice(allocator, "} "); + try unit.type_declarations.appendSlice(allocator, type_name.items); + try unit.type_declarations.appendSlice(allocator, ";\n\n"); + } + + return gop.value_ptr.*; + } + + fn writeSyscall(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, syscall_index: Compilation.Syscall.Index, function_return_type: Compilation.Type.Index, indentation: usize) !void { const syscall = module.syscalls.get(syscall_index); const arguments = syscall.getArguments(); - if (!unit.syscall_bitset.isSet(arguments.len)) { + + if (!unit.syscall_bitset.isSet(arguments.len - 1)) { try unit.function_declarations.appendSlice(allocator, "static __inline u64 syscall"); try unit.function_declarations.writer(allocator).print("{}(", .{arguments.len}); try unit.function_declarations.appendSlice(allocator, "u64 n, "); @@ -199,42 +660,65 @@ pub const TranslationUnit = struct { _ = unit.function_declarations.pop(); try unit.function_declarations.appendSlice(allocator, \\) { + \\ + ); + + const simple_register_argument_count = @min(arguments.len, 3); + const complex_register_argument_count = arguments.len - simple_register_argument_count; + const simple_argument_registers = [_]u8{ 'D', 'S', 'd' }; + const complex_argument_registers = [_]u8{ 10, 8, 9 }; + + for (0..complex_register_argument_count) |i| { + try unit.function_declarations.appendNTimes(allocator, ' ', indentation * margin_width); + try unit.function_declarations.writer(allocator).print("register unsigned long r{} __asm__(\"r{}\") = arg{};\n", .{ complex_argument_registers[i], complex_argument_registers[i], 3 + i }); + } + + try unit.function_declarations.appendSlice(allocator, \\ unsigned long ret; + \\ \\ __asm__ __volatile__("syscall" \\ : "=a"(ret) \\ : "a"(n), ); - const argument_registers = [_]u8{ 'D', 'S', 'd' }; - if (arguments.len <= 3) { - for (0..arguments.len, argument_registers[0..arguments.len]) |arg_i, arg_register| { - try unit.function_declarations.writer(allocator).print("\"{c}\"(arg{}), ", .{ arg_register, arg_i }); - } - } else { - unreachable; + for (0..simple_register_argument_count, simple_argument_registers[0..simple_register_argument_count]) |arg_i, arg_register| { + try unit.function_declarations.writer(allocator).print("\"{c}\"(arg{}), ", .{ arg_register, arg_i }); } + + for (complex_argument_registers[0..complex_register_argument_count]) |arg_register| { + try unit.function_declarations.writer(allocator).print("\"r\"(r{}), ", .{arg_register}); + } + _ = unit.function_declarations.pop(); _ = unit.function_declarations.pop(); + try unit.function_declarations.appendSlice(allocator, \\ \\ : "rcx", "r11", "memory" \\ ); + \\ \\ return ret; \\} \\ \\ ); - unit.syscall_bitset.set(arguments.len); + unit.syscall_bitset.set(arguments.len - 1); } try list.writer(allocator).print("syscall{}(", .{arguments.len}); - try unit.writeValue(module, list, allocator, syscall.number, indentation); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = syscall.number, + .type_index = function_return_type, + }); try list.appendSlice(allocator, ", "); for (arguments) |argument_index| { - try unit.writeValue(module, list, allocator, argument_index, indentation); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = argument_index, + .type_index = Compilation.Type.Index.invalid, + }); try list.appendSlice(allocator, ", "); } @@ -247,68 +731,149 @@ pub const TranslationUnit = struct { try list.appendSlice(allocator, "__builtin_unreachable()"); } - fn writeCall(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, call_index: Compilation.Call.Index, indentation: usize) !void { + fn writeCall(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, call_index: Compilation.Call.Index, function_return_type: Compilation.Type.Index, indentation: usize) !void { const call = module.calls.get(call_index); const call_value = module.values.get(call.value); - const callable_name = switch (call_value.*) { - .function_definition => |function_definition_index| module.getName(module.function_name_map.get(function_definition_index).?).?, + switch (call_value.*) { + .function_definition => |function_definition_index| { + const name = try unit.renderFunctionName(module, allocator, function_definition_index); + try list.appendSlice(allocator, name); + try list.append(allocator, '('); + }, + .field_access => |field_access_index| { + const field_access = module.field_accesses.get(field_access_index); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = field_access.declaration_reference, + .type_index = function_return_type, + }); + + const left_type = module.types.get(module.values.get(field_access.declaration_reference).declaration_reference.type); + const is_pointer = switch (left_type.*) { + .pointer => true, + else => false, + }; + + if (is_pointer) { + try list.appendSlice(allocator, "->"); + } else { + try list.append(allocator, '.'); + } + + const field = module.container_fields.get(field_access.field); + const field_name = module.getName(field.name).?; + try list.appendSlice(allocator, field_name); + try list.append(allocator, '('); + }, else => |t| @panic(@tagName(t)), - }; - try list.writer(allocator).print("{s}(", .{callable_name}); + } if (!call.arguments.invalid) { const argument_list = module.argument_lists.get(call.arguments); - for (argument_list.array.items) |argument_index| { - try unit.writeValue(module, list, allocator, argument_index, indentation); - try list.appendSlice(allocator, ", "); + + if (argument_list.array.items.len > 0) { + for (argument_list.array.items) |argument_index| { + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = argument_index, + .type_index = Compilation.Type.Index.invalid, + }); + try list.appendSlice(allocator, ", "); + } + + _ = list.pop(); + _ = list.pop(); } - _ = list.pop(); - _ = list.pop(); } try list.append(allocator, ')'); } - fn writeValue(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, value_index: Compilation.Value.Index, indentation: usize) anyerror!void { + const ValueArguments = struct { + value_index: Compilation.Value.Index, + type_index: Compilation.Type.Index, + }; + + fn writeValue(unit: *TranslationUnit, module: *Module, list: *ArrayList(u8), allocator: Allocator, function_return_type: Compilation.Type.Index, indentation: usize, arguments: ValueArguments) anyerror!void { + const value_index = arguments.value_index; + const type_index = arguments.type_index; + _ = type_index; const value = module.values.get(value_index); switch (value.*) { .integer => |integer| { try list.writer(allocator).print("{}", .{integer.value}); }, .declaration_reference => |declaration_reference| { - const declaration = module.declarations.get(declaration_reference.value); - const declaration_name = module.getName(declaration.name).?; - try list.appendSlice(allocator, declaration_name); + const mangle = true; + const name = try unit.renderDeclarationName(module, allocator, declaration_reference.value, mangle); + try list.appendSlice(allocator, name); }, .binary_operation => |binary_operation_index| { const binary_operation = module.binary_operations.get(binary_operation_index); - try unit.writeValue(module, list, allocator, binary_operation.left, indentation); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = binary_operation.left, + .type_index = binary_operation.type, + }); try list.append(allocator, ' '); + switch (binary_operation.id) { .add => try list.append(allocator, '+'), .sub => try list.append(allocator, '-'), - .logical_and => try list.append(allocator, '&'), - .logical_or => try list.append(allocator, '|'), - .logical_xor => try list.append(allocator, '^'), + .bit_and => try list.append(allocator, '&'), + .bit_or => try list.append(allocator, '|'), + .bit_xor => try list.append(allocator, '^'), .multiply => try list.append(allocator, '*'), .divide => try list.append(allocator, '/'), + .compare_greater_than => try list.append(allocator, '>'), + .compare_less_than => try list.append(allocator, '<'), .shift_left => try list.appendSlice(allocator, "<<"), .shift_right => try list.appendSlice(allocator, ">>"), .compare_equal => try list.appendSlice(allocator, "=="), + .compare_greater_or_equal => try list.appendSlice(allocator, ">="), + .compare_less_or_equal => try list.appendSlice(allocator, "<="), } + try list.append(allocator, ' '); - try unit.writeValue(module, list, allocator, binary_operation.right, indentation); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = binary_operation.right, + .type_index = binary_operation.type, + }); }, .sign_extend => |cast_index| { const sign_extend = module.casts.get(cast_index); - try unit.writeValue(module, list, allocator, sign_extend.value, indentation); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = sign_extend.value, + .type_index = sign_extend.type, + }); }, .cast => |cast_index| { const cast = module.casts.get(cast_index); try list.append(allocator, '('); try unit.writeType(module, list, allocator, cast.type); try list.append(allocator, ')'); - try unit.writeValue(module, list, allocator, cast.value, indentation); + const cast_value = module.values.get(cast.value); + const cast_value_type = module.types.get(cast_value.getType(module)); + + switch (cast_value_type.*) { + .@"struct" => |struct_index| { + const struct_type = module.structs.get(struct_index); + switch (struct_type.backing_type.invalid) { + false => { + try list.appendSlice(allocator, "*("); + try unit.writeType(module, list, allocator, struct_type.backing_type); + try list.appendSlice(allocator, "*)&("); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = cast.value, + .type_index = function_return_type, + }); + try list.append(allocator, ')'); + }, + true => @panic("Unable to bitcast non-packed struct"), + } + }, + else => try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = cast.value, + .type_index = Compilation.Type.Index.invalid, + }), + } }, .string_literal => |string_literal_hash| { try list.appendSlice(allocator, "(const u8*)"); @@ -317,31 +882,257 @@ pub const TranslationUnit = struct { try list.appendSlice(allocator, string_literal); try list.append(allocator, '"'); }, - .@"unreachable" => try writeUnreachable(list, allocator), - .call => |call_index| try unit.writeCall(module, list, allocator, call_index, indentation), - .syscall => |syscall_index| try unit.writeSyscall(module, list, allocator, syscall_index, indentation), + .@"unreachable" => { + try writeUnreachable(list, allocator); + }, + .call => |call_index| try unit.writeCall(module, list, allocator, call_index, function_return_type, indentation), + .syscall => |syscall_index| try unit.writeSyscall(module, list, allocator, syscall_index, function_return_type, indentation), .bool => |boolean| try list.appendSlice(allocator, if (boolean) "true" else "false"), - .block => |block_index| try unit.writeBlock(module, list, allocator, block_index, indentation + 1), + .block => |block_index| try unit.writeBlock(module, list, allocator, block_index, function_return_type, indentation), + .unary_operation => |unary_operation_index| { + const unary_operation = module.unary_operations.get(unary_operation_index); + const expression_character: u8 = switch (unary_operation.id) { + .boolean_not => '!', + .negation => '-', + .address_of => '&', + .pointer_dereference => '*', + }; + + try list.append(allocator, expression_character); + try list.append(allocator, '('); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = unary_operation.value, + .type_index = unary_operation.type, + }); + try list.append(allocator, ')'); + }, + .container_initialization => |container_initialization_index| { + const container_initialization = module.container_initializations.get(container_initialization_index); + try list.append(allocator, '('); + try unit.writeType(module, list, allocator, container_initialization.type); + try list.appendSlice(allocator, ") {\n"); + + const container_type = module.types.get(container_initialization.type); + const container_fields = switch (container_type.*) { + .@"struct" => module.structs.get(container_type.@"struct").fields, + else => |t| @panic(@tagName(t)), + }; + + for (container_initialization.field_initializations.items, container_fields.items) |field_initialization_index, container_field_index| { + try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); + try list.append(allocator, '.'); + const container_field = module.container_fields.get(container_field_index); + const field_name = module.getName(container_field.name).?; + try list.appendSlice(allocator, field_name); + try list.appendSlice(allocator, " = "); + try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ + .value_index = field_initialization_index, + .type_index = container_field.type, + }); + try list.appendSlice(allocator, ",\n"); + } + + try list.appendNTimes(allocator, ' ', indentation * margin_width); + try list.append(allocator, '}'); + }, + .field_access => |field_access_index| { + const field_access = module.field_accesses.get(field_access_index); + const left = module.values.get(field_access.declaration_reference); + const left_type = module.types.get(left.getType(module)); + const right_field = module.container_fields.get(field_access.field); + const right_field_name = module.getName(right_field.name).?; + const is_pointer = switch (left_type.*) { + .@"struct" => false, + .pointer => true, + else => |t| @panic(@tagName(t)), + }; + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = field_access.declaration_reference, + .type_index = right_field.type, + }); + + if (is_pointer) { + try list.appendSlice(allocator, "->"); + } else { + try list.append(allocator, '.'); + } + + try list.appendSlice(allocator, right_field_name); + }, + .pointer_null_literal => try list.appendSlice(allocator, "nullptr"), + .optional_null_literal => { + assert(!arguments.type_index.invalid); + try list.append(allocator, '('); + try unit.writeType(module, list, allocator, arguments.type_index); + try list.appendSlice(allocator, ") { .is_null = true }"); + }, + .slice => |slice_index| { + const slice = module.slices.get(slice_index); + const sliceable = module.values.get(slice.sliceable); + + const sliceable_type_index = switch (sliceable.*) { + .declaration_reference => |declaration_reference| declaration_reference.type, + else => |t| @panic(@tagName(t)), + }; + const sliceable_type = module.types.get(sliceable_type_index); + const sliceable_element_type = switch (sliceable_type.*) { + .pointer => |pointer| pointer.element_type, + else => |t| @panic(@tagName(t)), + }; + + try list.appendSlice(allocator, "(Slice_"); + try unit.writeType(module, list, allocator, sliceable_element_type); + try list.appendSlice(allocator, ") {\n"); + + try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); + try list.appendSlice(allocator, ".ptr = "); + + switch (sliceable_type.*) { + .pointer => { + try list.append(allocator, '('); + try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ + .value_index = slice.sliceable, + .type_index = sliceable_type_index, + }); + try list.appendSlice(allocator, ") + ("); + try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ + .value_index = slice.start, + .type_index = Compilation.Type.Index.invalid, + }); + try list.appendSlice(allocator, "),\n"); + }, + else => |t| @panic(@tagName(t)), + } + + try list.appendNTimes(allocator, ' ', (indentation + 1) * margin_width); + try list.appendSlice(allocator, ".len = "); + + switch (sliceable_type.*) { + .pointer => { + switch (slice.end.invalid) { + false => { + try list.append(allocator, '('); + try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ + .value_index = slice.end, + .type_index = Compilation.Type.Index.invalid, + }); + try list.appendSlice(allocator, ") - ("); + try unit.writeValue(module, list, allocator, function_return_type, indentation + 1, .{ + .value_index = slice.start, + .type_index = Compilation.Type.Index.invalid, + }); + try list.appendSlice(allocator, ")\n"); + }, + true => { + unreachable; + }, + } + }, + else => |t| @panic(@tagName(t)), + } + + try list.appendNTimes(allocator, ' ', indentation * margin_width); + try list.append(allocator, '}'); + }, + .function_definition => |function_definition_index| { + const function_name = try unit.writeFunctionDefinition(module, allocator, function_definition_index); + try list.appendSlice(allocator, function_name); + }, + .optional_check => |optional_check_index| { + const optional_check = module.optional_checks.get(optional_check_index); + const optional_type = module.types.get(module.values.get(optional_check.value).getType(module)); + assert(optional_type.* == .optional); + const optional_element_type = module.types.get(optional_type.optional.element_type); + const is_null_suffix_expression = switch (optional_element_type.*) { + .pointer => false, + else => true, + }; + if (is_null_suffix_expression) { + try list.append(allocator, '!'); + } + + try list.append(allocator, '('); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = optional_check.value, + .type_index = Compilation.Type.Index.invalid, + }); + try list.append(allocator, ')'); + + if (is_null_suffix_expression) { + try list.appendSlice(allocator, ".is_null"); + } + }, + .optional_unwrap => |optional_unwrap_index| { + const optional_unwrap = module.optional_unwraps.get(optional_unwrap_index); + const optional_value = module.values.get(optional_unwrap.value); + const optional_type = module.types.get(optional_value.getType(module)); + assert(optional_type.* == .optional); + const optional_element_type_index = optional_type.optional.element_type; + const optional_element_type = module.types.get(optional_element_type_index); + + try list.append(allocator, '('); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = optional_unwrap.value, + .type_index = optional_element_type_index, + }); + try list.append(allocator, ')'); + + switch (optional_element_type.*) { + .pointer => {}, + else => try list.appendSlice(allocator, ".value"), + } + }, + .slice_access => |slice_access_index| { + const slice_access = module.slice_accesses.get(slice_access_index); + try list.append(allocator, '('); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = slice_access.value, + .type_index = slice_access.type, + }); + try list.appendSlice(allocator, ")."); + try list.appendSlice(allocator, @tagName(slice_access.field)); + }, + .indexed_access => |indexed_access_index| { + const indexed_access = module.indexed_accesses.get(indexed_access_index); + try list.append(allocator, '('); + const indexed_expression_index = indexed_access.indexed_expression; + const indexed_expression = module.values.get(indexed_expression_index); + const indexed_expression_type_index = indexed_expression.getType(module); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = indexed_expression_index, + .type_index = indexed_expression_type_index, + }); + + const indexed_expression_type = module.types.get(indexed_expression_type_index); + switch (indexed_expression_type.*) { + .slice => { + try list.appendSlice(allocator, ".ptr"); + }, + else => |t| @panic(@tagName(t)), + } + + try list.appendSlice(allocator, ")["); + try unit.writeValue(module, list, allocator, function_return_type, indentation, .{ + .value_index = indexed_access.index_expression, + .type_index = Compilation.Type.Index.invalid, + }); + try list.append(allocator, ']'); + }, else => |t| @panic(@tagName(t)), } } }; -// fn writeDeclarationReference(module: *Module, list: *ArrayList(u8), allocator: Allocator, declaration_reference: Compilation.Declaration.Reference) !void { -// _ = module; -// _ = list; -// _ = allocator; -// _ = declaration_reference; -// } - pub fn initialize(compilation: *Compilation, module: *Module, descriptor: Compilation.Module.Descriptor) !void { const allocator = compilation.base_allocator; - const unit = try TranslationUnit.create(module, allocator); + var unit = try TranslationUnit.create(module, allocator); const c_source_file_path = try std.mem.concat(allocator, u8, &.{ descriptor.executable_path, ".c" }); const c_source_file = try std.fs.cwd().createFile(c_source_file_path, .{}); + try unit.type_forward_declarations.append(allocator, '\n'); + var offset: u64 = 0; - const slices = [_][]const u8{ unit.type_declarations.items, unit.function_declarations.items, unit.string_literals.items, unit.function_definitions.items }; + const slices = [_][]const u8{ unit.primitive_type_declarations.items, unit.type_forward_declarations.items, unit.type_declarations.items, unit.function_declarations.items, unit.global_variable_declarations.items, unit.string_literals.items, unit.function_definitions.items }; for (slices) |slice| { try c_source_file.pwriteAll(slice, offset); offset += slice.len; diff --git a/bootstrap/frontend/lexical_analyzer.zig b/bootstrap/frontend/lexical_analyzer.zig index b926b9e..b3b5464 100644 --- a/bootstrap/frontend/lexical_analyzer.zig +++ b/bootstrap/frontend/lexical_analyzer.zig @@ -46,6 +46,10 @@ pub const Token = packed struct(u64) { fixed_keyword_enum = 0x17, fixed_keyword_union = 0x18, fixed_keyword_extern = 0x19, + fixed_keyword_null = 0x1a, + fixed_keyword_align = 0x1b, + fixed_keyword_export = 0x1c, + fixed_keyword_cc = 0x1d, keyword_unsigned_integer = 0x1f, keyword_signed_integer = 0x20, bang = '!', // 0x21 @@ -106,6 +110,10 @@ pub const FixedKeyword = enum { @"enum", @"union", @"extern", + null, + @"align", + @"export", + cc, }; pub const Result = struct { @@ -169,10 +177,6 @@ pub fn analyze(allocator: Allocator, text: []const u8, file_index: File.Index) ! inline else => |comptime_fixed_keyword| @field(Token.Id, "fixed_keyword_" ++ @tagName(comptime_fixed_keyword)), } else .identifier; }, - '(', ')', '{', '}', '[', ']', '=', ';', '#', '@', ',', '.', ':', '>', '<', '!', '+', '-', '*', '\\', '/', '&', '|', '^' => |operator| blk: { - index += 1; - break :blk @enumFromInt(operator); - }, '0'...'9' => blk: { // Detect other non-decimal literals if (text[index] == '0' and index + 1 < text.len) { @@ -217,6 +221,10 @@ pub fn analyze(allocator: Allocator, text: []const u8, file_index: File.Index) ! index += 1; continue; }, + '(', ')', '{', '}', '[', ']', '=', ';', '#', '@', ',', '.', ':', '>', '<', '!', '+', '-', '*', '\\', '/', '&', '|', '^', '?', '$' => |operator| blk: { + index += 1; + break :blk @enumFromInt(operator); + }, else => |ch| { std.debug.panic("NI: '{c}'", .{ch}); }, diff --git a/bootstrap/frontend/semantic_analyzer.zig b/bootstrap/frontend/semantic_analyzer.zig index bf57480..b9e9d8f 100644 --- a/bootstrap/frontend/semantic_analyzer.zig +++ b/bootstrap/frontend/semantic_analyzer.zig @@ -20,6 +20,7 @@ const Intrinsic = Compilation.Intrinsic; const Loop = Compilation.Loop; const Scope = Compilation.Scope; const ScopeType = Compilation.ScopeType; +const Slice = Compilation.Slice; const Struct = Compilation.Struct; const Type = Compilation.Type; const Value = Compilation.Value; @@ -37,17 +38,23 @@ pub const Logger = enum { @"switch", block, call, + scope_lookup, + debug, + fn_return_type, pub var bitset = std.EnumSet(Logger).initMany(&.{ - .type, + // .type, .identifier, - .symbol_declaration, - .scope_node, - .node, - .typecheck, - .@"switch", - .block, - .call, + // .symbol_declaration, + // .scope_node, + // .node, + // .typecheck, + // .@"switch", + // .block, + // .call, + // // .scope_lookup, + .debug, + .fn_return_type, }); }; @@ -63,10 +70,43 @@ const data_structures = @import("../data_structures.zig"); const ArrayList = data_structures.ArrayList; const HashMap = data_structures.AutoHashMap; +const ExpectType = union(enum) { + none, + type_index: Type.Index, + flexible_integer: FlexibleInteger, + + pub const none = ExpectType{ + .none = {}, + }; + pub const boolean = ExpectType{ + .type_index = Type.boolean, + }; + + pub const @"type" = ExpectType{ + .type_index = Type.type, + }; + + const FlexibleInteger = struct { + byte_count: u8, + sign: ?bool = null, + }; +}; + +pub var unreachable_index = Value.Index.invalid; +pub var optional_null_index = Value.Index.invalid; +pub var pointer_null_index = Value.Index.invalid; + +pub var pointer_to_any_type = Type.Index.invalid; +pub var optional_pointer_to_any_type = Type.Index.invalid; +pub var optional_any = Type.Index.invalid; + const Analyzer = struct { allocator: Allocator, module: *Module, current_file: File.Index, + current_declaration: Declaration.Index = Declaration.Index.invalid, + payloads: ArrayList(Payload) = .{}, + maybe_count: usize = 0, fn getScopeSourceFile(analyzer: *Analyzer, scope_index: Scope.Index) []const u8 { const scope = analyzer.module.scopes.get(scope_index); @@ -78,7 +118,7 @@ const Analyzer = struct { const scope = analyzer.module.scopes.get(scope_index); const file = analyzer.module.files.get(scope.file); const result = &file.syntactic_analyzer_result.nodes.items[node_index.unwrap()]; - logln(.sema, .scope_node, "Fetching node #{} (0x{x}) from scope #{} from file #{} with id: {s}\n", .{ node_index.uniqueInteger(), @intFromPtr(result), scope_index.uniqueInteger(), scope.file.uniqueInteger(), @tagName(result.id) }); + // logln(.sema, .scope_node, "Fetching node #{} (0x{x}) from scope #{} from file #{} with id: {s}", .{ node_index.uniqueInteger(), @intFromPtr(result), scope_index.uniqueInteger(), scope.file.uniqueInteger(), @tagName(result.id) }); return result.*; } @@ -124,51 +164,49 @@ const Analyzer = struct { return value_allocation.index; } - fn unresolved(analyzer: *Analyzer, node_index: Node.Index) !Value.Allocation { + fn unresolvedAllocate(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value.Allocation { const value_allocation = try analyzer.module.values.addOne(analyzer.allocator); value_allocation.ptr.* = .{ .unresolved = .{ .node_index = node_index, }, }; - + try analyzer.resolveNode(value_allocation.ptr, scope_index, expect_type, node_index); return value_allocation; } - fn unresolvedAllocate(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value.Allocation { - const new = try analyzer.unresolved(node_index); - try analyzer.resolveNode(new.ptr, scope_index, expect_type, node_index); - return new; - } - - fn block(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) anyerror!Block.Index { - logln(.sema, .block, "Resolving block from scope #{} in file #{}\n", .{ scope_index.uniqueInteger(), analyzer.module.scopes.get(scope_index).file.uniqueInteger() }); + fn block(analyzer: *Analyzer, parent_scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) anyerror!Block.Index { + logln(.sema, .block, "Resolving block from scope #{} in file #{}", .{ parent_scope_index.uniqueInteger(), analyzer.module.scopes.get(parent_scope_index).file.uniqueInteger() }); var reaches_end = true; - const block_node = analyzer.getScopeNode(scope_index, node_index); - var statement_nodes = ArrayList(Node.Index){}; + const block_node = analyzer.getScopeNode(parent_scope_index, node_index); + const statement_nodes = analyzer.getScopeNodeList(parent_scope_index, analyzer.getScopeNode(parent_scope_index, block_node.left)); - switch (block_node.id) { - .block_one, .comptime_block_one => { - try statement_nodes.append(analyzer.allocator, block_node.left); - }, - .block_zero, .comptime_block_zero => {}, - .block_two, .comptime_block_two => { - try statement_nodes.append(analyzer.allocator, block_node.left); - try statement_nodes.append(analyzer.allocator, block_node.right); - }, - .block, .comptime_block => statement_nodes = analyzer.getScopeNodeList(scope_index, analyzer.getScopeNode(scope_index, block_node.left)), - else => |t| @panic(@tagName(t)), + const new_scope = try analyzer.allocateScope(.{ + .parent = parent_scope_index, + .file = analyzer.module.scopes.get(parent_scope_index).file, + .token = block_node.token, + }); + const scope_index = new_scope.index; + + var statements = ArrayList(Value.Index){}; + + for (analyzer.payloads.items) |payload| { + const declaration_index = try analyzer.declarationCommon(scope_index, .local, payload.mutability, payload.name, payload.type, payload.value, null); + const statement_value_allocation = try analyzer.module.values.append(analyzer.allocator, .{ + .declaration = declaration_index, + }); + try statements.append(analyzer.allocator, statement_value_allocation.index); } + analyzer.payloads.clearRetainingCapacity(); + const is_comptime = switch (block_node.id) { - .comptime_block, .comptime_block_zero, .comptime_block_one, .comptime_block_two => true, - .block, .block_zero, .block_one, .block_two => false, + .comptime_block => true, + .block => false, else => |t| @panic(@tagName(t)), }; - logln(.sema, .block, "Is comptime: {}\n", .{is_comptime}); - - var statements = ArrayList(Value.Index){}; + logln(.sema, .block, "Is comptime: {}", .{is_comptime}); for (statement_nodes.items) |statement_node_index| { if (!reaches_end) { @@ -176,6 +214,7 @@ const Analyzer = struct { } const statement_node = analyzer.getScopeNode(scope_index, statement_node_index); + logln(.sema, .node, "Trying to resolve statement of id {s}", .{@tagName(statement_node.id)}); const statement_value_index = switch (statement_node.id) { .assign => (try analyzer.module.values.append(analyzer.allocator, try analyzer.processAssignment(scope_index, statement_node_index))).index, .simple_while => blk: { @@ -222,7 +261,7 @@ const Analyzer = struct { break :blk return_value_allocation.index; }, - .call_two, .call => blk: { + .call => blk: { const call_index = try analyzer.processCall(scope_index, statement_node_index); const call_statement = try analyzer.module.values.append(analyzer.allocator, .{ .call = call_index, @@ -244,12 +283,75 @@ const Analyzer = struct { break :blk switch_value_allocation.index; }, .if_else => blk: { - const if_else_value = try analyzer.processIfElse(scope_index, expect_type, statement_node_index); + const if_else_node_index = statement_node_index; + const payload_node_index = Node.Index.invalid; + const if_else_value = try analyzer.processIfElse(scope_index, expect_type, if_else_node_index, payload_node_index); const branch = analyzer.module.branches.get(if_else_value.branch); reaches_end = branch.reaches_end; - const branch_statement = try analyzer.module.values.append(analyzer.allocator, if_else_value); + assert(if_else_value.maybe_payload_declaration_index == null); + const branch_value = try analyzer.module.values.append(analyzer.allocator, .{ + .branch = if_else_value.branch, + }); + break :blk branch_value.index; + }, + .if_else_payload => blk: { + const if_else_node_index = statement_node.left; + const payload_node_index = statement_node.right; + const if_else_value = try analyzer.processIfElse(scope_index, expect_type, if_else_node_index, payload_node_index); + + if (if_else_value.maybe_payload_declaration_index) |maybe_payload_declaration| { + try statements.append(analyzer.allocator, maybe_payload_declaration); + } + + const branch = analyzer.module.branches.get(if_else_value.branch); + reaches_end = branch.reaches_end; + const branch_statement = try analyzer.module.values.append(analyzer.allocator, .{ + .branch = if_else_value.branch, + }); break :blk branch_statement.index; }, + .@"if", + .if_payload, + => blk: { + const if_statement_node_index = switch (statement_node.id) { + .@"if" => statement_node_index, + .if_payload => statement_node.left, + else => unreachable, + }; + + const payload_node_index = switch (statement_node.id) { + .@"if" => Node.Index.invalid, + .if_payload => statement_node.right, + else => unreachable, + }; + + const if_expression = try analyzer.processIf(scope_index, expect_type, if_statement_node_index, payload_node_index); + + if (if_expression.maybe_payload_declaration_value) |maybe_payload_declaration| { + try statements.append(analyzer.allocator, maybe_payload_declaration); + } + + const branch = try analyzer.module.branches.append(analyzer.allocator, .{ + .expression = if_expression.expression, + .taken_expression = if_expression.taken_expression, + .not_taken_expression = Value.Index.invalid, + .reaches_end = true, // The else branch, as it doesnt exist, always reaches the end + }); + const branch_statement = try analyzer.module.values.append(analyzer.allocator, .{ + .branch = branch.index, + }); + break :blk branch_statement.index; + }, + .compiler_intrinsic => blk: { + const intrinsic_value = try analyzer.compilerIntrinsic(scope_index, ExpectType.none, statement_node_index); + const value = try analyzer.module.values.append(analyzer.allocator, intrinsic_value); + break :blk value.index; + }, + .assembly_block => blk: { + const assembly_value = try analyzer.assembly(scope_index, ExpectType.none, statement_node_index); + const value = try analyzer.module.values.append(analyzer.allocator, assembly_value); + break :blk value.index; + }, else => |t| @panic(@tagName(t)), }; @@ -264,14 +366,199 @@ const Analyzer = struct { return block_allocation.index; } + fn processAssemblyStatements(analyzer: *Analyzer, comptime architecture: type, scope_index: Scope.Index, assembly_statement_nodes: []const Node.Index) ![]Compilation.Assembly.Instruction.Index { + var instructions = try ArrayList(Compilation.Assembly.Instruction.Index).initCapacity(analyzer.allocator, assembly_statement_nodes.len); + + for (assembly_statement_nodes) |assembly_statement_node_index| { + const assembly_statement_node = analyzer.getScopeNode(scope_index, assembly_statement_node_index); + const instruction_name = analyzer.tokenIdentifier(scope_index, assembly_statement_node.token); + const instruction = inline for (@typeInfo(architecture.Instruction).Enum.fields) |instruction_enum_field| { + if (equal(u8, instruction_name, instruction_enum_field.name)) { + break @field(architecture.Instruction, instruction_enum_field.name); + } + } else unreachable; + const operand_node_list_node = analyzer.getScopeNode(scope_index, assembly_statement_node.left); + const operand_nodes = analyzer.getScopeNodeList(scope_index, operand_node_list_node); + + var operand_list = try ArrayList(Compilation.Assembly.Operand).initCapacity(analyzer.allocator, operand_nodes.items.len); + + for (operand_nodes.items) |operand_node_index| { + const operand_node = analyzer.getScopeNode(scope_index, operand_node_index); + const operand: Compilation.Assembly.Operand = switch (operand_node.id) { + .assembly_register => blk: { + const register_name = analyzer.tokenIdentifier(scope_index, operand_node.token); + + const register = inline for (@typeInfo(architecture.Register).Enum.fields) |register_enum_field| { + if (equal(u8, register_name, register_enum_field.name)) { + break @field(architecture.Register, register_enum_field.name); + } + } else unreachable; + break :blk .{ + .register = @intFromEnum(register), + }; + }, + .number_literal => switch (std.zig.parseNumberLiteral(analyzer.numberBytes(scope_index, operand_node.token))) { + .int => |integer| .{ + .number_literal = integer, + }, + else => |t| @panic(@tagName(t)), + }, + .identifier => .{ + .value_index = try analyzer.doIdentifier(scope_index, ExpectType.none, operand_node.token, scope_index), + }, + else => |t| @panic(@tagName(t)), + }; + operand_list.appendAssumeCapacity(operand); + } + + const assembly_instruction = try analyzer.module.assembly_instructions.append(analyzer.allocator, .{ + .id = @intFromEnum(instruction), + .operands = operand_list.items, + }); + instructions.appendAssumeCapacity(assembly_instruction.index); + } + + return instructions.items; + } + + fn assembly(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { + _ = expect_type; + const assembly_node = analyzer.getScopeNode(scope_index, node_index); + assert(assembly_node.id == .assembly_block); + assert(assembly_node.right.invalid); + const assembly_statement_node_list = analyzer.getScopeNode(scope_index, assembly_node.left); + const assembly_statement_nodes = analyzer.getScopeNodeList(scope_index, assembly_statement_node_list); + const assembly_statements = switch (analyzer.module.descriptor.target.cpu.arch) { + .x86_64 => try analyzer.processAssemblyStatements(Compilation.Assembly.x86_64, scope_index, assembly_statement_nodes.items), + else => unreachable, + }; + const assembly_block = try analyzer.module.assembly_blocks.append(analyzer.allocator, .{ + .instructions = assembly_statements, + }); + return .{ + .assembly_block = assembly_block.index, + }; + } + + fn processCallToFunctionPrototype(analyzer: *Analyzer, scope_index: Scope.Index, function_prototype_index: Function.Prototype.Index, call_argument_node_list: []const Node.Index, method_object: Value.Index) !ArrayList(Value.Index) { + const function_prototype = analyzer.module.function_prototypes.get(function_prototype_index); + + const method_object_count = @intFromBool(!method_object.invalid); + const call_argument_count = call_argument_node_list.len + method_object_count; + + var argument_array = try ArrayList(Value.Index).initCapacity(analyzer.allocator, call_argument_count); + + if (!method_object.invalid) { + const first_argument_index = function_prototype.arguments.?[0]; + const first_argument = analyzer.module.declarations.get(first_argument_index); + const first_argument_type = first_argument.type; + const method_object_value = analyzer.module.values.get(method_object); + const method_object_type = method_object_value.getType(analyzer.module); + // TODO: further typecheck + const method_object_argument = switch (analyzer.module.types.get(first_argument_type).*) { + .pointer => switch (analyzer.module.types.get(method_object_type).*) { + .pointer => method_object, + else => blk: { + const unary = try analyzer.module.unary_operations.append(analyzer.allocator, .{ + .id = .address_of, + .value = method_object, + .type = first_argument_type, + }); + const address_of = try analyzer.module.values.append(analyzer.allocator, .{ + .unary_operation = unary.index, + }); + break :blk address_of.index; + }, + }, + else => method_object, + }; + + argument_array.appendAssumeCapacity(method_object_argument); + } + + if (function_prototype.arguments) |argument_declarations| { + logln(.sema, .call, "Argument declaration count: {}. Argument node list count: {}", .{ argument_declarations.len, call_argument_node_list.len }); + + if (argument_declarations.len == call_argument_count) { + for (argument_declarations[method_object_count..], call_argument_node_list, 0..) |argument_declaration_index, argument_node_index, _index| { + const index = _index + method_object_count; + const argument_declaration = analyzer.module.declarations.get(argument_declaration_index); + const argument_node = analyzer.getScopeNode(scope_index, argument_node_index); + const value_node_index = switch (argument_node.id) { + .identifier => blk: { + const identifier = analyzer.tokenIdentifier(scope_index, argument_node.token); + const identifier_hash = try analyzer.processIdentifier(identifier); + + if (identifier_hash == argument_declaration.name) { + break :blk argument_node_index; + } else { + const call_site_name = analyzer.module.getName(identifier_hash).?; + const definition_site_name = analyzer.module.getName(argument_declaration.name).?; + // const function_name = analyzer.module.getName(analyzer.module.function_name_map.get(function_index).?).?; + std.debug.panic("At function call, argument #{} must be named the same way. Call site was name '{s}' while function definition has it named as '{s}'", .{ index, call_site_name, definition_site_name }); + } + }, + .named_argument => blk: { + const identifier_node = analyzer.getScopeNode(scope_index, argument_node.left); + if (identifier_node.id != .identifier) { + @panic("expected identifier"); + } + const identifier = analyzer.tokenIdentifier(scope_index, identifier_node.token); + const identifier_hash = try analyzer.processIdentifier(identifier); + + if (identifier_hash == argument_declaration.name) { + break :blk argument_node.right; + } else { + const call_site_name = analyzer.module.getName(identifier_hash).?; + const definition_site_name = analyzer.module.getName(argument_declaration.name).?; + // const function_name = analyzer.module.getName(analyzer.module.function_name_map.get(function_index).?).?; + std.debug.panic("At function call, argument #{} must be named the same way. Call site was name '{s}' while function definition has it named as '{s}'", .{ index, call_site_name, definition_site_name }); + } + }, + else => |node_id| { + const definition_site_name = analyzer.module.getName(argument_declaration.name).?; + // const function_name = analyzer.module.getName(analyzer.module.function_name_map.get(function_index).?).?; + + std.debug.panic("Argument #{} of call to function of type {s} must be named as '{s}'", .{ index, @tagName(node_id), definition_site_name }); + }, + }; + const call_argument_allocation = try analyzer.unresolvedAllocate(scope_index, ExpectType{ + .type_index = argument_declaration.type, + }, value_node_index); + const call_site_type = call_argument_allocation.ptr.getType(analyzer.module); + const result = try analyzer.typeCheck(ExpectType{ + .type_index = argument_declaration.type, + }, call_site_type); + assert(result == .success); + + argument_array.appendAssumeCapacity(call_argument_allocation.index); + } + } else { + panic("{s} call has argument count mismatch: call has {}, function declaration has {}", .{ switch (method_object.invalid) { + true => "Function", + false => "Method function", + }, call_argument_count, argument_declarations.len }); + } + } + + return argument_array; + } + fn processCall(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Call.Index { const node = analyzer.getScopeNode(scope_index, node_index); - logln(.sema, .call, "Node index: {}. Left index: {}\n", .{ node_index.uniqueInteger(), node.left.uniqueInteger() }); + logln(.sema, .call, "Node index: {}. Left index: {}", .{ node_index.uniqueInteger(), node.left.uniqueInteger() }); assert(!node.left.invalid); + var is_field_access = false; + const left_value_index = switch (!node.left.invalid) { true => blk: { const member_or_namespace_node_index = node.left; assert(!member_or_namespace_node_index.invalid); + const n = analyzer.getScopeNode(scope_index, member_or_namespace_node_index); + is_field_access = switch (n.id) { + .field_access => true, + else => false, + }; const this_value_allocation = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, member_or_namespace_node_index); break :blk this_value_allocation.index; }, @@ -281,116 +568,89 @@ const Analyzer = struct { const left_type = switch (left_value_index.invalid) { false => switch (analyzer.module.values.get(left_value_index).*) { .function_definition => |function_index| analyzer.module.function_prototypes.get(analyzer.module.types.get(analyzer.module.function_definitions.get(function_index).prototype).function).return_type, + .field_access => |field_access_index| blk: { + const field_access_type_index = analyzer.module.container_fields.get(analyzer.module.field_accesses.get(field_access_index).field).type; + const field_access_type = analyzer.module.types.get(field_access_type_index); + break :blk switch (field_access_type.*) { + .pointer => |pointer| b: { + assert(!pointer.many); + assert(pointer.@"const"); + const appointee_type = analyzer.module.types.get(pointer.element_type); + break :b switch (appointee_type.*) { + .function => |function_prototype_index| analyzer.module.function_prototypes.get(function_prototype_index).return_type, + else => |t| @panic(@tagName(t)), + }; + }, + else => |t| @panic(@tagName(t)), + }; + }, else => |t| @panic(@tagName(t)), }, true => Type.Index.invalid, }; + const arguments_index = switch (node.id) { - .call, .call_two => |call_tag| (try analyzer.module.argument_lists.append(analyzer.allocator, .{ + .call => (try analyzer.module.argument_lists.append(analyzer.allocator, .{ .array = b: { const argument_list_node_index = node.right; - const call_argument_node_list = switch (call_tag) { - .call => analyzer.getScopeNodeList(scope_index, analyzer.getScopeNode(scope_index, argument_list_node_index)).items, - .call_two => &.{argument_list_node_index}, - else => unreachable, - }; + const call_argument_node_list = analyzer.getScopeNodeList(scope_index, analyzer.getScopeNode(scope_index, argument_list_node_index)); switch (analyzer.module.values.get(left_value_index).*) { .function_definition => |function_index| { const function_definition = analyzer.module.function_definitions.get(function_index); - const function_prototype = analyzer.module.function_prototypes.get(analyzer.module.types.get(function_definition.prototype).function); - const argument_declarations = function_prototype.arguments.?; - logln(.sema, .call, "Argument declaration count: {}. Argument node list count: {}\n", .{ argument_declarations.len, call_argument_node_list.len }); - var argument_array = ArrayList(Value.Index){}; + const function_prototype_index = analyzer.module.types.get(function_definition.prototype).function; - if (argument_declarations.len == call_argument_node_list.len) { - for (argument_declarations, call_argument_node_list, 0..) |argument_declaration_index, argument_node_index, index| { - const argument_declaration = analyzer.module.declarations.get(argument_declaration_index); - const argument_node = analyzer.getScopeNode(scope_index, argument_node_index); - const value_node_index = switch (argument_node.id) { - .identifier => blk: { - const identifier = analyzer.tokenIdentifier(scope_index, argument_node.token); - const identifier_hash = try analyzer.processIdentifier(identifier); - - if (identifier_hash == argument_declaration.name) { - break :blk argument_node_index; - } else { - const call_site_name = analyzer.module.getName(identifier_hash).?; - const definition_site_name = analyzer.module.getName(argument_declaration.name).?; - const function_name = analyzer.module.getName(analyzer.module.function_name_map.get(function_index).?).?; - std.debug.panic("At function '{s}' call, argument #{} must be named the same way. Call site was name '{s}' while function definition has it named as '{s}'", .{ function_name, index, call_site_name, definition_site_name }); - } - }, - .named_argument => blk: { - const identifier_node = analyzer.getScopeNode(scope_index, argument_node.left); - if (identifier_node.id != .identifier) { - @panic("expected identifier"); - } - const identifier = analyzer.tokenIdentifier(scope_index, identifier_node.token); - const identifier_hash = try analyzer.processIdentifier(identifier); - - if (identifier_hash == argument_declaration.name) { - break :blk argument_node.right; - } else { - const call_site_name = analyzer.module.getName(identifier_hash).?; - const definition_site_name = analyzer.module.getName(argument_declaration.name).?; - const function_name = analyzer.module.getName(analyzer.module.function_name_map.get(function_index).?).?; - std.debug.panic("At function '{s}' call, argument #{} must be named the same way. Call site was name '{s}' while function definition has it named as '{s}'", .{ function_name, index, call_site_name, definition_site_name }); - } - }, - else => |node_id| { - const definition_site_name = analyzer.module.getName(argument_declaration.name).?; - const function_name = analyzer.module.getName(analyzer.module.function_name_map.get(function_index).?).?; - - std.debug.panic("Argument #{} of call to function '{s}' of type {s} must be named as '{s}'", .{ index, function_name, @tagName(node_id), definition_site_name }); + const method_object = switch (is_field_access) { + true => mob: { + const field_access_node = analyzer.getScopeNode(scope_index, node.left); + assert(field_access_node.id == .field_access); + const maybe_left_value = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, field_access_node.left); + break :mob switch (analyzer.module.types.get(maybe_left_value.ptr.getType(analyzer.module)).*) { + .type => Value.Index.invalid, + .@"struct" => switch (analyzer.module.values.get(left_value_index).*) { + .function_definition => maybe_left_value.index, + else => |t| @panic(@tagName(t)), }, + // .field_access => maybe_left_value.index, + else => |t| @panic(@tagName(t)), }; - const call_argument_allocation = try analyzer.unresolvedAllocate(scope_index, ExpectType{ - .type_index = argument_declaration.type, - }, value_node_index); - // switch (call_argument_allocation.ptr.*) { - // .integer, - // .string_literal, - // => {}, - // .declaration_reference => |declaration_reference| { - // if (call_argument_declaration.name != argument_declaration.name) { - // const call_site_name = analyzer.module.getName(call_argument_declaration.name).?; - // const definition_site_name = analyzer.module.getName(argument_declaration.name).?; - // const function_name = analyzer.module.getName(analyzer.module.function_name_map.get(function_index).?).?; - // std.debug.panic("At function '{s}' call, argument #{} must be named the same way. Call site was name '{s}' while function definition has it named as '{s}'", .{ function_name, index, call_site_name, definition_site_name }); - // } - // }, - // else => |t| @panic(@tagName(t)), - // } - try call_argument_allocation.ptr.typeCheck(analyzer.module, argument_declaration.type); - // const call_argument_type_index = call_argument_allocation.ptr.getType(analyzer.module); - // const call_argument_type = analyzer.module.types.get(call_argument_type_index); - // if (call_argument_type_index != argument_declaration.type) { - // if (std.meta.activeTag(call_argument_type.*) == std.meta.activeTag(argument_declaration_type.*)) { - // if (!call_argument_type.equalTypeCanCoerce(argument_declaration_type)) { - // unreachable; - // } - // } else { - // try call_argument_type.promote(argument_declaration_type); - // call_argument_allocation.ptr.setType(argument_declaration.type); - // } - // } + }, + false => Value.Index.invalid, + }; + break :b try analyzer.processCallToFunctionPrototype(scope_index, function_prototype_index, call_argument_node_list.items, method_object); + }, + .field_access => |field_access_index| { + const field_access = analyzer.module.field_accesses.get(field_access_index); + const container_field = analyzer.module.container_fields.get(field_access.field); + const container_field_type = analyzer.module.types.get(container_field.type); + switch (container_field_type.*) { + .pointer => |function_pointer| { + if (!function_pointer.@"const") { + unreachable; + } - try argument_array.append(analyzer.allocator, call_argument_allocation.index); - } + if (function_pointer.many) { + unreachable; + } - break :b argument_array; - } else { - panic("Function call has argument count mismatch: call has {}, function declaration has {}\n", .{ call_argument_node_list.len, argument_declarations.len }); + const appointee_type = analyzer.module.types.get(function_pointer.element_type); + switch (appointee_type.*) { + .function => |function_prototype_index| { + break :b try analyzer.processCallToFunctionPrototype(scope_index, function_prototype_index, call_argument_node_list.items, Value.Index.invalid); + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), } }, else => |t| @panic(@tagName(t)), } }, })).index, - .call_one => ArgumentList.Index.invalid, else => |t| @panic(@tagName(t)), }; + const call_allocation = try analyzer.module.calls.append(analyzer.allocator, .{ .value = left_value_index, .arguments = arguments_index, @@ -401,7 +661,7 @@ const Analyzer = struct { return call_allocation.index; } - fn typeCheckEnumLiteral(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index, enum_type: *const Enum) !?u32 { + fn typeCheckEnumLiteral(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index, enum_type: *const Enum) !?Enum.Field.Index { const enum_name = tokenBytes(analyzer.getScopeToken(scope_index, token_index), analyzer.getScopeSourceFile(scope_index)); const enum_name_hash = try analyzer.processIdentifier(enum_name); @@ -409,10 +669,10 @@ const Analyzer = struct { const enum_field = analyzer.module.enum_fields.get(enum_field_index); const existing = analyzer.module.getName(enum_field.name).?; if (enum_field.name == enum_name_hash) { - return enum_name_hash; + return enum_field_index; } - logln(.sema, .typecheck, "Existing enum field \"{s}\" != enum literal \"{s}\"\n", .{ existing, enum_name }); + logln(.sema, .typecheck, "Existing enum field \"{s}\" != enum literal \"{s}\"", .{ existing, enum_name }); } else { return null; } @@ -423,7 +683,7 @@ const Analyzer = struct { const node = analyzer.getScopeNode(scope_index, node_index); assert(node.id == .@"switch"); - analyzer.debugNode(scope_index, node_index); + // analyzer.debugNode(scope_index, node_index); const switch_expr = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); const switch_case_list_node = analyzer.getScopeNode(scope_index, node.right); @@ -439,28 +699,31 @@ const Analyzer = struct { const enum_field_name = analyzer.module.getName(e_field.name); _ = enum_field_name; - var existing_enums = ArrayList(u32){}; - var switch_case_groups = try ArrayList(ArrayList(u32)).initCapacity(analyzer.allocator, switch_case_node_list.len); + var existing_enums = ArrayList(Enum.Field.Index){}; + var switch_case_groups = try ArrayList(ArrayList(Enum.Field.Index)).initCapacity(analyzer.allocator, switch_case_node_list.len); + var else_switch_case_group = false; for (switch_case_node_list, 0..) |switch_case_node_index, index| { _ = index; const switch_case_node = analyzer.getScopeNode(scope_index, switch_case_node_index); + switch (switch_case_node.left.invalid) { false => { const switch_case_condition_node = analyzer.getScopeNode(scope_index, switch_case_node.left); - var switch_case_group = ArrayList(u32){}; + var switch_case_group = ArrayList(Enum.Field.Index){}; + switch (switch_case_condition_node.id) { .enum_literal => { - if (try typeCheckEnumLiteral(analyzer, scope_index, switch_case_condition_node.token + 1, enum_type)) |enum_name_hash| { + if (try typeCheckEnumLiteral(analyzer, scope_index, switch_case_condition_node.token + 1, enum_type)) |enum_field_index| { for (existing_enums.items) |existing| { - if (enum_name_hash == existing) { + if (enum_field_index.eq(existing)) { // Duplicate case unreachable; } } - try switch_case_group.append(analyzer.allocator, enum_name_hash); - try existing_enums.append(analyzer.allocator, enum_name_hash); + try switch_case_group.append(analyzer.allocator, enum_field_index); + try existing_enums.append(analyzer.allocator, enum_field_index); } else { unreachable; } @@ -468,20 +731,21 @@ const Analyzer = struct { .node_list => { const node_list = analyzer.getScopeNodeList(scope_index, switch_case_condition_node); try switch_case_group.ensureTotalCapacity(analyzer.allocator, node_list.items.len); + for (node_list.items) |case_condition_node_index| { const case_condition_node = analyzer.getScopeNode(scope_index, case_condition_node_index); switch (case_condition_node.id) { .enum_literal => { - if (try typeCheckEnumLiteral(analyzer, scope_index, case_condition_node.token + 1, enum_type)) |enum_name_hash| { + if (try typeCheckEnumLiteral(analyzer, scope_index, case_condition_node.token + 1, enum_type)) |enum_field_index| { for (existing_enums.items) |existing| { - if (enum_name_hash == existing) { + if (enum_field_index.eq(existing)) { // Duplicate case unreachable; } } - try existing_enums.append(analyzer.allocator, enum_name_hash); - switch_case_group.appendAssumeCapacity(enum_name_hash); + try existing_enums.append(analyzer.allocator, enum_field_index); + switch_case_group.appendAssumeCapacity(enum_field_index); } else { unreachable; } @@ -496,27 +760,24 @@ const Analyzer = struct { switch_case_groups.appendAssumeCapacity(switch_case_group); }, true => { - unreachable; - // if (existing_enums.items.len == enum_type.fields.items.len) { - // unreachable; - // } - // - // else_case_index = index; + else_switch_case_group = true; }, } } const group_index = for (switch_case_groups.items, 0..) |switch_case_group, switch_case_group_index| { - break for (switch_case_group.items) |case_name| { - if (e_field.name == case_name) { + break for (switch_case_group.items) |enum_field_index| { + if (e_field_index.eq(enum_field_index)) { break switch_case_group_index; } - } else continue; + } else { + continue; + }; } else { unreachable; }; - logln(.sema, .@"switch", "Index: {}\n", .{group_index}); + logln(.sema, .@"switch", "Index: {}", .{group_index}); const true_switch_case_node = analyzer.getScopeNode(scope_index, switch_case_node_list[group_index]); var result = Value{ @@ -535,50 +796,150 @@ const Analyzer = struct { unreachable; } - fn processIfElse(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { + const If = struct { + maybe_payload_declaration_value: ?Value.Index, + expression: Value.Index, + taken_expression: Value.Index, + reaches_end: bool, + }; + + const Payload = struct { + name: []const u8, + mutability: Compilation.Mutability, + type: Type.Index, + value: Value.Index, + }; + + fn processIf(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, if_node_index: Node.Index, payload_node_index: Node.Index) !If { + const if_branch_node = analyzer.getScopeNode(scope_index, if_node_index); + // analyzer.debugNode(scope_index, if_node_index); + assert(if_branch_node.id == .@"if"); + var if_expression = try analyzer.unresolvedAllocate(scope_index, ExpectType{ + .type_index = switch (payload_node_index.invalid) { + true => Type.boolean, + false => optional_any, + }, + }, if_branch_node.left); + + const maybe_payload_declaration_value_index: ?Value.Index = if (!payload_node_index.invalid) blk: { + const if_type_index = if_expression.ptr.getType(analyzer.module); + logln(.sema, .fn_return_type, "If condition expression has type #{}", .{if_type_index.uniqueInteger()}); + const if_type = analyzer.module.types.get(if_type_index); + assert(if_type.* == .optional); + const payload_type_index = if_type.optional.element_type; + const payload_node = analyzer.getScopeNode(scope_index, payload_node_index); + const payload_name = analyzer.tokenIdentifier(scope_index, payload_node.token); + + switch (analyzer.module.types.get(if_expression.ptr.getType(analyzer.module)).*) { + .optional => {}, + else => |t| @panic(@tagName(t)), + } + + const is_discard = equal(u8, payload_name, "_"); + const result: ?Value.Index = if (is_discard) null else b: { + // TODO: + const maybe_payload_declaration_index = try analyzer.declarationCommon(scope_index, .local, .@"const", try std.fmt.allocPrint(analyzer.allocator, "maybe_{}_{s}", .{ maybe: { + const r = analyzer.maybe_count; + analyzer.maybe_count += 1; + break :maybe r; + }, payload_name }), if_type_index, if_expression.index, null); + const maybe_payload_declaration_value = try analyzer.module.values.append(analyzer.allocator, .{ + .declaration = maybe_payload_declaration_index, + }); + + if_expression = try analyzer.module.values.append(analyzer.allocator, .{ + .declaration_reference = .{ + .value = maybe_payload_declaration_index, + .type = if_type_index, + }, + }); + + break :b maybe_payload_declaration_value.index; + }; + + const if_expression_before_optional_check = if_expression.index; + + const optional_check = try analyzer.module.optional_checks.append(analyzer.allocator, .{ + .value = if_expression.index, + }); + if_expression = try analyzer.module.values.append(analyzer.allocator, .{ + .optional_check = optional_check.index, + }); + + if (!is_discard) { + const optional_unwrap = try analyzer.module.optional_unwraps.append(analyzer.allocator, .{ + .value = if_expression_before_optional_check, + }); + const payload_value = try analyzer.module.values.append(analyzer.allocator, .{ + .optional_unwrap = optional_unwrap.index, + }); + + try analyzer.payloads.append(analyzer.allocator, Payload{ + .name = payload_name, + .mutability = .@"const", + .type = payload_type_index, + .value = payload_value.index, + }); + } + + break :blk result; + } else null; + + const taken_expression = try analyzer.unresolvedAllocate(scope_index, expect_type, if_branch_node.right); + + const true_reaches_end = switch (taken_expression.ptr.*) { + .block => |block_index| analyzer.module.blocks.get(block_index).reaches_end, + else => |t| @panic(@tagName(t)), + }; + + const if_result = If{ + .maybe_payload_declaration_value = maybe_payload_declaration_value_index, + .expression = if_expression.index, + .taken_expression = taken_expression.index, + .reaches_end = true_reaches_end, + }; + return if_result; + } + + const IfElseResult = struct { + maybe_payload_declaration_index: ?Value.Index, + branch: Compilation.Branch.Index, + }; + + fn processIfElse(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index, payload_node_index: Node.Index) !IfElseResult { const node = analyzer.getScopeNode(scope_index, node_index); assert(node.id == .if_else); assert(!node.left.invalid); assert(!node.right.invalid); - const if_branch_node = analyzer.getScopeNode(scope_index, node.left); - const if_condition = try analyzer.unresolvedAllocate(scope_index, ExpectType.boolean, if_branch_node.left); - switch (if_condition.ptr.*) { - .declaration_reference => { - const true_expression = try analyzer.unresolvedAllocate(scope_index, expect_type, if_branch_node.right); - const true_reaches_end = switch (true_expression.ptr.*) { - .block => |block_index| analyzer.module.blocks.get(block_index).reaches_end, - else => |t| @panic(@tagName(t)), - }; - const false_expression = try analyzer.unresolvedAllocate(scope_index, expect_type, node.right); - const false_reaches_end = switch (true_expression.ptr.*) { - .block => |block_index| analyzer.module.blocks.get(block_index).reaches_end, - else => |t| @panic(@tagName(t)), - }; - const reaches_end = true_reaches_end and false_reaches_end; - - const branch = try analyzer.module.branches.append(analyzer.allocator, .{ - .condition = if_condition.index, - .true_expression = true_expression.index, - .false_expression = false_expression.index, - .reaches_end = reaches_end, - }); - - return Value{ - .branch = branch.index, - }; - }, - .bool => unreachable, + const if_result = try analyzer.processIf(scope_index, expect_type, node.left, payload_node_index); + const not_taken_expression = try analyzer.unresolvedAllocate(scope_index, expect_type, node.right); + const false_reaches_end = switch (not_taken_expression.ptr.*) { + .block => |block_index| analyzer.module.blocks.get(block_index).reaches_end, else => |t| @panic(@tagName(t)), - } + }; + const reaches_end = if_result.reaches_end and false_reaches_end; + + const branch = try analyzer.module.branches.append(analyzer.allocator, .{ + .expression = if_result.expression, + .taken_expression = if_result.taken_expression, + .not_taken_expression = not_taken_expression.index, + .reaches_end = reaches_end, + }); + + return IfElseResult{ + .maybe_payload_declaration_index = if_result.maybe_payload_declaration_value, + .branch = branch.index, + }; } fn processAssignment(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Value { const node = analyzer.getScopeNode(scope_index, node_index); assert(node.id == .assign); - switch (node.left.invalid) { - // In an assignment, the node being invalid means a discarding underscore, like this: ```_ = result``` - true => { + assert(!node.left.invalid); + const left_node = analyzer.getScopeNode(scope_index, node.left); + switch (left_node.id) { + .discard => { var result = Value{ .unresolved = .{ .node_index = node.right, @@ -589,9 +950,9 @@ const Analyzer = struct { return result; }, - false => { + else => { // const id = analyzer.tokenIdentifier(.token); - // logln("id: {s}\n", .{id}); + // logln("id: {s}", .{id}); const left = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); const right = try analyzer.unresolvedAllocate(scope_index, ExpectType{ .type_index = left.ptr.getType(analyzer.module), @@ -644,18 +1005,27 @@ const Analyzer = struct { const binary_operation_id: Compilation.BinaryOperation.Id = switch (node.id) { .add => .add, .sub => .sub, - .logical_and => .logical_and, - .logical_xor => .logical_xor, - .logical_or => .logical_or, + .bit_and => .bit_and, + .bit_xor => .bit_xor, + .bit_or => .bit_or, .multiply => .multiply, .divide => .divide, .shift_left => .shift_left, .shift_right => .shift_right, .compare_equal => .compare_equal, + .compare_greater_than => .compare_greater_than, + .compare_greater_or_equal => .compare_greater_or_equal, + .compare_less_than => .compare_less_than, + .compare_less_or_equal => .compare_less_or_equal, else => |t| @panic(@tagName(t)), }; const left_expect_type: ExpectType = switch (binary_operation_id) { - .compare_equal => ExpectType.none, + .compare_equal, + .compare_less_or_equal, + .compare_less_than, + .compare_greater_than, + .compare_greater_or_equal, + => ExpectType.none, else => expect_type, }; @@ -663,9 +1033,9 @@ const Analyzer = struct { const right_expect_type: ExpectType = switch (binary_operation_id) { .add, .sub, - .logical_and, - .logical_xor, - .logical_or, + .bit_and, + .bit_xor, + .bit_or, .multiply, .divide, => expect_type, @@ -674,7 +1044,12 @@ const Analyzer = struct { => ExpectType{ .type_index = Type.u8, }, - .compare_equal => ExpectType{ + .compare_equal, + .compare_less_than, + .compare_greater_or_equal, + .compare_greater_than, + .compare_less_or_equal, + => ExpectType{ .type_index = left_allocation.ptr.getType(analyzer.module), }, }; @@ -688,7 +1063,7 @@ const Analyzer = struct { .right = right_allocation.index, .type = switch (expect_type) { .none => switch (binary_operation_id) { - .logical_and => left_type, + .bit_and => left_type, else => |t| @panic(@tagName(t)), }, .type_index => |type_index| type_index, @@ -707,11 +1082,31 @@ const Analyzer = struct { scope: Scope.Index, }; + fn getScopeSlice(analyzer: *Analyzer, scope_index: Scope.Index) []const u8 { + const scope = analyzer.module.scopes.get(scope_index); + + return analyzer.getScopeSliceCustomToken(scope_index, scope.token); + } + + fn getScopeSliceCustomToken(analyzer: *Analyzer, scope_index: Scope.Index, custom_token_index: Token.Index) []const u8 { + const scope = analyzer.module.scopes.get(scope_index); + const token = analyzer.getFileToken(scope.file, custom_token_index); + const scope_slice = analyzer.module.files.get(scope.file).source_code[token.start..]; + return scope_slice; + } + fn lookupDeclarationInCurrentAndParentScopes(analyzer: *Analyzer, scope_index: Scope.Index, identifier_hash: u32) ?DeclarationLookup { var scope_iterator = scope_index; while (!scope_iterator.invalid) { const scope = analyzer.module.scopes.get(scope_iterator); + if (Logger.bitset.contains(.scope_lookup)) { + const scope_slice = analyzer.getScopeSlice(scope_iterator); + logln(.sema, .scope_lookup, "Searching for identifier 0x{x} in scope #{}:\n```{s}\n```", .{ identifier_hash, scope_iterator.uniqueInteger(), scope_slice }); + } + if (scope.declarations.get(identifier_hash)) |declaration_index| { + logln(.sema, .scope_lookup, "Identifier 0x{x} found in scope #{}", .{ identifier_hash, scope_iterator.uniqueInteger() }); + return .{ .declaration = declaration_index, .scope = scope_iterator, @@ -724,9 +1119,8 @@ const Analyzer = struct { return null; } - fn doIdentifier(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_token: Token.Index, node_scope_index: Scope.Index) !Value.Index { - const identifier = analyzer.tokenIdentifier(node_scope_index, node_token); - logln(.sema, .identifier, "Referencing identifier: \"{s}\"\n", .{identifier}); + fn doIdentifierString(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, identifier: []const u8) !Value.Index { + logln(.sema, .identifier, "Referencing identifier: \"{s}\"", .{identifier}); const identifier_hash = try analyzer.processIdentifier(identifier); if (analyzer.lookupDeclarationInCurrentAndParentScopes(scope_index, identifier_hash)) |lookup| { @@ -737,26 +1131,46 @@ const Analyzer = struct { const typecheck_result = switch (declaration.init_value.invalid) { false => blk: { const init_value = analyzer.module.values.get(declaration.init_value); - logln(.sema, .identifier, "Declaration found: {}\n", .{init_value}); - const is_unresolved = init_value.* == .unresolved; - switch (is_unresolved) { - true => { + // logln(.sema, .identifier, "Declaration found: {}", .{init_value}); + switch (init_value.*) { + .unresolved => { + const previous_declaration = analyzer.current_declaration; + analyzer.current_declaration = declaration_index; try analyzer.resolveNode(init_value, lookup.scope, expect_type, init_value.unresolved.node_index); - declaration.type = init_value.getType(analyzer.module); + analyzer.current_declaration = previous_declaration; + switch (init_value.*) { .function_definition => |function_index| { - try analyzer.module.function_name_map.put(analyzer.allocator, function_index, declaration.name); + const function_definition = analyzer.module.function_definitions.get(function_index); + const function_prototype = analyzer.module.types.get(function_definition.prototype); + const return_type_index = analyzer.functionPrototypeReturnType(function_prototype.function); + logln(.sema, .fn_return_type, "Function {s} has return type #{}", .{ analyzer.module.getName(declaration.name).?, return_type_index.uniqueInteger() }); + try analyzer.module.function_map.put(analyzer.allocator, function_index, declaration_index); + }, + .type => |type_index| { + try analyzer.module.type_map.put(analyzer.allocator, type_index, declaration_index); }, else => {}, } }, - false => {}, + else => {}, } - logln(.sema, .identifier, "Declaration resolved as: {}\n", .{init_value}); - logln(.sema, .identifier, "Declaration mutability: {s}. Is comptime: {}\n", .{ @tagName(declaration.mutability), init_value.isComptime(analyzer.module) }); + if (declaration.type.invalid) { + declaration.type = init_value.getType(analyzer.module); + } + // logln(.sema, .identifier, "Declaration resolved as: {}", .{init_value}); + // logln(.sema, .identifier, "Declaration mutability: {s}. Is comptime: {}", .{ @tagName(declaration.mutability), init_value.isComptime(analyzer.module) }); + + assert(!declaration.type.invalid); + logln(.sema, .identifier, "About to typecheck identifier: \"{s}\"", .{identifier}); const typecheck_result = try analyzer.typeCheck(expect_type, declaration.type); + logln(.sema, .identifier, "Done typecheck identifier: \"{s}\"", .{identifier}); + + assert(!declaration.type.eq(pointer_to_any_type)); + assert(!declaration.type.eq(optional_pointer_to_any_type)); + assert(!declaration.type.eq(optional_any)); if (init_value.isComptime(analyzer.module) and declaration.mutability == .@"const") { assert(!declaration.init_value.invalid); @@ -787,7 +1201,9 @@ const Analyzer = struct { }); return switch (typecheck_result) { - .success => ref_allocation.index, + .success, + .take_source, + => ref_allocation.index, inline .zero_extend, .sign_extend => |extend| blk: { const cast_allocation = try analyzer.module.casts.append(analyzer.allocator, .{ .value = ref_allocation.index, @@ -811,36 +1227,19 @@ const Analyzer = struct { }, }; } else { - const scope = analyzer.module.scopes.get(scope_index); - panic("Identifier \"{s}\" not found in scope #{} of file #{} referenced by scope #{} of file #{}: {s}", .{ identifier, scope_index.uniqueInteger(), scope.file.uniqueInteger(), node_scope_index.uniqueInteger(), analyzer.module.scopes.get(node_scope_index).file.uniqueInteger(), tokenBytes(analyzer.getScopeToken(scope_index, node_token), analyzer.getScopeSourceFile(scope_index)) }); + // const scope = analyzer.module.scopes.get(scope_index); + panic("Identifier \"{s}\" not found in scope", .{identifier}); } } - fn getArguments(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !ArrayList(Node.Index) { - var arguments = ArrayList(Node.Index){}; - const node = analyzer.getScopeNode(scope_index, node_index); - switch (node.id) { - .compiler_intrinsic_two => { - try arguments.append(analyzer.allocator, node.left); - try arguments.append(analyzer.allocator, node.right); - }, - .compiler_intrinsic => { - const argument_list_node_index = node.left; - assert(!argument_list_node_index.invalid); - const node_list_node = analyzer.getScopeNode(scope_index, argument_list_node_index); - const node_list = analyzer.getScopeNodeList(scope_index, node_list_node); - - return node_list; - }, - else => |t| @panic(@tagName(t)), - } - - return arguments; + fn doIdentifier(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_token: Token.Index, node_scope_index: Scope.Index) !Value.Index { + const identifier = analyzer.tokenIdentifier(node_scope_index, node_token); + return try analyzer.doIdentifierString(scope_index, expect_type, identifier); } fn resolveNode(analyzer: *Analyzer, value: *Value, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) anyerror!void { const node = analyzer.getScopeNode(scope_index, node_index); - logln(.sema, .node, "Resolving node #{} in scope #{} from file #{}: {}\n", .{ node_index.uniqueInteger(), scope_index.uniqueInteger(), analyzer.module.scopes.get(scope_index).file.uniqueInteger(), node }); + // logln(.sema, .node, "Resolving node #{} in scope #{} from file #{}: {}", .{ node_index.uniqueInteger(), scope_index.uniqueInteger(), analyzer.module.scopes.get(scope_index).file.uniqueInteger(), node }); assert(value.* == .unresolved); @@ -869,132 +1268,23 @@ const Analyzer = struct { }, }; }, - .compiler_intrinsic_one, .compiler_intrinsic_two, .compiler_intrinsic => blk: { - const intrinsic_name = analyzer.tokenIdentifier(scope_index, node.token + 1); - logln(.sema, .node, "Intrinsic: {s}\n", .{intrinsic_name}); - const intrinsic = data_structures.enumFromString(Intrinsic, intrinsic_name) orelse panic("Unknown intrinsic: {s}\n", .{intrinsic_name}); - switch (intrinsic) { - .import => { - assert(node.id == .compiler_intrinsic_one); - const import_argument = analyzer.getScopeNode(scope_index, node.left); - switch (import_argument.id) { - .string_literal => { - const import_name = analyzer.tokenStringLiteral(scope_index, import_argument.token); - const import_file = try analyzer.module.importFile(analyzer.allocator, analyzer.current_file, import_name); - logln(.sema, .node, "Importing \"{s}\"...\n", .{import_name}); - - const result = .{ - .type = switch (import_file.file.is_new) { - true => true_block: { - const new_file_index = import_file.file.index; - try analyzer.module.generateAbstractSyntaxTreeForFile(analyzer.allocator, new_file_index); - const analyze_result = try analyzeFile(value, analyzer.allocator, analyzer.module, new_file_index); - logln(.sema, .node, "Done analyzing {s}!\n", .{import_name}); - break :true_block analyze_result; - }, - false => false_block: { - const file_type = import_file.file.ptr.type; - assert(!file_type.invalid); - break :false_block file_type; - }, - }, - }; - - break :blk result; - }, - else => unreachable, - } - }, - .syscall => { - var argument_nodes = try analyzer.getArguments(scope_index, node_index); - logln(.sema, .node, "Argument count: {}\n", .{argument_nodes.items.len}); - if (argument_nodes.items.len > 0 and argument_nodes.items.len <= 6 + 1) { - const argument_expect_type = .{ - .flexible_integer = .{ - .byte_count = 8, - }, - }; - const number_allocation = try analyzer.unresolvedAllocate(scope_index, argument_expect_type, argument_nodes.items[0]); - const number = number_allocation.index; - assert(!number.invalid); - var arguments = std.mem.zeroes([6]Value.Index); - for (argument_nodes.items[1..], 0..) |argument_node_index, argument_index| { - const argument_allocation = try analyzer.unresolvedAllocate(scope_index, argument_expect_type, argument_node_index); - arguments[argument_index] = argument_allocation.index; - } - - // TODO: typecheck for usize - for (arguments[0..argument_nodes.items.len]) |argument| { - _ = argument; - } - - break :blk .{ - .syscall = (try analyzer.module.syscalls.append(analyzer.allocator, .{ - .number = number, - .arguments = arguments, - .argument_count = @intCast(argument_nodes.items.len - 1), - })).index, - }; - } else { - unreachable; - } - }, - .@"error" => { - assert(node.id == .compiler_intrinsic_one); - const message_node = analyzer.getScopeNode(scope_index, node.left); - switch (message_node.id) { - .string_literal => panic("error: {s}", .{analyzer.tokenStringLiteral(scope_index, message_node.token)}), - else => |t| @panic(@tagName(t)), - } - unreachable; - }, - .cast => { - assert(node.id == .compiler_intrinsic_one); - const value_to_cast = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); - const value_type = value_to_cast.ptr.getType(analyzer.module); - assert(expect_type != .none); - const cast_result = try analyzer.canCast(expect_type, value_type); - if (cast_result == .success) { - const cast = try analyzer.module.casts.append(analyzer.allocator, .{ - .value = value_to_cast.index, - .type = switch (expect_type) { - .none => unreachable, - .flexible_integer => |flexible_integer| if (flexible_integer.sign) |sign| switch (sign) { - else => unreachable, - } else switch (flexible_integer.byte_count) { - 1 => Type.u8, - 2 => Type.u16, - 4 => Type.u32, - 8 => Type.u64, - else => unreachable, - }, - else => unreachable, - }, - }); - - break :blk .{ - .cast = cast.index, - }; - } else { - std.debug.panic("Can't cast", .{}); - } - }, - } - unreachable; - }, + .compiler_intrinsic => try analyzer.compilerIntrinsic(scope_index, expect_type, node_index), .function_definition => blk: { const function_scope_allocation = try analyzer.allocateScope(.{ .parent = scope_index, .file = analyzer.module.scopes.get(scope_index).file, + .token = node.token, }); const function_prototype_index = try analyzer.functionPrototype(function_scope_allocation.index, node.left); const function_prototype = analyzer.module.function_prototypes.get(function_prototype_index); assert(!function_prototype.attributes.@"extern"); - const function_body = try analyzer.block(function_scope_allocation.index, .{ + const expected_type = ExpectType{ .type_index = analyzer.functionPrototypeReturnType(function_prototype_index), - }, node.right); + }; + logln(.sema, .fn_return_type, "Return type: #{}", .{expected_type.type_index.uniqueInteger()}); + const function_body = try analyzer.block(function_scope_allocation.index, expected_type, node.right); const prototype_type = try analyzer.module.types.append(analyzer.allocator, .{ .function = function_prototype_index, @@ -1032,7 +1322,7 @@ const Analyzer = struct { }; }, .simple_while => unreachable, - .block_zero, .block_one => blk: { + .block => blk: { const block_index = try analyzer.block(scope_index, expect_type, node_index); break :blk .{ .block = block_index, @@ -1044,34 +1334,35 @@ const Analyzer = struct { .value = integer, .type = switch (expect_type) { .none => Type.comptime_int, - .flexible_integer, .type_index => Type.Integer.getIndex(switch (expect_type) { - .flexible_integer => |flexible_integer_type| Compilation.Type.Integer{ + .flexible_integer, .type_index => switch (expect_type) { + .flexible_integer => |flexible_integer_type| Type.Integer.getIndex(Compilation.Type.Integer{ .bit_count = flexible_integer_type.byte_count << 3, .signedness = .unsigned, - }, + }), .type_index => |type_index| a: { const type_info = analyzer.module.types.get(type_index); break :a switch (type_info.*) { - .integer => |int| int, + .integer => type_index, else => |t| @panic(@tagName(t)), }; }, else => unreachable, - }), + }, }, .signedness = .unsigned, }, }, else => |t| @panic(@tagName(t)), }, - .call, .call_one, .call_two => .{ + .call => .{ .call = try analyzer.processCall(scope_index, node_index), }, .field_access => blk: { - logln(.sema, .node, "left alocation...\n", .{}); + logln(.sema, .node, "Field access", .{}); const identifier = analyzer.tokenIdentifier(scope_index, node.right.value); - logln(.sema, .node, "Field access identifier for RHS: \"{s}\"\n", .{identifier}); - analyzer.debugNode(scope_index, node_index); + const identifier_hash = try analyzer.processIdentifier(identifier); + logln(.sema, .node, "Field access identifier for RHS: \"{s}\"", .{identifier}); + // analyzer.debugNode(scope_index, node_index); const left_allocation = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); switch (left_allocation.ptr.*) { @@ -1083,25 +1374,20 @@ const Analyzer = struct { const struct_type = analyzer.module.structs.get(struct_index); const right_index = try analyzer.doIdentifier(struct_type.scope, ExpectType.none, node.right.value, scope_index); const right_value = analyzer.module.values.get(right_index); + switch (right_value.*) { .function_definition, .type, .enum_field => break :blk right_value.*, - .declaration_reference => |declaration_reference| { - const declaration = analyzer.module.declarations.get(declaration_reference.value); - const declaration_name = analyzer.module.getName(declaration.name).?; - logln(.sema, .node, "Decl ref: {s}\n", .{declaration_name}); - logln(.sema, .node, "TODO: maybe this should not be runtime", .{}); - unreachable; - }, + .declaration_reference => break :blk right_value.*, else => |t| @panic(@tagName(t)), } - logln(.sema, .node, "Right: {}\n", .{right_value}); + + logln(.sema, .node, "Right: {}", .{right_value}); // struct_scope.declarations.get(identifier); unreachable; }, .@"enum" => |enum_index| { const enum_type = analyzer.module.enums.get(enum_index); - const identifier_hash = try analyzer.processIdentifier(identifier); const result = for (enum_type.fields.items) |enum_field_index| { const enum_field = analyzer.module.enum_fields.get(enum_field_index); @@ -1113,7 +1399,7 @@ const Analyzer = struct { }; const enum_field = analyzer.module.enum_fields.get(result); const enum_field_name = analyzer.module.getName(enum_field.name).?; - logln(.sema, .node, "Enum field name resolution: {s}\n", .{enum_field_name}); + logln(.sema, .node, "Enum field name resolution: {s}", .{enum_field_name}); break :blk .{ .enum_field = result, }; @@ -1125,25 +1411,135 @@ const Analyzer = struct { panic("Identifier \"{s}\" not found. Type empty", .{identifier}); } }, + .enum_field => |enum_field_index| { + const enum_field = analyzer.module.enum_fields.get(enum_field_index); + const enum_field_name = analyzer.module.getName(enum_field.name).?; + std.debug.panic("LEFT: enum {s}. RIGHT: {s}", .{ enum_field_name, identifier }); + }, .declaration_reference => |declaration_reference| { - switch (left_allocation.ptr.*) { - .declaration_reference => |reference| { - const declaration = analyzer.module.declarations.get(reference.value); - const declaration_type_index = declaration.type; - const declaration_type = analyzer.module.types.get(declaration_type_index); - switch (declaration_type.*) { - .slice => unreachable, + const declaration_type = analyzer.module.types.get(declaration_reference.type); + + switch (declaration_type.*) { + .@"struct" => |struct_index| { + const struct_type = analyzer.module.structs.get(struct_index); + for (struct_type.fields.items) |struct_field_index| { + const struct_field = analyzer.module.container_fields.get(struct_field_index); + if (struct_field.name == identifier_hash) { + const field_access = try analyzer.module.field_accesses.append(analyzer.allocator, .{ + .declaration_reference = left_allocation.index, + .field = struct_field_index, + }); + break :blk .{ + .field_access = field_access.index, + }; + } + } else { + const right_node = analyzer.getScopeNode(scope_index, node.right); + _ = right_node; + const declaration_value = try analyzer.doIdentifier(struct_type.scope, ExpectType.none, node.right.value, scope_index); + const value_ref = analyzer.module.values.get(declaration_value); + break :blk value_ref.*; + } + }, + .pointer => |pointer| { + const pointer_element_type = analyzer.module.types.get(pointer.element_type); + switch (pointer_element_type.*) { + .@"struct" => |struct_index| { + const struct_type = analyzer.module.structs.get(struct_index); + for (struct_type.fields.items) |struct_field_index| { + const struct_field = analyzer.module.container_fields.get(struct_field_index); + if (struct_field.name == identifier_hash) { + const field_access = try analyzer.module.field_accesses.append(analyzer.allocator, .{ + .declaration_reference = left_allocation.index, + .field = struct_field_index, + }); + break :blk .{ + .field_access = field_access.index, + }; + } + } else { + const declaration_value = try analyzer.doIdentifier(struct_type.scope, ExpectType.none, node.right.value, scope_index); + _ = declaration_value; + + unreachable; + } + }, else => |t| @panic(@tagName(t)), } + + unreachable; + }, + .slice => { + const slice_field = inline for (@typeInfo(Slice.Field).Enum.fields) |slice_field| { + if (equal(u8, slice_field.name, identifier)) { + break @field(Slice.Field, slice_field.name); + } + } else unreachable; + const slice_access_type = switch (slice_field) { + .ptr => t: { + const slice_type_index = left_allocation.ptr.getType(analyzer.module); + const slice_type = analyzer.module.types.get(slice_type_index); + const slice_type_slice = slice_type.slice; + const pointer_type = try analyzer.getPointerType(.{ + .element_type = slice_type_slice.element_type, + .@"const" = slice_type_slice.@"const", + .many = true, + }); + break :t pointer_type; + }, + .len => Type.usize, + }; + const field_access = try analyzer.module.slice_accesses.append(analyzer.allocator, .{ + .value = left_allocation.index, + .field = slice_field, + .type = slice_access_type, + }); + break :blk .{ + .slice_access = field_access.index, + }; + }, + else => |t| @panic(@tagName(t)), + } + + unreachable; + }, + .field_access => |field_access| { + const left_field_access = analyzer.module.field_accesses.get(field_access); + const left_field = analyzer.module.container_fields.get(left_field_access.field); + const left_field_type = analyzer.module.types.get(left_field.type); + + switch (left_field_type.*) { + .@"struct" => |struct_index| { + const struct_type = analyzer.module.structs.get(struct_index); + + for (struct_type.fields.items) |struct_field_index| { + const struct_field = analyzer.module.container_fields.get(struct_field_index); + if (struct_field.name == identifier_hash) { + const new_field_access = try analyzer.module.field_accesses.append(analyzer.allocator, .{ + .declaration_reference = left_allocation.index, + .field = struct_field_index, + }); + break :blk .{ + .field_access = new_field_access.index, + }; + } + } else { + const scope1 = struct_type.scope; + const scope2 = scope_index; + const declaration_value = try analyzer.doIdentifier(scope1, ExpectType.none, node.right.value, scope2); + + const value_ref = analyzer.module.values.get(declaration_value); + break :blk value_ref.*; + } + + unreachable; }, else => |t| @panic(@tagName(t)), } - _ = declaration_reference; unreachable; }, else => |t| @panic(@tagName(t)), } - unreachable; }, .string_literal => .{ .string_literal = try analyzer.processStringLiteral(scope_index, node_index), @@ -1165,7 +1561,7 @@ const Analyzer = struct { for (field_node_list.items) |field_node_index| { const field_node = analyzer.getScopeNode(scope_index, field_node_index); const identifier = analyzer.tokenIdentifier(scope_index, field_node.token); - logln(.sema, .node, "Enum field: {s}\n", .{identifier}); + logln(.sema, .node, "Enum field: {s}", .{identifier}); assert(field_node.left.invalid); const enum_hash_name = try analyzer.processIdentifier(identifier); @@ -1191,30 +1587,303 @@ const Analyzer = struct { }, .assign => try analyzer.processAssignment(scope_index, node_index), .signed_integer_type, .unsigned_integer_type => .{ - .type = try analyzer.resolveType(scope_index, node_index), + .type = try analyzer.resolveType(.{ + .scope_index = scope_index, + .node_index = node_index, + }), }, .@"return" => try analyzer.processReturn(scope_index, expect_type, node_index), .add, .sub, - .logical_and, - .logical_xor, - .logical_or, + .bit_and, + .bit_xor, + .bit_or, .multiply, .divide, .shift_left, .shift_right, .compare_equal, + .compare_greater_than, + .compare_greater_or_equal, + .compare_less_than, + .compare_less_or_equal, => try analyzer.processBinaryOperation(scope_index, expect_type, node_index), .expression_group => return try analyzer.resolveNode(value, scope_index, expect_type, node.left), //unreachable, + .container_literal => blk: { + const literal_type = try analyzer.resolveType(.{ + .scope_index = scope_index, + .node_index = node.left, + }); + const container_initialization = try analyzer.analyzeContainerLiteral(scope_index, literal_type, node.right); + break :blk .{ + .container_initialization = container_initialization, + }; + }, + .struct_type => blk: { + const left_node = analyzer.getScopeNode(scope_index, node.left); + const nodes = analyzer.getScopeNodeList(scope_index, left_node); + const scope = analyzer.module.scopes.get(scope_index); + const struct_type = try analyzer.structType(value, scope_index, nodes.items, scope.file, node_index); + break :blk .{ + .type = struct_type, + }; + }, + .anonymous_container_literal => blk: { + assert(expect_type == .type_index); + const container_initialization = try analyzer.analyzeContainerLiteral(scope_index, expect_type.type_index, node.left); + break :blk .{ + .container_initialization = container_initialization, + }; + }, + .boolean_not => blk: { + const typecheck_result = try analyzer.typeCheck(expect_type, Type.boolean); + assert(typecheck_result == .success); + const not_value = try analyzer.unresolvedAllocate(scope_index, ExpectType.boolean, node.left); + const unary = try analyzer.module.unary_operations.append(analyzer.allocator, .{ + .id = .boolean_not, + .value = not_value.index, + .type = Type.boolean, + }); + + break :blk .{ + .unary_operation = unary.index, + }; + }, + .null_literal => switch (expect_type) { + .type_index => |type_index| switch (analyzer.module.types.get(type_index).*) { + .optional => |optional| switch (analyzer.module.types.get(optional.element_type).*) { + .pointer => .pointer_null_literal, + else => .optional_null_literal, + }, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + }, + .negation => blk: { + const negation_value = try analyzer.unresolvedAllocate(scope_index, expect_type, node.left); + const unary = try analyzer.module.unary_operations.append(analyzer.allocator, .{ + .id = .negation, + .value = negation_value.index, + .type = negation_value.ptr.getType(analyzer.module), + }); + + break :blk .{ + .unary_operation = unary.index, + }; + }, + .address_of => blk: { + const new_expect_type = switch (expect_type) { + .none => expect_type, + .type_index => |type_index| switch (analyzer.module.types.get(type_index).*) { + .pointer => |pointer| ExpectType{ + .type_index = pointer.element_type, + }, + else => |t| @panic(@tagName(t)), + }, + .flexible_integer => unreachable, + }; + + const appointee_value = try analyzer.unresolvedAllocate(scope_index, new_expect_type, node.left); + const unary = try analyzer.module.unary_operations.append(analyzer.allocator, .{ + .id = .address_of, + .value = appointee_value.index, + .type = switch (expect_type) { + .none => unreachable, + .type_index => |type_index| type_index, + .flexible_integer => unreachable, + }, + }); + + break :blk .{ + .unary_operation = unary.index, + }; + }, + .pointer_dereference => blk: { + const new_expect_type = switch (expect_type) { + .none => expect_type, + .type_index => |type_index| switch (analyzer.module.types.get(type_index).*) { + .pointer => unreachable, + else => ExpectType{ + .type_index = try analyzer.getPointerType(.{ + .element_type = type_index, + .many = false, + .@"const" = false, + }), + }, + }, + .flexible_integer => unreachable, + }; + const pointer_value = try analyzer.unresolvedAllocate(scope_index, new_expect_type, node.left); + const pointer_type = analyzer.module.types.get(pointer_value.ptr.getType(analyzer.module)); + assert(pointer_type.* == .pointer); + const element_type = pointer_type.pointer.element_type; + const unary = try analyzer.module.unary_operations.append(analyzer.allocator, .{ + .id = .pointer_dereference, + .value = pointer_value.index, + .type = element_type, + }); + + break :blk .{ + .unary_operation = unary.index, + }; + }, + .slice => blk: { + const expression_to_slice = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); + const expression_to_slice_type = expression_to_slice.ptr.getType(analyzer.module); + const element_type = switch (analyzer.module.types.get(expression_to_slice_type).*) { + .pointer => |pointer| pointer.element_type, + else => |t| @panic(@tagName(t)), + }; + const slice_range_node = analyzer.getScopeNode(scope_index, node.right); + assert(slice_range_node.id == .slice_range); + const slice_range_start = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, slice_range_node.left); + const slice_range_end = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, slice_range_node.right); + + const slice = try analyzer.module.slices.append(analyzer.allocator, .{ + .sliceable = expression_to_slice.index, + .start = slice_range_start.index, + .end = slice_range_end.index, + .type = try analyzer.getSliceType(.{ + .element_type = element_type, + .@"const" = true, + }), + }); + + break :blk .{ + .slice = slice.index, + }; + }, + .indexed_access => blk: { + const indexable_expression = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node.left); + const indexable_expression_type = indexable_expression.ptr.getType(analyzer.module); + switch (analyzer.module.types.get(indexable_expression_type).*) { + .slice => {}, + else => |t| @panic(@tagName(t)), + } + const index_expression = try analyzer.unresolvedAllocate(scope_index, ExpectType{ + .type_index = Type.usize, + }, node.right); + + const indexed_access = try analyzer.module.indexed_accesses.append(analyzer.allocator, .{ + .indexed_expression = indexable_expression.index, + .index_expression = index_expression.index, + }); + + break :blk .{ + .indexed_access = indexed_access.index, + }; + }, + .enum_literal => blk: { + const enum_literal_identifier_token = node.token + 1; + switch (expect_type) { + .type_index => |type_index| { + switch (analyzer.module.types.get(type_index).*) { + .@"enum" => |enum_index| { + const enum_type = analyzer.module.enums.get(enum_index); + const enum_field_index = try analyzer.typeCheckEnumLiteral(scope_index, enum_literal_identifier_token, enum_type) orelse unreachable; + + break :blk .{ + .enum_field = enum_field_index, + }; + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + } + }, else => |t| @panic(@tagName(t)), }; } + fn getSliceType(analyzer: *Analyzer, slice: Type.Slice) !Type.Index { + const gop = try analyzer.module.slice_types.getOrPut(analyzer.allocator, slice); + + if (!gop.found_existing) { + const type_allocation = try analyzer.module.types.append(analyzer.allocator, .{ + .slice = slice, + }); + gop.value_ptr.* = type_allocation.index; + } + + const result = gop.value_ptr.*; + return result; + } + + fn analyzeContainerLiteral(analyzer: *Analyzer, scope_index: Scope.Index, expected_type_index: Type.Index, node_list_node_index: Node.Index) !Compilation.ContainerInitialization.Index { + const field_initialization_node_list = analyzer.getScopeNode(scope_index, node_list_node_index); + const field_nodes = analyzer.getScopeNodeList(scope_index, field_initialization_node_list); + assert(!expected_type_index.invalid); + const expected_type = analyzer.module.types.get(expected_type_index); + + switch (expected_type.*) { + .@"struct" => |struct_index| { + const struct_type = analyzer.module.structs.get(struct_index); + var bitset = try std.DynamicBitSetUnmanaged.initEmpty(analyzer.allocator, field_nodes.items.len); + + var list = try ArrayList(Value.Index).initCapacity(analyzer.allocator, struct_type.fields.items.len); + + for (struct_type.fields.items) |struct_field_index| { + const struct_field = analyzer.module.container_fields.get(struct_field_index); + + var value_index = Value.Index.invalid; + + for (field_nodes.items, 0..) |field_node_index, index| { + const field_node = analyzer.getScopeNode(scope_index, field_node_index); + assert(field_node.id == .field_initialization); + const identifier = analyzer.tokenIdentifier(scope_index, field_node.token + 1); + const identifier_index = try analyzer.processIdentifier(identifier); + + if (struct_field.name == identifier_index) { + if (!value_index.invalid) { + @panic("Field initialized twice"); + } + + bitset.set(index); + + value_index = (try analyzer.unresolvedAllocate(scope_index, ExpectType{ + .type_index = struct_field.type, + }, field_node.right)).index; + } + } + + if (value_index.invalid) { + if (!struct_field.default_value.invalid) { + value_index = struct_field.default_value; + } else { + const struct_field_name = analyzer.module.getName(struct_field.name).?; + std.debug.panic("Field \"{s}\" forgotten in struct initialization", .{struct_field_name}); + } + } + + list.appendAssumeCapacity(value_index); + } + + if (bitset.count() != bitset.bit_length) { + @panic("Some field name in struct initialization is wrong"); + } + + const container_initialization = try analyzer.module.container_initializations.append(analyzer.allocator, .{ + .field_initializations = list, + .type = expected_type_index, + }); + return container_initialization.index; + }, + else => |t| @panic(@tagName(t)), + } + + unreachable; + } + fn debugNode(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) void { const node = analyzer.getScopeNode(scope_index, node_index); + analyzer.debugToken(scope_index, node.token); + } + + fn debugToken(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index) void { const source_file = analyzer.getScopeSourceFile(scope_index); - const token = analyzer.getScopeToken(scope_index, node.token); - logln(.sema, .node, "Debugging node {s}:\n\n```\n{s}\n```\n", .{ @tagName(node.id), source_file[token.start..] }); + const token = analyzer.getScopeToken(scope_index, token_index); + logln(.sema, .debug, "Debugging:\n\n```\n{s}\n```", .{source_file[token.start..]}); } fn processStringLiteral(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !u32 { @@ -1262,7 +1931,14 @@ const Analyzer = struct { return function_prototype.return_type; } - fn resolveType(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index) !Type.Index { + fn resolveType(analyzer: *Analyzer, args: struct { + scope_index: Scope.Index, + node_index: Node.Index, + allow_non_primitive_size: bool = false, + }) anyerror!Type.Index { + const scope_index = args.scope_index; + const node_index = args.node_index; + const type_node = analyzer.getScopeNode(scope_index, node_index); const type_index: Type.Index = switch (type_node.id) { .identifier => blk: { @@ -1290,35 +1966,113 @@ const Analyzer = struct { else => @compileError("OOO"), }, }), - else => unreachable, + else => switch (args.allow_non_primitive_size) { + true => b: { + const integer = .{ + .bit_count = bit_count, + .signedness = switch (int_type_signedness) { + .signed_integer_type => .signed, + .unsigned_integer_type => .unsigned, + else => @compileError("OOO"), + }, + }; + const gop = try analyzer.module.non_primitive_integer_types.getOrPut(analyzer.allocator, integer); + + if (!gop.found_existing) { + const new_type = try analyzer.module.types.append(analyzer.allocator, .{ + .integer = integer, + }); + + gop.value_ptr.* = new_type.index; + } + + break :b gop.value_ptr.*; + }, + false => @panic("non primitive size not allowed"), + }, }; }, - .many_pointer_type => blk: { - const type_allocation = try analyzer.module.types.append(analyzer.allocator, .{ - .pointer = .{ - .element_type = try resolveType(analyzer, scope_index, type_node.left), - .many = true, - .@"const" = switch (analyzer.getScopeToken(scope_index, type_node.token + 3).id) { - .fixed_keyword_const => true, - .fixed_keyword_var => false, - else => |t| @panic(@tagName(t)), - }, - }, + .const_single_pointer_type, .single_pointer_type, .const_many_pointer_type, .many_pointer_type => blk: { + const element_type = try resolveType(analyzer, .{ + .scope_index = scope_index, + .node_index = type_node.left, + }); + const many = switch (type_node.id) { + .const_many_pointer_type, + .many_pointer_type, + => true, + .const_single_pointer_type, + .single_pointer_type, + => false, + else => |t| @panic(@tagName(t)), + }; + const is_const = switch (type_node.id) { + .const_many_pointer_type, + .const_single_pointer_type, + => true, + .many_pointer_type, + .single_pointer_type, + => false, + else => |t| @panic(@tagName(t)), + }; + + break :blk try analyzer.getPointerType(.{ + .element_type = element_type, + .many = many, + .@"const" = is_const, }); - break :blk type_allocation.index; }, .slice_type => blk: { - const type_allocation = try analyzer.module.types.append(analyzer.allocator, .{ - .slice = .{ - .element_type = try resolveType(analyzer, scope_index, type_node.right), - }, + const element_type = try resolveType(analyzer, .{ + .scope_index = scope_index, + .node_index = type_node.right, }); - break :blk type_allocation.index; + const is_const = false; + + break :blk try analyzer.getSliceType(.{ + .element_type = element_type, + .@"const" = is_const, + }); + }, + .optional_type => blk: { + const element_type = try resolveType(analyzer, .{ + .scope_index = scope_index, + .node_index = type_node.left, + }); + const gop = try analyzer.module.optional_types.getOrPut(analyzer.allocator, element_type); + const result = switch (gop.found_existing) { + true => gop.value_ptr.*, + false => b: { + const type_allocation = try analyzer.module.types.append(analyzer.allocator, .{ + .optional = .{ + .element_type = element_type, + }, + }); + gop.value_ptr.* = type_allocation.index; + + break :b gop.value_ptr.*; + }, + }; + + break :blk result; }, .void_type => Type.void, .ssize_type => Type.ssize, .usize_type => Type.usize, .bool_type => Type.boolean, + .simple_function_prototype => blk: { + const function_prototype = try analyzer.module.function_prototypes.append(analyzer.allocator, try analyzer.processSimpleFunctionPrototype(scope_index, node_index)); + + const function_type = try analyzer.module.types.append(analyzer.allocator, .{ + .function = function_prototype.index, + }); + break :blk function_type.index; + }, + .field_access => blk: { + const type_resolution = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, node_index); + assert(type_resolution.ptr.* == .type); + break :blk type_resolution.ptr.type; + }, else => |t| @panic(@tagName(t)), }; return type_index; @@ -1349,8 +2103,12 @@ const Analyzer = struct { const argument_node = analyzer.getScopeNode(scope_index, argument_node_index); switch (argument_node.id) { .argument_declaration => { - const argument_type = try analyzer.resolveType(scope_index, argument_node.left); - const argument_declaration = try analyzer.declarationCommon(scope_index, .local, .@"const", argument_node.token, argument_type, Value.Index.invalid, @intCast(index)); + const argument_type = try analyzer.resolveType(.{ + .scope_index = scope_index, + .node_index = argument_node.left, + }); + const argument_name = analyzer.tokenIdentifier(scope_index, argument_node.token); + const argument_declaration = try analyzer.declarationCommon(scope_index, .local, .@"const", argument_name, argument_type, Value.Index.invalid, @intCast(index)); arguments.appendAssumeCapacity(argument_declaration); }, @@ -1365,7 +2123,10 @@ const Analyzer = struct { }, }; - const return_type = try analyzer.resolveType(scope_index, return_type_node_index); + const return_type = try analyzer.resolveType(.{ + .scope_index = scope_index, + .node_index = return_type_node_index, + }); return .{ .arguments = arguments, @@ -1385,13 +2146,38 @@ const Analyzer = struct { var function_prototype = try analyzer.processSimpleFunctionPrototype(scope_index, function_prototype_node.left); const function_prototype_attribute_list_node = analyzer.getScopeNode(scope_index, function_prototype_node.right); const attribute_node_list = analyzer.getScopeNodeList(scope_index, function_prototype_attribute_list_node); - const calling_convention: ?Compilation.CallingConvention = null; + var calling_convention: ?Compilation.CallingConvention = null; for (attribute_node_list.items) |attribute_node_index| { const attribute_node = analyzer.getScopeNode(scope_index, attribute_node_index); switch (attribute_node.id) { .extern_qualifier => function_prototype.attributes.@"extern" = true, + .export_qualifier => function_prototype.attributes.@"export" = true, + .calling_convention => { + const calling_convention_type_declaration = try analyzer.forceDeclarationAnalysis(scope_index, "std.builtin.CallingConvention"); + const calling_convention_type = switch (analyzer.module.values.get(calling_convention_type_declaration).*) { + .type => |type_index| type_index, + else => |t| @panic(@tagName(t)), + }; + const cc_value = try analyzer.unresolvedAllocate(scope_index, ExpectType{ + .type_index = calling_convention_type, + }, attribute_node.left); + const enum_field_value = cc_value.ptr; + switch (enum_field_value.*) { + .enum_field => |enum_field_index| { + const enum_field = analyzer.module.enum_fields.get(enum_field_index); + const enum_field_name = analyzer.module.getName(enum_field.name).?; + + calling_convention = inline for (@typeInfo(Compilation.CallingConvention).Enum.fields) |cc_enum_field| { + if (equal(u8, cc_enum_field.name, enum_field_name)) { + break @field(Compilation.CallingConvention, cc_enum_field.name); + } + } else unreachable; + }, + else => |t| @panic(@tagName(t)), + } + }, else => |t| @panic(@tagName(t)), } } @@ -1405,69 +2191,136 @@ const Analyzer = struct { } } - fn structType(analyzer: *Analyzer, value: *Value, parent_scope_index: Scope.Index, index: Node.Index, file_index: File.Index) !Type.Index { - var node_buffer: [2]Node.Index = undefined; - // We have the file because this might be the first file - const file = analyzer.module.files.get(file_index); - const node = file.syntactic_analyzer_result.nodes.items[index.unwrap()]; - const nodes = switch (node.id) { - .main_one => blk: { - node_buffer[0] = node.left; - break :blk node_buffer[0..1]; - }, - .main_two => blk: { - node_buffer[0] = node.left; - node_buffer[1] = node.right; - break :blk &node_buffer; - }, - .main => blk: { - const node_list_node = analyzer.getFileNode(file_index, node.left); - const node_list = switch (node_list_node.id) { - .node_list => analyzer.getFileNodeList(file_index, node_list_node), - else => |t| @panic(@tagName(t)), - }; - break :blk node_list.items; - // const node_list = file.syntactic_analyzer_result.node_lists.items[node.left.unwrap()]; - // break :blk node_list.items; - }, - .main_zero => &.{}, - else => |t| @panic(@tagName(t)), - }; + fn forceDeclarationAnalysis(analyzer: *Analyzer, scope_index: Scope.Index, whole_expression: []const u8) !Value.Index { + var expression_iterator = std.mem.tokenizeScalar(u8, whole_expression, '.'); + var before_expression = Value.Index.invalid; + var last_scope = scope_index; - if (nodes.len > 0) { + while (expression_iterator.next()) |expression_name| { + const result = switch (before_expression.invalid) { + true => try analyzer.doIdentifierString(scope_index, ExpectType.type, expression_name), + false => blk: { + const before_expression_value = analyzer.module.values.get(before_expression); + const expression_name_hash = try analyzer.processIdentifier(expression_name); + switch (before_expression_value.*) { + .type => |type_index| { + const expression_type = analyzer.module.types.get(type_index); + switch (expression_type.*) { + .@"struct" => |struct_index| { + const struct_type = analyzer.module.structs.get(struct_index); + const struct_type_scope = analyzer.module.scopes.get(struct_type.scope); + const declaration_index = struct_type_scope.declarations.get(expression_name_hash).?; + const declaration = analyzer.module.declarations.get(declaration_index); + assert(declaration.name == expression_name_hash); + last_scope = declaration.scope; + + break :blk declaration.init_value; + }, + else => |t| @panic(@tagName(t)), + } + }, + .unresolved => |unresolved| { + try analyzer.resolveNode(before_expression_value, last_scope, ExpectType.none, unresolved.node_index); + switch (before_expression_value.*) { + .type => |type_index| { + const expression_type = analyzer.module.types.get(type_index); + switch (expression_type.*) { + .@"struct" => |struct_index| { + const struct_type = analyzer.module.structs.get(struct_index); + const struct_type_scope = analyzer.module.scopes.get(struct_type.scope); + const declaration_index = struct_type_scope.declarations.get(expression_name_hash).?; + const declaration = analyzer.module.declarations.get(declaration_index); + assert(declaration.name == expression_name_hash); + last_scope = declaration.scope; + + break :blk declaration.init_value; + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + } + }, + }; + + before_expression = result; + } + + const expression_value = analyzer.module.values.get(before_expression); + switch (expression_value.*) { + .unresolved => |unresolved| { + try analyzer.resolveNode(expression_value, last_scope, ExpectType.none, unresolved.node_index); + }, + else => {}, + } + + return before_expression; + } + + fn structType(analyzer: *Analyzer, value: *Value, parent_scope_index: Scope.Index, struct_nodes: []const Node.Index, struct_file_index: File.Index, struct_node_index: Node.Index) !Type.Index { + const struct_node = analyzer.getFileNode(struct_file_index, struct_node_index); + if (struct_nodes.len > 0) { const new_scope = try analyzer.allocateScope(.{ .parent = parent_scope_index, - .file = file_index, + .file = struct_file_index, + .token = struct_node.token, }); - const scope = new_scope.ptr; + const scope_index = new_scope.index; const is_file = parent_scope_index.invalid; - assert(is_file); + const backing_type = blk: { + if (!is_file) { + if (analyzer.getScopeToken(parent_scope_index, struct_node.token + 1).id == .left_parenthesis) { + const backing_type_token = analyzer.getScopeToken(parent_scope_index, struct_node.token + 2); + const source_file = analyzer.getScopeSourceFile(parent_scope_index); + const token_bytes = tokenBytes(backing_type_token, source_file); + + break :blk switch (backing_type_token.id) { + .keyword_unsigned_integer => if (equal(u8, token_bytes, "u8")) Type.u8 else if (equal(u8, token_bytes, "u16")) Type.u16 else if (equal(u8, token_bytes, "u32")) Type.u32 else if (equal(u8, token_bytes, "u64")) Type.u64 else if (equal(u8, token_bytes, "usize")) Type.usize else unreachable, + else => |t| @panic(@tagName(t)), + }; + } + } + + break :blk Type.Index.invalid; + }; const struct_allocation = try analyzer.module.structs.append(analyzer.allocator, .{ .scope = new_scope.index, + .backing_type = backing_type, }); + const type_allocation = try analyzer.module.types.append(analyzer.allocator, .{ .@"struct" = struct_allocation.index, }); - if (parent_scope_index.invalid) { + if (is_file) { + const file = analyzer.module.files.get(struct_file_index); file.type = type_allocation.index; } - scope.type = type_allocation.index; + analyzer.module.scopes.get(scope_index).type = type_allocation.index; value.* = .{ .type = type_allocation.index, }; + if (!analyzer.current_declaration.invalid) { + const current_declaration = analyzer.module.declarations.get(analyzer.current_declaration); + assert(current_declaration.type.invalid); + current_declaration.type = Type.type; + } + const count = blk: { var result: struct { fields: u32 = 0, declarations: u32 = 0, } = .{}; - for (nodes) |member_index| { - const member = analyzer.getFileNode(file_index, member_index); + for (struct_nodes) |member_index| { + const member = analyzer.getFileNode(struct_file_index, member_index); const member_type = getContainerMemberType(member.id); switch (member_type) { @@ -1481,8 +2334,8 @@ const Analyzer = struct { var declaration_nodes = try ArrayList(Node.Index).initCapacity(analyzer.allocator, count.declarations); var field_nodes = try ArrayList(Node.Index).initCapacity(analyzer.allocator, count.fields); - for (nodes) |member_index| { - const member = analyzer.getFileNode(file_index, member_index); + for (struct_nodes) |member_index| { + const member = analyzer.getFileNode(struct_file_index, member_index); const member_type = getContainerMemberType(member.id); const array_list = switch (member_type) { .declaration => &declaration_nodes, @@ -1492,7 +2345,7 @@ const Analyzer = struct { } for (declaration_nodes.items) |declaration_node_index| { - const declaration_node = analyzer.getFileNode(file_index, declaration_node_index); + const declaration_node = analyzer.getFileNode(struct_file_index, declaration_node_index); switch (declaration_node.id) { .@"comptime" => {}, .simple_symbol_declaration => _ = try analyzer.symbolDeclaration(scope_index, declaration_node_index, .global), @@ -1502,7 +2355,7 @@ const Analyzer = struct { // TODO: consider iterating over scope declarations instead? for (declaration_nodes.items) |declaration_node_index| { - const declaration_node = analyzer.getFileNode(file_index, declaration_node_index); + const declaration_node = analyzer.getFileNode(struct_file_index, declaration_node_index); switch (declaration_node.id) { .@"comptime" => _ = try analyzer.comptimeBlock(scope_index, declaration_node_index), .simple_symbol_declaration => {}, @@ -1510,11 +2363,42 @@ const Analyzer = struct { } } - for (field_nodes.items) |field_index| { - const field_node = analyzer.getFileNode(file_index, field_index); - _ = field_node; + struct_allocation.ptr.fields = try ArrayList(Compilation.ContainerField.Index).initCapacity(analyzer.allocator, field_nodes.items.len); - @panic("TODO: fields"); + if (field_nodes.items.len > 0) { + // This is done in order for the names inside fields not to collision with the declaration ones + const field_scope = try analyzer.allocateScope(.{ + .token = analyzer.getScopeNode(scope_index, field_nodes.items[0]).token, + .file = struct_file_index, + .parent = scope_index, + }); + + for (field_nodes.items) |field_index| { + const field_node = analyzer.getFileNode(struct_file_index, field_index); + const identifier = analyzer.tokenIdentifier(field_scope.index, field_node.token); + const identifier_index = try analyzer.processIdentifier(identifier); + const type_index = try analyzer.resolveType(.{ + .scope_index = field_scope.index, + .node_index = field_node.left, + .allow_non_primitive_size = !backing_type.invalid, + }); + + const default_value = if (field_node.right.invalid) Value.Index.invalid else blk: { + const v = try analyzer.unresolvedAllocate(field_scope.index, ExpectType{ + .type_index = type_index, + }, field_node.right); + break :blk v.index; + }; + + const container_field = try analyzer.module.container_fields.append(analyzer.allocator, .{ + .name = identifier_index, + .type = type_index, + .default_value = default_value, + .parent = type_allocation.index, + }); + + struct_allocation.ptr.fields.appendAssumeCapacity(container_field.index); + } } return type_allocation.index; @@ -1523,13 +2407,13 @@ const Analyzer = struct { } } - fn declarationCommon(analyzer: *Analyzer, scope_index: Scope.Index, scope_type: ScopeType, mutability: Compilation.Mutability, identifier_token: Token.Index, type_index: Type.Index, init_value: Value.Index, argument_index: ?u32) !Declaration.Index { - const identifier = analyzer.tokenIdentifier(scope_index, identifier_token); - const identifier_index = try analyzer.processIdentifier(identifier); + fn declarationCommon(analyzer: *Analyzer, scope_index: Scope.Index, scope_type: ScopeType, mutability: Compilation.Mutability, name: []const u8, type_index: Type.Index, init_value: Value.Index, argument_index: ?u32) !Declaration.Index { + const identifier_index = try analyzer.processIdentifier(name); if (analyzer.lookupDeclarationInCurrentAndParentScopes(scope_index, identifier_index)) |lookup| { - const declaration_name = analyzer.tokenIdentifier(lookup.scope, identifier_token); - panic("Existing name in lookup: {s}", .{declaration_name}); + const declaration = analyzer.module.declarations.get(lookup.declaration); + const declaration_name = analyzer.module.getName(declaration.name).?; + panic("Existing name in lookup: {s}.\nSource scope: #{}. Lookup scope: #{}", .{ declaration_name, scope_index.uniqueInteger(), lookup.scope.uniqueInteger() }); } // Check if the symbol name is already occupied in the same scope @@ -1541,9 +2425,10 @@ const Analyzer = struct { .init_value = init_value, .type = type_index, .argument_index = argument_index, + .scope = scope_index, }); - try scope.declarations.put(analyzer.allocator, identifier_index, declaration_allocation.index); + try scope.declarations.putNoClobber(analyzer.allocator, identifier_index, declaration_allocation.index); return declaration_allocation.index; } @@ -1551,21 +2436,27 @@ const Analyzer = struct { fn symbolDeclaration(analyzer: *Analyzer, scope_index: Scope.Index, node_index: Node.Index, scope_type: ScopeType) !Declaration.Index { const declaration_node = analyzer.getScopeNode(scope_index, node_index); assert(declaration_node.id == .simple_symbol_declaration); + const expected_identifier_token_index = declaration_node.token + 1; + const identifier = analyzer.tokenIdentifier(scope_index, expected_identifier_token_index); + const expect_type = switch (declaration_node.left.invalid) { false => switch (scope_type) { .local => ExpectType{ - .type_index = try analyzer.resolveType(scope_index, declaration_node.left), + .type_index = try analyzer.resolveType(.{ + .scope_index = scope_index, + .node_index = declaration_node.left, + }), }, .global => ExpectType.none, }, true => ExpectType.none, }; + const mutability: Compilation.Mutability = switch (analyzer.getScopeToken(scope_index, declaration_node.token).id) { .fixed_keyword_const => .@"const", .fixed_keyword_var => .@"var", else => |t| @panic(@tagName(t)), }; - const expected_identifier_token_index = declaration_node.token + 1; const expected_identifier_token = analyzer.getScopeToken(scope_index, expected_identifier_token_index); if (expected_identifier_token.id != .identifier) { logln(.sema, .symbol_declaration, "Error: found: {}", .{expected_identifier_token.id}); @@ -1577,6 +2468,7 @@ const Analyzer = struct { const argument = null; assert(argument == null); + const init_value_allocation = switch (scope_type) { .local => try analyzer.unresolvedAllocate(scope_index, expect_type, declaration_node.right), .global => try analyzer.module.values.append(analyzer.allocator, .{ @@ -1588,11 +2480,14 @@ const Analyzer = struct { assert(argument == null); const type_index = switch (scope_type) { - .local => init_value_allocation.ptr.getType(analyzer.module), + .local => switch (expect_type) { + .type_index => |type_index| type_index, + else => init_value_allocation.ptr.getType(analyzer.module), + }, .global => Type.Index.invalid, }; - const result = try analyzer.declarationCommon(scope_index, scope_type, mutability, expected_identifier_token_index, type_index, init_value_allocation.index, argument); + const result = try analyzer.declarationCommon(scope_index, scope_type, mutability, identifier, type_index, init_value_allocation.index, argument); return result; } @@ -1606,7 +2501,8 @@ const Analyzer = struct { return switch (member_id) { .@"comptime" => .declaration, .simple_symbol_declaration => .declaration, - else => unreachable, + .container_field => .field, + else => |t| @panic(@tagName(t)), }; } @@ -1616,6 +2512,7 @@ const Analyzer = struct { fn tokenIdentifier(analyzer: *Analyzer, scope_index: Scope.Index, token_index: Token.Index) []const u8 { const token = analyzer.getScopeToken(scope_index, token_index); + // logln(.sema, .identifier, "trying to get identifier from token of id {s}", .{@tagName(token.id)}); assert(token.id == .identifier); const source_file = analyzer.getScopeSourceFile(scope_index); const identifier = tokenBytes(token, source_file); @@ -1654,20 +2551,62 @@ const Analyzer = struct { success, zero_extend, sign_extend, + take_source, }; fn canCast(analyzer: *Analyzer, expect_type: ExpectType, source: Type.Index) !TypeCheckResult { return switch (expect_type) { .none => unreachable, .flexible_integer => |flexible_integer| blk: { - _ = flexible_integer; const source_type = analyzer.module.types.get(source); break :blk switch (source_type.*) { .pointer => .success, + .optional => b: { + const optional_element_type = analyzer.module.types.get(source_type.optional.element_type); + break :b switch (optional_element_type.*) { + .pointer => .success, + else => |t| @panic(@tagName(t)), + }; + }, + .@"struct" => |struct_type_index| { + const struct_type = analyzer.module.structs.get(struct_type_index); + if (!struct_type.backing_type.invalid) { + const backing_integer_type = analyzer.module.types.get(struct_type.backing_type); + if (backing_integer_type.integer.bit_count >> 3 <= flexible_integer.byte_count) { + return .success; + } + } + + unreachable; + }, + else => |t| @panic(@tagName(t)), + }; + }, + .type_index => |type_index| blk: { + if (source.eq(type_index)) { + unreachable; + } + + const destination_type = analyzer.module.types.get(type_index); + const source_type = analyzer.module.types.get(source); + break :blk switch (source_type.*) { + .integer => |integer| switch (destination_type.*) { + .optional => |optional| switch (analyzer.module.types.get(optional.element_type).*) { + .pointer => if (integer.bit_count == 64) .success else unreachable, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + }, + .pointer => switch (destination_type.*) { + .optional => |destination_optional| switch (analyzer.module.types.get(destination_optional.element_type).*) { + .pointer => .success, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + }, else => |t| @panic(@tagName(t)), }; }, - else => |t| @panic(@tagName(t)), }; } @@ -1706,16 +2645,145 @@ const Analyzer = struct { // TODO: type safety .pointer => |destination_pointer| switch (source_type.*) { .pointer => |source_pointer| { - switch (source_pointer.many == destination_pointer.many and source_pointer.element_type.eq(destination_pointer.element_type)) { - true => return TypeCheckResult.success, - false => unreachable, + if (source_pointer.many == destination_pointer.many) { + if (source_pointer.element_type.eq(destination_pointer.element_type)) { + return .success; + } else { + switch (analyzer.module.types.get(source_pointer.element_type).*) { + .array => |array| { + if (array.element_type.eq(destination_pointer.element_type)) { + return .success; + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + } + } + } else { + unreachable; } }, + .@"struct" => |struct_index| { + _ = struct_index; + + @panic("expected pointer, found struct"); + }, else => |t| @panic(@tagName(t)), }, .bool => switch (source_type.*) { else => |t| @panic(@tagName(t)), }, + .optional => |destination_optional| switch (source_type.*) { + .optional => |source_optional| { + if (expected_type_index.eq(optional_any)) { + return .take_source; + } else { + if (destination_optional.element_type.eq(source_optional.element_type)) { + return .success; + } else { + const destination_optional_element_type = analyzer.module.types.get(destination_optional.element_type); + const source_optional_element_type = analyzer.module.types.get(source_optional.element_type); + + switch (destination_optional_element_type.*) { + .pointer => |destination_pointer| switch (source_optional_element_type.*) { + .pointer => |source_pointer| { + if (source.eq(optional_pointer_to_any_type)) { + return .success; + } + + if (expected_type_index.eq(optional_pointer_to_any_type)) { + return .take_source; + } + + if (destination_pointer.many == source_pointer.many) { + if (destination_pointer.@"const" == source_pointer.@"const") { + if (destination_pointer.element_type.eq(source_pointer.element_type)) { + return .success; + } + } + } + + unreachable; + }, + .slice => |source_slice| { + _ = source_slice; + + unreachable; + // _ = source_slice; + // if (source.eq(optional_any) or expected_type_index.eq(optional_pointer_to_any_type)) { + // return .success; + // } + }, + else => |t| @panic(@tagName(t)), + }, + .slice => |destination_slice| switch (source_optional_element_type.*) { + .slice => |source_slice| { + if (destination_slice.element_type.eq(source_slice.element_type)) { + return .success; + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + } + } + } + }, + .pointer => |source_pointer| { + if (destination_optional.element_type.eq(source)) { + return .success; + } else { + const destination_optional_element_type = analyzer.module.types.get(destination_optional.element_type); + + switch (destination_optional_element_type.*) { + .pointer => |destination_pointer| { + if (source.eq(optional_pointer_to_any_type)) { + return .success; + } + + if (expected_type_index.eq(optional_pointer_to_any_type)) { + return .take_source; + } + + if (destination_pointer.many == source_pointer.many) { + if (destination_pointer.@"const" or destination_pointer.@"const" == source_pointer.@"const") { + if (destination_pointer.element_type.eq(source_pointer.element_type)) { + return .success; + } + } + } + + std.debug.panic("Destination: {}. Source: {}", .{ destination_pointer, source_pointer }); + }, + .slice => |destination_slice| { + _ = destination_slice; + + // if (destination_slice.element_type.eq(source_slice.element_type)) { + // return .success; + // } else { + unreachable; + // } + }, + else => |t| @panic(@tagName(t)), + } + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + }, + .function => |destination_function| switch (source_type.*) { + .function => |source_function| { + _ = destination_function; + _ = source_function; + + // TODO: typecheck properly + return .success; + }, + else => |t| @panic(@tagName(t)), + }, else => |t| @panic(@tagName(t)), } }, @@ -1745,38 +2813,150 @@ const Analyzer = struct { }, else => |t| @panic(@tagName(t)), } - // if (expected_flexible_integer.byte_count < - // _ = expected_flexible_integer; }, // else => |t| @panic(@tagName(t)), }; } + + fn getPointerType(analyzer: *Analyzer, pointer: Type.Pointer) !Type.Index { + const gop = try analyzer.module.pointer_types.getOrPut(analyzer.allocator, pointer); + if (!gop.found_existing) { + const type_allocation = try analyzer.module.types.append(analyzer.allocator, .{ + .pointer = .{ + .element_type = pointer.element_type, + .many = pointer.many, + .@"const" = pointer.@"const", + }, + }); + gop.value_ptr.* = type_allocation.index; + } + + const result = gop.value_ptr.*; + return result; + } + + fn compilerIntrinsic(analyzer: *Analyzer, scope_index: Scope.Index, expect_type: ExpectType, node_index: Node.Index) !Value { + const intrinsic_node = analyzer.getScopeNode(scope_index, node_index); + + const intrinsic_name = analyzer.tokenIdentifier(scope_index, intrinsic_node.token + 1); + logln(.sema, .node, "Intrinsic: {s}", .{intrinsic_name}); + const intrinsic = data_structures.enumFromString(Intrinsic, intrinsic_name) orelse panic("Unknown intrinsic: {s}", .{intrinsic_name}); + const intrinsic_argument_node_list = analyzer.getScopeNodeList(scope_index, analyzer.getScopeNode(scope_index, intrinsic_node.left)); + + const result = switch (intrinsic) { + .import => blk: { + assert(intrinsic_argument_node_list.items.len == 1); + const import_argument = analyzer.getScopeNode(scope_index, intrinsic_argument_node_list.items[0]); + + switch (import_argument.id) { + .string_literal => { + const import_name = analyzer.tokenStringLiteral(scope_index, import_argument.token); + const import_file = try analyzer.module.importFile(analyzer.allocator, analyzer.current_file, import_name); + logln(.sema, .node, "Importing \"{s}\"...", .{import_name}); + + if (import_file.file.is_new) { + const new_file_index = import_file.file.index; + try analyzer.module.generateAbstractSyntaxTreeForFile(analyzer.allocator, new_file_index); + var value = Value{ + .unresolved = undefined, + }; + const analyze_result = try analyzeFile(&value, analyzer.allocator, analyzer.module, new_file_index); + logln(.sema, .node, "Done analyzing {s}!", .{import_name}); + const result = Value{ + .type = analyze_result, + }; + break :blk result; + } else { + const result = Value{ + .type = import_file.file.ptr.type, + }; + assert(!result.type.invalid); + + break :blk result; + } + }, + else => unreachable, + } + }, + .syscall => blk: { + if (intrinsic_argument_node_list.items.len > 0 and intrinsic_argument_node_list.items.len <= 6 + 1) { + const argument_expect_type = .{ + .flexible_integer = .{ + .byte_count = 8, + }, + }; + const number_allocation = try analyzer.unresolvedAllocate(scope_index, argument_expect_type, intrinsic_argument_node_list.items[0]); + const number = number_allocation.index; + assert(!number.invalid); + var arguments = std.mem.zeroes([6]Value.Index); + for (intrinsic_argument_node_list.items[1..], 0..) |argument_node_index, argument_index| { + const argument_allocation = try analyzer.unresolvedAllocate(scope_index, argument_expect_type, argument_node_index); + arguments[argument_index] = argument_allocation.index; + } + + // TODO: typecheck for usize + // for (arguments[0..intrinsic_argument_node_list.items.len]) |argument| { + // _ = argument; + // } + + break :blk Value{ + .syscall = (try analyzer.module.syscalls.append(analyzer.allocator, .{ + .number = number, + .arguments = arguments, + .argument_count = @intCast(intrinsic_argument_node_list.items.len - 1), + })).index, + }; + } else { + unreachable; + } + }, + .@"error" => { + assert(intrinsic_argument_node_list.items.len == 1); + const message_node = analyzer.getScopeNode(scope_index, intrinsic_argument_node_list.items[0]); + switch (message_node.id) { + .string_literal => panic("error: {s}", .{analyzer.tokenStringLiteral(scope_index, message_node.token)}), + else => |t| @panic(@tagName(t)), + } + unreachable; + }, + .cast => blk: { + assert(intrinsic_argument_node_list.items.len == 1); + const value_to_cast = try analyzer.unresolvedAllocate(scope_index, ExpectType.none, intrinsic_argument_node_list.items[0]); + const value_type = value_to_cast.ptr.getType(analyzer.module); + assert(expect_type != .none); + const cast_result = try analyzer.canCast(expect_type, value_type); + + if (cast_result == .success) { + const cast = try analyzer.module.casts.append(analyzer.allocator, .{ + .value = value_to_cast.index, + .type = switch (expect_type) { + .none => unreachable, + .flexible_integer => |flexible_integer| if (flexible_integer.sign) |sign| switch (sign) { + else => unreachable, + } else switch (flexible_integer.byte_count) { + 1 => Type.u8, + 2 => Type.u16, + 4 => Type.u32, + 8 => Type.u64, + else => unreachable, + }, + .type_index => |type_index| type_index, + }, + }); + + break :blk Value{ + .cast = cast.index, + }; + } else { + std.debug.panic("Can't cast", .{}); + } + }, + }; + + return result; + } }; -const ExpectType = union(enum) { - none, - type_index: Type.Index, - flexible_integer: FlexibleInteger, - - pub const none = ExpectType{ - .none = {}, - }; - pub const boolean = ExpectType{ - .type_index = Type.boolean, - }; - - pub const @"type" = ExpectType{ - .type_index = Type.type, - }; - - const FlexibleInteger = struct { - byte_count: u8, - sign: ?bool = null, - }; -}; - -pub var unreachable_index = Value.Index.invalid; - pub fn initialize(compilation: *Compilation, module: *Module, package: *Package, main_value: *Value) !void { _ = try analyzeExistingPackage(main_value, compilation, module, package); @@ -1787,7 +2967,7 @@ pub fn initialize(compilation: *Compilation, module: *Module, package: *Package, const value = module.values.get(decl.init_value); module.entry_point = switch (value.*) { .function_definition => |function_index| function_index, - .unresolved => panic("Unresolved declaration: {s}\n", .{declaration_name}), + .unresolved => panic("Unresolved declaration: {s}", .{declaration_name}), else => |t| @panic(@tagName(t)), }; break; @@ -1816,6 +2996,9 @@ pub fn analyzeFile(value: *Value, allocator: Allocator, module: *Module, file_in .module = module, }; - const result = try analyzer.structType(value, Scope.Index.invalid, .{ .value = 0 }, file_index); + const node = file.syntactic_analyzer_result.nodes.items[0]; + const node_list_node = analyzer.getFileNode(file_index, node.left); + const nodes = analyzer.getFileNodeList(file_index, node_list_node); + const result = try analyzer.structType(value, Scope.Index.invalid, nodes.items, file_index, .{ .value = 0 }); return result; } diff --git a/bootstrap/frontend/syntactic_analyzer.zig b/bootstrap/frontend/syntactic_analyzer.zig index 86fc611..9208723 100644 --- a/bootstrap/frontend/syntactic_analyzer.zig +++ b/bootstrap/frontend/syntactic_analyzer.zig @@ -53,11 +53,11 @@ pub const Logger = enum { }; // TODO: pack it to be more efficient -pub const Node = packed struct(u128) { - token: u32, - id: Id, +pub const Node = struct { left: Node.Index, right: Node.Index, + token: Token.Index, + id: Id, pub const List = ArrayList(Node.Index); @@ -90,80 +90,91 @@ pub const Node = packed struct(u128) { end: u32, }; - pub const Id = enum(u32) { - main = 0, - identifier = 1, - number = 2, - @"return" = 3, - block_one = 4, - function_declaration_no_arguments = 5, - container_declaration = 6, - string_literal = 7, - compiler_intrinsic_one = 8, - simple_symbol_declaration = 9, - assign = 10, - @"comptime" = 11, - node_list = 12, - block_zero = 13, - simple_while = 14, - simple_function_prototype = 15, - function_definition = 16, - keyword_noreturn = 17, - keyword_true = 18, - comptime_block_zero = 19, - comptime_block_one = 20, - number_literal = 21, - compiler_intrinsic_two = 22, - comptime_block_two = 23, - block_two = 24, - @"unreachable" = 25, - field_access = 26, - call_one = 27, - comptime_block = 28, - block = 29, - unsigned_integer_type = 30, - signed_integer_type = 31, - main_one = 32, - main_two = 33, - main_zero = 34, - call_two = 35, - slice_type = 36, - argument_declaration = 37, - compiler_intrinsic = 38, - ssize_type = 39, - usize_type = 40, - void_type = 41, - call = 42, - many_pointer_type = 43, - enum_literal = 44, - address_of = 45, - keyword_false = 46, - compare_equal = 47, - compare_not_equal = 48, - compare_less_than = 49, - compare_greater_than = 50, - compare_less_or_equal = 51, - compare_greater_or_equal = 52, - @"if" = 53, - if_else = 54, - @"switch" = 55, - switch_case = 56, - enum_type = 57, - enum_field = 58, - extern_qualifier = 59, - function_prototype = 60, - add = 61, - sub = 62, - logical_and = 63, - logical_xor = 64, - expression_group = 65, - logical_or = 66, - multiply = 67, - divide = 68, - shift_left = 69, - shift_right = 70, - bool_type = 71, - named_argument = 72, + pub const Id = enum { + main, + identifier, + number, + @"return", + function_declaration_no_arguments, + container_declaration, + string_literal, + simple_symbol_declaration, + assign, + @"comptime", + node_list, + simple_while, + simple_function_prototype, + function_definition, + keyword_noreturn, + keyword_true, + number_literal, + @"unreachable", + field_access, + comptime_block, + block, + unsigned_integer_type, + signed_integer_type, + slice_type, + argument_declaration, + compiler_intrinsic, + ssize_type, + usize_type, + void_type, + call, + const_many_pointer_type, + many_pointer_type, + enum_literal, + address_of, + pointer_dereference, + keyword_false, + compare_equal, + compare_not_equal, + compare_less_than, + compare_greater_than, + compare_less_or_equal, + compare_greater_or_equal, + @"if", + if_else, + @"switch", + switch_case, + enum_type, + enum_field, + extern_qualifier, + export_qualifier, + function_prototype, + add, + sub, + bit_and, + bit_xor, + expression_group, + bit_or, + multiply, + divide, + shift_left, + shift_right, + bool_type, + named_argument, + optional_type, + container_field, + struct_type, + container_literal, + field_initialization, + boolean_not, + null_literal, + if_else_payload, + if_payload, + discard, + slice, + slice_range, + negation, + anonymous_container_literal, + const_single_pointer_type, + single_pointer_type, + indexed_access, + calling_convention, + assembly_register, + assembly_statement, + assembly_block, }; }; @@ -180,7 +191,6 @@ const Analyzer = struct { source_file: []const u8, file_index: File.Index, allocator: Allocator, - temporal_node_heap: ArrayList(Node.Index) = .{}, node_lists: ArrayList(Node.List) = .{}, fn expectToken(analyzer: *Analyzer, token_id: Token.Id) !u32 { @@ -193,6 +203,7 @@ const Analyzer = struct { return result; } else { logln(.parser, .token_errors, "Unexpected token {s} when expected {s}\n| |\n v \n```\n{s}\n```", .{ @tagName(token.id), @tagName(token_id), analyzer.source_file[token.start..] }); + @breakpoint(); return error.unexpected_token; } } @@ -278,59 +289,6 @@ const Analyzer = struct { return try analyzer.addNode(declaration); } - fn containerMembers(analyzer: *Analyzer) !Members { - const node_heap_top = analyzer.temporal_node_heap.items.len; - defer analyzer.temporal_node_heap.shrinkRetainingCapacity(node_heap_top); - - while (analyzer.token_i < analyzer.tokens.len) { - const first = analyzer.token_i; - logln(.parser, .container_members, "First token for container member: {s}", .{@tagName(analyzer.tokens[first].id)}); - const member_node_index: Node.Index = switch (analyzer.tokens[first].id) { - .fixed_keyword_comptime => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .left_brace => blk: { - analyzer.token_i += 1; - const comptime_block = try analyzer.block(.{ .is_comptime = true }); - - break :blk try analyzer.addNode(.{ - .id = .@"comptime", - .token = first, - .left = comptime_block, - .right = Node.Index.invalid, - }); - }, - else => |foo| @panic(@tagName(foo)), - }, - .fixed_keyword_const, .fixed_keyword_var => try analyzer.symbolDeclaration(), - else => |t| @panic(@tagName(t)), - }; - - logln(.parser, .container_members, "Container member {s}", .{@tagName(analyzer.nodes.items[member_node_index.unwrap()].id)}); - - try analyzer.temporal_node_heap.append(analyzer.allocator, member_node_index); - } - - const members_array = analyzer.temporal_node_heap.items[node_heap_top..]; - const members: Members = switch (members_array.len) { - 1 => .{ - .len = 1, - .left = members_array[0], - .right = Node.Index.invalid, - }, - 2 => .{ - .len = 2, - .left = members_array[0], - .right = members_array[1], - }, - else => |len| .{ - .len = len, - .left = try analyzer.nodeList(members_array), - .right = Node.Index.invalid, - }, - }; - - return members; - } - fn function(analyzer: *Analyzer) !Node.Index { const token = analyzer.token_i; assert(analyzer.tokens[token].id == .fixed_keyword_fn); @@ -364,27 +322,56 @@ const Analyzer = struct { }); return switch (analyzer.tokens[analyzer.token_i].id) { - .semicolon, .left_brace => simple_function_prototype, + .semicolon, + .left_brace, + .comma, + => simple_function_prototype, else => blk: { var list = Node.List{}; + while (true) { - const attribute = switch (analyzer.tokens[analyzer.token_i].id) { - .semicolon, .left_brace => break, + const attribute_token = analyzer.token_i; + const attribute = switch (analyzer.tokens[attribute_token].id) { + .semicolon, + .left_brace, + => break, .fixed_keyword_extern => b: { const result = try analyzer.addNode(.{ .id = .extern_qualifier, - .token = analyzer.token_i, + .token = attribute_token, .left = Node.Index.invalid, .right = Node.Index.invalid, }); analyzer.token_i += 1; break :b result; }, - else => b: { - if (true) unreachable; - break :b undefined; + .fixed_keyword_export => b: { + const result = try analyzer.addNode(.{ + .id = .export_qualifier, + .token = attribute_token, + .left = Node.Index.invalid, + .right = Node.Index.invalid, + }); + analyzer.token_i += 1; + break :b result; }, + .fixed_keyword_cc => b: { + analyzer.token_i += 1; + _ = try analyzer.expectToken(.left_parenthesis); + const calling_conv_expression = try analyzer.expression(); + _ = try analyzer.expectToken(.right_parenthesis); + + const result = try analyzer.addNode(.{ + .id = .calling_convention, + .token = attribute_token, + .left = calling_conv_expression, + .right = Node.Index.invalid, + }); + break :b result; + }, + else => |t| @panic(@tagName(t)), }; + try list.append(analyzer.allocator, attribute); } @@ -392,7 +379,7 @@ const Analyzer = struct { .id = .function_prototype, .token = token, .left = simple_function_prototype, - .right = try analyzer.nodeList(list.items), + .right = try analyzer.nodeList(list), }); }, }; @@ -412,7 +399,7 @@ const Analyzer = struct { const type_expression = try analyzer.typeExpression(); // const type_expression_node = analyzer.nodes.items[type_expression.unwrap()]; // _ = type_expression_node; - // logln("Type expression node: {}\n", .{type_expression_node}); + // logln("Type expression node: {}", .{type_expression_node}); foo = true; if (analyzer.tokens[analyzer.token_i].id == .comma) { @@ -430,7 +417,7 @@ const Analyzer = struct { _ = try analyzer.expectToken(end_token); if (list.items.len != 0) { - return try analyzer.nodeList(list.items); + return try analyzer.nodeList(list); } else { return Node.Index.invalid; } @@ -444,12 +431,11 @@ const Analyzer = struct { fn block(analyzer: *Analyzer, options: Options) !Node.Index { const left_brace = try analyzer.expectToken(.left_brace); - const node_heap_top = analyzer.temporal_node_heap.items.len; - defer analyzer.temporal_node_heap.shrinkRetainingCapacity(node_heap_top); + var list = ArrayList(Node.Index){}; while (analyzer.tokens[analyzer.token_i].id != .right_brace) { const first_statement_token = analyzer.tokens[analyzer.token_i]; - logln(.parser, .block, "First statement token: {s}\n", .{@tagName(first_statement_token.id)}); + logln(.parser, .block, "First statement token: {s}", .{@tagName(first_statement_token.id)}); const statement_index = switch (first_statement_token.id) { .identifier => switch (analyzer.tokens[analyzer.token_i + 1].id) { .colon => { @@ -463,58 +449,31 @@ const Analyzer = struct { .fixed_keyword_switch => try analyzer.switchExpression(), .fixed_keyword_if => try analyzer.ifExpression(), .fixed_keyword_const, .fixed_keyword_var => try analyzer.symbolDeclaration(), + .hash => blk: { + const intrinsic = try analyzer.compilerIntrinsic(); + _ = try analyzer.expectToken(.semicolon); + break :blk intrinsic; + }, else => |t| @panic(@tagName(t)), }; const node = analyzer.nodes.items[statement_index.unwrap()]; - logln(.parser, .block, "Adding statement: {s}\n", .{@tagName(node.id)}); + logln(.parser, .block, "Adding statement: {s}", .{@tagName(node.id)}); - try analyzer.temporal_node_heap.append(analyzer.allocator, statement_index); + try list.append(analyzer.allocator, statement_index); } _ = try analyzer.expectToken(.right_brace); - const statement_array = analyzer.temporal_node_heap.items[node_heap_top..]; - const node: Node = switch (statement_array.len) { - 0 => .{ - .id = switch (options.is_comptime) { - true => .comptime_block_zero, - false => .block_zero, - }, - .token = left_brace, - .left = Node.Index.invalid, - .right = Node.Index.invalid, + return try analyzer.addNode(.{ + .id = switch (options.is_comptime) { + true => .comptime_block, + false => .block, }, - 1 => .{ - .id = switch (options.is_comptime) { - true => .comptime_block_one, - false => .block_one, - }, - .token = left_brace, - .left = statement_array[0], - .right = Node.Index.invalid, - }, - 2 => .{ - .id = switch (options.is_comptime) { - true => .comptime_block_two, - false => .block_two, - }, - .token = left_brace, - .left = statement_array[0], - .right = statement_array[1], - }, - else => .{ - .id = switch (options.is_comptime) { - true => .comptime_block, - false => .block, - }, - .token = left_brace, - .left = try analyzer.nodeList(statement_array), - .right = Node.Index.invalid, - }, - }; - - return analyzer.addNode(node); + .token = left_brace, + .left = try analyzer.nodeList(list), + .right = Node.Index.invalid, + }); } fn whileExpression(analyzer: *Analyzer, options: Options) anyerror!Node.Index { @@ -536,20 +495,20 @@ const Analyzer = struct { } fn switchExpression(analyzer: *Analyzer) anyerror!Node.Index { - logln(.parser, .@"switch", "Parsing switch...\n", .{}); + logln(.parser, .@"switch", "Parsing switch...", .{}); const switch_token = analyzer.token_i; analyzer.token_i += 1; _ = try analyzer.expectToken(.left_parenthesis); const switch_expression = try analyzer.expression(); _ = try analyzer.expectToken(.right_parenthesis); - logln(.parser, .@"switch", "Parsed switch expression...\n", .{}); + logln(.parser, .@"switch", "Parsed switch expression...", .{}); _ = try analyzer.expectToken(.left_brace); var list = Node.List{}; while (analyzer.tokens[analyzer.token_i].id != .right_brace) { const case_token = analyzer.token_i; - logln(.parser, .@"switch", "Parsing switch case...\n", .{}); + logln(.parser, .@"switch", "Parsing switch case...", .{}); const case_node = switch (analyzer.tokens[case_token].id) { .fixed_keyword_else => blk: { analyzer.token_i += 1; @@ -572,7 +531,7 @@ const Analyzer = struct { break :blk switch (array_list.items.len) { 0 => unreachable, 1 => array_list.items[0], - else => try analyzer.nodeList(array_list.items), + else => try analyzer.nodeList(array_list), }; }, }; @@ -604,7 +563,7 @@ const Analyzer = struct { .id = .@"switch", .token = switch_token, .left = switch_expression, - .right = try analyzer.nodeList(list.items), + .right = try analyzer.nodeList(list), }); } @@ -616,6 +575,13 @@ const Analyzer = struct { const if_condition = try analyzer.expression(); _ = try analyzer.expectToken(.right_parenthesis); + const payload = if (analyzer.tokens[analyzer.token_i].id == .vertical_bar) blk: { + analyzer.token_i += 1; + const payload_identifier_node = try analyzer.identifierNode(); + _ = try analyzer.expectToken(.vertical_bar); + break :blk payload_identifier_node; + } else Node.Index.invalid; + const if_block = try analyzer.block(.{ .is_comptime = false }); const if_node = try analyzer.addNode(.{ @@ -639,7 +605,19 @@ const Analyzer = struct { else => if_node, }; - return result; + if (payload.invalid) { + return result; + } else { + return try analyzer.addNode(.{ + .id = switch (result.value == if_node.value) { + true => .if_payload, + false => .if_else_payload, + }, + .token = if_token, + .left = result, + .right = payload, + }); + } } fn assignExpression(analyzer: *Analyzer) !Node.Index { @@ -661,54 +639,106 @@ const Analyzer = struct { .right = try analyzer.expression(), }; - logln(.parser, .assign, "assign:\nleft: {}.\nright: {}\n", .{ node.left, node.right }); + logln(.parser, .assign, "assign:\nleft: {}.\nright: {}", .{ node.left, node.right }); return try analyzer.addNode(node); } + fn parseAsmOperand(analyzer: *Analyzer) !Node.Index { + const token = analyzer.token_i; + const result = switch (analyzer.tokens[token].id) { + .identifier => try analyzer.addNode(.{ + .id = .assembly_register, + .token = blk: { + analyzer.token_i += 1; + break :blk token; + }, + .left = Node.Index.invalid, + .right = Node.Index.invalid, + }), + .number_literal => blk: { + analyzer.token_i += 1; + break :blk analyzer.addNode(.{ + .id = .number_literal, + .token = token, + .left = Node.Index.invalid, + .right = Node.Index.invalid, + }); + }, + .left_brace => blk: { + analyzer.token_i += 1; + const result = try analyzer.expression(); + _ = try analyzer.expectToken(.right_brace); + break :blk result; + }, + else => |t| @panic(@tagName(t)), + }; + return result; + } + fn compilerIntrinsic(analyzer: *Analyzer) !Node.Index { const hash = try analyzer.expectToken(.hash); - _ = try analyzer.expectToken(.identifier); + const intrinsic_token = try analyzer.expectToken(.identifier); _ = try analyzer.expectToken(.left_parenthesis); - const temporal_heap_top = analyzer.temporal_node_heap.items.len; - defer analyzer.temporal_node_heap.shrinkRetainingCapacity(temporal_heap_top); + if (equal(u8, analyzer.bytes(intrinsic_token), "asm")) { + _ = try analyzer.expectToken(.left_brace); + var statements = ArrayList(Node.Index){}; - while (analyzer.tokens[analyzer.token_i].id != .right_parenthesis) { - const parameter = try analyzer.expression(); - try analyzer.temporal_node_heap.append(analyzer.allocator, parameter); + while (analyzer.tokens[analyzer.token_i].id != .right_brace) { + const instruction_token = try analyzer.expectToken(.identifier); + var operand_list = ArrayList(Node.Index){}; - switch (analyzer.tokens[analyzer.token_i].id) { - .comma => analyzer.token_i += 1, - .right_parenthesis => continue, - else => unreachable, + while (analyzer.tokens[analyzer.token_i].id != .semicolon) { + const asm_operand = try analyzer.parseAsmOperand(); + switch (analyzer.tokens[analyzer.token_i].id) { + .semicolon => {}, + .comma => analyzer.token_i += 1, + else => |t| @panic(@tagName(t)), + } + try operand_list.append(analyzer.allocator, asm_operand); + } + + _ = try analyzer.expectToken(.semicolon); + + try statements.append(analyzer.allocator, try analyzer.addNode(.{ + .id = .assembly_statement, + .token = instruction_token, + .left = try analyzer.nodeList(operand_list), + .right = Node.Index.invalid, + })); } - } + _ = try analyzer.expectToken(.right_brace); + _ = try analyzer.expectToken(.right_parenthesis); - // Consume the right parenthesis - analyzer.token_i += 1; - - const parameters = analyzer.temporal_node_heap.items[temporal_heap_top..]; - - return try switch (parameters.len) { - 1 => analyzer.addNode(.{ - .id = .compiler_intrinsic_one, + return try analyzer.addNode(.{ + .id = .assembly_block, .token = hash, - .left = parameters[0], + .left = try analyzer.nodeList(statements), .right = Node.Index.invalid, - }), - 2 => analyzer.addNode(.{ - .id = .compiler_intrinsic_two, - .token = hash, - .left = parameters[0], - .right = parameters[1], - }), - else => analyzer.addNode(.{ + }); + } else { + var list = ArrayList(Node.Index){}; + while (analyzer.tokens[analyzer.token_i].id != .right_parenthesis) { + const parameter = try analyzer.expression(); + try list.append(analyzer.allocator, parameter); + + switch (analyzer.tokens[analyzer.token_i].id) { + .comma => analyzer.token_i += 1, + .right_parenthesis => continue, + else => unreachable, + } + } + + // Consume the right parenthesis + analyzer.token_i += 1; + + return try analyzer.addNode(.{ .id = .compiler_intrinsic, .token = hash, - .left = try analyzer.nodeList(parameters), + .left = try analyzer.nodeList(list), .right = Node.Index.invalid, - }), - }; + }); + } } fn expression(analyzer: *Analyzer) anyerror!Node.Index { @@ -718,11 +748,15 @@ const Analyzer = struct { const PrecedenceOperator = enum { compare_equal, compare_not_equal, + compare_less_than, + compare_greater_than, + compare_less_or_equal, + compare_greater_or_equal, add, sub, - logical_and, - logical_xor, - logical_or, + bit_and, + bit_xor, + bit_or, multiply, divide, shift_left, @@ -732,11 +766,15 @@ const Analyzer = struct { const operator_precedence = std.EnumArray(PrecedenceOperator, i32).init(.{ .compare_equal = 30, .compare_not_equal = 30, + .compare_less_than = 30, + .compare_greater_than = 30, + .compare_less_or_equal = 30, + .compare_greater_or_equal = 30, .add = 60, .sub = 60, - .logical_and = 40, - .logical_xor = 40, - .logical_or = 40, + .bit_and = 40, + .bit_xor = 40, + .bit_or = 40, .multiply = 70, .divide = 70, .shift_left = 50, @@ -746,11 +784,15 @@ const Analyzer = struct { const operator_associativity = std.EnumArray(PrecedenceOperator, Associativity).init(.{ .compare_equal = .none, .compare_not_equal = .none, + .compare_less_than = .none, + .compare_greater_than = .none, + .compare_less_or_equal = .none, + .compare_greater_or_equal = .none, .add = .left, .sub = .left, - .logical_and = .left, - .logical_xor = .left, - .logical_or = .left, + .bit_and = .left, + .bit_xor = .left, + .bit_or = .left, .multiply = .left, .divide = .left, .shift_left = .left, @@ -760,11 +802,15 @@ const Analyzer = struct { const operator_node_id = std.EnumArray(PrecedenceOperator, Node.Id).init(.{ .compare_equal = .compare_equal, .compare_not_equal = .compare_not_equal, + .compare_greater_than = .compare_greater_than, + .compare_less_than = .compare_less_than, + .compare_greater_or_equal = .compare_greater_or_equal, + .compare_less_or_equal = .compare_less_or_equal, .add = .add, .sub = .sub, - .logical_and = .logical_and, - .logical_xor = .logical_xor, - .logical_or = .logical_or, + .bit_and = .bit_and, + .bit_xor = .bit_xor, + .bit_or = .bit_or, .multiply = .multiply, .divide = .divide, .shift_left = .shift_left, @@ -775,20 +821,20 @@ const Analyzer = struct { var result = try analyzer.prefixExpression(); if (!result.invalid) { const prefix_node = analyzer.nodes.items[result.unwrap()]; - logln(.parser, .precedence, "Prefix: {}\n", .{prefix_node.id}); + logln(.parser, .precedence, "Prefix: {}", .{prefix_node.id}); } var banned_precedence: i32 = -1; while (analyzer.token_i < analyzer.tokens.len) { const token = analyzer.tokens[analyzer.token_i]; - // logln("Looping in expression precedence with token {}\n", .{token}); + // logln("Looping in expression precedence with token {}", .{token}); const operator: PrecedenceOperator = switch (token.id) { .semicolon, .right_parenthesis, .right_brace, + .right_bracket, .comma, - .period, .fixed_keyword_const, .fixed_keyword_var, .identifier, @@ -818,15 +864,15 @@ const Analyzer = struct { }, .ampersand => switch (next_token_id) { .equal => unreachable, - else => .logical_and, + else => .bit_and, }, .caret => switch (next_token_id) { .equal => unreachable, - else => .logical_xor, + else => .bit_xor, }, .vertical_bar => switch (next_token_id) { .equal => unreachable, - else => .logical_or, + else => .bit_or, }, .asterisk => switch (next_token_id) { .equal => unreachable, @@ -842,7 +888,12 @@ const Analyzer = struct { }, .greater => switch (next_token_id) { .greater => .shift_right, - else => unreachable, + .equal => .compare_greater_or_equal, + else => .compare_greater_than, + }, + .period => switch (next_token_id) { + .period => break, + else => break, }, else => |t| @panic(@tagName(t)), }; @@ -854,12 +905,12 @@ const Analyzer = struct { const precedence = operator_precedence.get(operator); if (precedence < minimum_precedence) { - logln(.parser, .precedence, "Breaking for minimum_precedence\n", .{}); + logln(.parser, .precedence, "Breaking for minimum_precedence", .{}); break; } if (precedence < banned_precedence) { - logln(.parser, .precedence, "Breaking for banned_precedence\n", .{}); + logln(.parser, .precedence, "Breaking for banned_precedence", .{}); break; } @@ -867,14 +918,18 @@ const Analyzer = struct { const extra_tokens: u32 = switch (operator) { .add, .sub, - .logical_and, - .logical_xor, - .logical_or, + .bit_and, + .bit_xor, + .bit_or, .multiply, .divide, + .compare_less_than, + .compare_greater_than, => 0, .compare_equal, .compare_not_equal, + .compare_less_or_equal, + .compare_greater_or_equal, .shift_right, .shift_left, => 1, @@ -906,18 +961,18 @@ const Analyzer = struct { fn prefixExpression(analyzer: *Analyzer) !Node.Index { const token = analyzer.token_i; - // logln("Prefix...\n", .{}); + // logln("Prefix...", .{}); const node_id: Node.Id = switch (analyzer.tokens[token].id) { else => |pref| { _ = pref; return try analyzer.primaryExpression(); }, - .at => .address_of, .bang => switch (analyzer.tokens[token + 1].id) { .equal => return try analyzer.primaryExpression(), - else => unreachable, + else => .boolean_not, }, - .minus, .tilde => |t| @panic(@tagName(t)), + .minus => .negation, + .tilde => |t| @panic(@tagName(t)), }; return try analyzer.addNode(.{ @@ -941,14 +996,16 @@ const Analyzer = struct { .number_literal, .fixed_keyword_true, .fixed_keyword_false, - .hash, .fixed_keyword_unreachable, + .fixed_keyword_null, .fixed_keyword_switch, + .hash, .period, - .fixed_keyword_enum, + .left_parenthesis, .keyword_signed_integer, .keyword_unsigned_integer, - .left_parenthesis, + .fixed_keyword_enum, + .fixed_keyword_struct, => try analyzer.curlySuffixExpression(), .fixed_keyword_fn => try analyzer.function(), .fixed_keyword_return => try analyzer.addNode(.{ @@ -973,7 +1030,12 @@ const Analyzer = struct { const left = try analyzer.typeExpression(); return switch (analyzer.tokens[analyzer.token_i].id) { - .left_brace => unreachable, + .left_brace => try analyzer.addNode(.{ + .id = .container_literal, + .token = analyzer.token_i, + .left = left, + .right = try analyzer.fieldInitialization(), + }), else => left, }; } @@ -1005,30 +1067,72 @@ const Analyzer = struct { }); } - fn typeExpression(analyzer: *Analyzer) !Node.Index { + fn pointerTypeExpression(analyzer: *Analyzer, arguments: struct { + many: bool, + start_token: Token.Index, + }) !Node.Index { + const is_const = analyzer.tokens[analyzer.token_i].id == .fixed_keyword_const; + analyzer.token_i += @intFromBool(is_const); + // TODO: handle correctly + switch (analyzer.tokens[analyzer.token_i].id) { + .fixed_keyword_fn => {}, + .identifier => {}, + .keyword_signed_integer, .keyword_unsigned_integer => {}, + else => |t| @panic(@tagName(t)), + } + + const pointer_element_type = try analyzer.typeExpression(); + + return try analyzer.addNode(.{ + .id = switch (arguments.many) { + true => switch (is_const) { + true => .const_many_pointer_type, + false => .many_pointer_type, + }, + false => switch (is_const) { + true => .const_single_pointer_type, + false => .single_pointer_type, + }, + }, + .token = arguments.start_token, + .left = pointer_element_type, + .right = Node.Index.invalid, + }); + } + + fn typeExpression(analyzer: *Analyzer) anyerror!Node.Index { const first = analyzer.token_i; return switch (analyzer.tokens[first].id) { else => try analyzer.errorUnionExpression(), - .at => unreachable, // pointer + .question_mark => blk: { + analyzer.token_i += 1; + break :blk try analyzer.addNode(.{ + .id = .optional_type, + .token = first, + .left = try analyzer.typeExpression(), + .right = Node.Index.invalid, + }); + }, + .ampersand => blk: { + analyzer.token_i += 1; + break :blk try analyzer.pointerTypeExpression(.{ + .many = false, + .start_token = first, + }); + }, // pointer .bang => unreachable, // error .left_bracket => switch (analyzer.tokens[analyzer.token_i + 1].id) { - .at => { + .ampersand => blk: { // many item pointer analyzer.token_i += 2; _ = try analyzer.expectToken(.right_bracket); - const is_const = analyzer.tokens[analyzer.token_i].id == .fixed_keyword_const; - analyzer.token_i += @intFromBool(is_const); - - const pointer_element_type = try analyzer.typeExpression(); - - return try analyzer.addNode(.{ - .id = .many_pointer_type, - .token = first, - .left = pointer_element_type, - .right = Node.Index.invalid, + break :blk try analyzer.pointerTypeExpression(.{ + .many = true, + .start_token = first, }); }, + .asterisk => @panic("Meant to use ampersand?"), else => { const left_bracket = analyzer.token_i; analyzer.token_i += 1; @@ -1086,7 +1190,7 @@ const Analyzer = struct { const current_token = analyzer.token_i; var parameter = try analyzer.expression(); const parameter_node = analyzer.nodes.items[parameter.unwrap()]; - logln(.parser, .suffix, "Paremeter node: {s}\n", .{@tagName(parameter_node.id)}); + logln(.parser, .suffix, "Paremeter node: {s}", .{@tagName(parameter_node.id)}); if (analyzer.tokens[analyzer.token_i].id == .equal) { analyzer.token_i += 1; @@ -1108,25 +1212,11 @@ const Analyzer = struct { _ = try analyzer.expectToken(.right_parenthesis); // const is_comma = analyzer.tokens[analyzer.token_i].id == .comma; - return try analyzer.addNode(switch (expression_list.items.len) { - 0 => .{ - .id = .call_one, - .token = left_parenthesis, - .left = result, - .right = Node.Index.invalid, - }, - 1 => .{ - .id = .call_two, - .token = left_parenthesis, - .left = result, - .right = expression_list.items[0], - }, - else => .{ - .id = .call, - .token = left_parenthesis, - .left = result, - .right = try analyzer.nodeList(expression_list.items), - }, + return try analyzer.addNode(.{ + .id = .call, + .token = left_parenthesis, + .left = result, + .right = try analyzer.nodeList(expression_list), }); } else { return result; @@ -1137,11 +1227,48 @@ const Analyzer = struct { unreachable; } - fn primaryTypeExpression(analyzer: *Analyzer) !Node.Index { + fn fieldInitialization(analyzer: *Analyzer) !Node.Index { + _ = try analyzer.expectToken(.left_brace); + + var list = ArrayList(Node.Index){}; + + while (analyzer.tokens[analyzer.token_i].id != .right_brace) { + const start_token = analyzer.token_i; + switch (analyzer.tokens[start_token].id) { + .period => { + analyzer.token_i += 1; + _ = try analyzer.expectToken(.identifier); + _ = try analyzer.expectToken(.equal); + const field_expression_initializer = try analyzer.expression(); + _ = try analyzer.expectToken(.comma); + + const field_initialization = try analyzer.addNode(.{ + .id = .field_initialization, + .token = start_token, + .left = Node.Index.invalid, + .right = field_expression_initializer, + }); + + try list.append(analyzer.allocator, field_initialization); + }, + else => |t| @panic(@tagName(t)), + } + } + + _ = try analyzer.expectToken(.right_brace); + + return try analyzer.nodeList(list); + } + + fn primaryTypeExpression(analyzer: *Analyzer) anyerror!Node.Index { const token_i = analyzer.token_i; const token = analyzer.tokens[token_i]; return try switch (token.id) { + .fixed_keyword_fn => blk: { + analyzer.token_i += 1; + break :blk analyzer.functionPrototype(); + }, .string_literal => blk: { analyzer.token_i += 1; break :blk analyzer.addNode(.{ @@ -1160,24 +1287,18 @@ const Analyzer = struct { .right = Node.Index.invalid, }); }, - .identifier => switch (analyzer.tokens[token_i + 1].id) { - .colon => unreachable, - else => blk: { - const identifier = analyzer.bytes(token_i); - // logln("identifier: {s}\n", .{identifier}); - analyzer.token_i += 1; - if (equal(u8, identifier, "_")) { - break :blk Node.Index.invalid; - } else break :blk analyzer.addNode(.{ - .id = .identifier, - .token = token_i, - .left = Node.Index.invalid, - .right = Node.Index.invalid, - }); - }, - }, + .identifier => analyzer.identifierNode(), .fixed_keyword_noreturn => analyzer.noReturn(), .fixed_keyword_true, .fixed_keyword_false => analyzer.boolLiteral(), + .fixed_keyword_null => analyzer.addNode(.{ + .id = .null_literal, + .token = blk: { + analyzer.token_i += 1; + break :blk token_i; + }, + .left = Node.Index.invalid, + .right = Node.Index.invalid, + }), .fixed_keyword_unreachable => analyzer.addNode(.{ .id = .@"unreachable", .token = blk: { @@ -1243,6 +1364,15 @@ const Analyzer = struct { .left = Node.Index.invalid, .right = Node.Index.invalid, }), + .left_brace => try analyzer.addNode(.{ + .id = .anonymous_container_literal, + .token = blk: { + analyzer.token_i += 1; + break :blk token_i; + }, + .left = try analyzer.fieldInitialization(), + .right = Node.Index.invalid, + }), else => |t| @panic(@tagName(t)), }, .fixed_keyword_enum => blk: { @@ -1251,7 +1381,7 @@ const Analyzer = struct { var enum_field_list = Node.List{}; while (analyzer.tokens[analyzer.token_i].id != .right_brace) { - const enum_name = try analyzer.expectToken(.identifier); + const enum_name_token = try analyzer.expectToken(.identifier); const value_associated = switch (analyzer.tokens[analyzer.token_i].id) { .comma => comma: { analyzer.token_i += 1; @@ -1262,7 +1392,7 @@ const Analyzer = struct { const enum_field_node = try analyzer.addNode(.{ .id = .enum_field, - .token = enum_name, + .token = enum_name_token, .left = value_associated, .right = Node.Index.invalid, }); @@ -1275,7 +1405,28 @@ const Analyzer = struct { break :blk try analyzer.addNode(.{ .id = .enum_type, .token = token_i, - .left = try analyzer.nodeList(enum_field_list.items), + .left = try analyzer.nodeList(enum_field_list), + .right = Node.Index.invalid, + }); + }, + .fixed_keyword_struct => blk: { + analyzer.token_i += 1; + + // TODO: is this the best way? + if (analyzer.tokens[analyzer.token_i].id == .left_parenthesis) { + analyzer.token_i += 1; + assert(analyzer.tokens[analyzer.token_i + 1].id == .right_parenthesis); + analyzer.token_i += 2; + } + + _ = try analyzer.expectToken(.left_brace); + const node_list = try analyzer.containerMembers(); + _ = try analyzer.expectToken(.right_brace); + + break :blk try analyzer.addNode(.{ + .id = .struct_type, + .token = token_i, + .left = try analyzer.nodeList(node_list), .right = Node.Index.invalid, }); }, @@ -1299,27 +1450,77 @@ const Analyzer = struct { // TODO: fn suffixOperator(analyzer: *Analyzer, left: Node.Index) !Node.Index { - const token = analyzer.tokens[analyzer.token_i]; - return switch (token.id) { - .left_bracket => unreachable, + const token = analyzer.token_i; + return switch (analyzer.tokens[token].id) { + .left_bracket => blk: { + analyzer.token_i += 1; + const index_expression = try analyzer.expression(); + + if (analyzer.tokens[analyzer.token_i].id == .period and analyzer.token_i + 1 < analyzer.tokens.len and analyzer.tokens[analyzer.token_i + 1].id == .period) { + analyzer.token_i += 2; + const range_end_expression = switch (analyzer.tokens[analyzer.token_i].id) { + .right_bracket => unreachable, + else => try analyzer.expression(), + }; + + _ = try analyzer.expectToken(.right_bracket); + + break :blk try analyzer.addNode(.{ + .id = .slice, + .token = token, + .left = left, + .right = try analyzer.addNode(.{ + .id = .slice_range, + .token = token, + .left = index_expression, + .right = range_end_expression, + }), + }); + } else { + _ = try analyzer.expectToken(.right_bracket); + break :blk try analyzer.addNode(.{ + .id = .indexed_access, + .token = token, + .left = left, + .right = index_expression, + }); + } + }, .period => switch (analyzer.tokens[analyzer.token_i + 1].id) { .identifier => try analyzer.addNode(.{ .id = .field_access, .token = blk: { - const main_token = analyzer.token_i; analyzer.token_i += 1; - break :blk main_token; + break :blk token; }, .left = left, .right = blk: { //TODO ??? - const right_token = analyzer.token_i; + const result: Node.Index = @bitCast(analyzer.token_i); analyzer.token_i += 1; - const result: Node.Index = @bitCast(right_token); - logln(.parser, .suffix, "WARNING: rhs has node index {} but it's token #{}\n", .{ result, right_token }); + logln(.parser, .suffix, "WARNING: rhs has node index {} but it's token #{}", .{ result, token }); break :blk result; }, }), + .period => Node.Index.invalid, + .ampersand => try analyzer.addNode(.{ + .id = .address_of, + .token = blk: { + analyzer.token_i += 2; + break :blk token; + }, + .left = left, + .right = Node.Index.invalid, + }), + .at => try analyzer.addNode(.{ + .id = .pointer_dereference, + .token = blk: { + analyzer.token_i += 2; + break :blk token; + }, + .left = left, + .right = Node.Index.invalid, + }), else => |t| @panic(@tagName(t)), }, else => Node.Index.invalid, @@ -1329,20 +1530,18 @@ const Analyzer = struct { fn addNode(analyzer: *Analyzer, node: Node) !Node.Index { const index = analyzer.nodes.items.len; try analyzer.nodes.append(analyzer.allocator, node); - logln(.parser, .node_creation, "Adding node #{} (0x{x}) {s} to file #{}\n", .{ index, @intFromPtr(&analyzer.nodes.items[index]), @tagName(node.id), analyzer.file_index.uniqueInteger() }); + logln(.parser, .node_creation, "Adding node #{} (0x{x}) {s} to file #{}", .{ index, @intFromPtr(&analyzer.nodes.items[index]), @tagName(node.id), analyzer.file_index.uniqueInteger() }); // if (node.id == .identifier) { - // logln("Node identifier: {s}\n", .{analyzer.bytes(node.token)}); + // logln("Node identifier: {s}", .{analyzer.bytes(node.token)}); // } return Node.Index{ .value = @intCast(index), }; } - fn nodeList(analyzer: *Analyzer, input: []const Node.Index) !Node.Index { + fn nodeList(analyzer: *Analyzer, node_list: ArrayList(Node.Index)) !Node.Index { const index = analyzer.node_lists.items.len; - var new_node_list = try ArrayList(Node.Index).initCapacity(analyzer.allocator, input.len); - try new_node_list.appendSlice(analyzer.allocator, input); - try analyzer.node_lists.append(analyzer.allocator, new_node_list); + try analyzer.node_lists.append(analyzer.allocator, node_list); return try analyzer.addNode(.{ .id = .node_list, .token = 0, @@ -1350,6 +1549,74 @@ const Analyzer = struct { .right = Node.Index.invalid, }); } + + fn identifierNode(analyzer: *Analyzer) !Node.Index { + const identifier_token = analyzer.token_i; + assert(analyzer.tokens[identifier_token].id == .identifier); + analyzer.token_i += 1; + const identifier = analyzer.bytes(identifier_token); + // logln("identifier: {s}", .{identifier}); + return try analyzer.addNode(.{ + .id = if (equal(u8, identifier, "_")) .discard else .identifier, + .token = identifier_token, + .left = Node.Index.invalid, + .right = Node.Index.invalid, + }); + } + + fn containerMembers(analyzer: *Analyzer) !ArrayList(Node.Index) { + var list = ArrayList(Node.Index){}; + while (analyzer.token_i < analyzer.tokens.len and analyzer.tokens[analyzer.token_i].id != .right_brace) { + const first = analyzer.token_i; + logln(.parser, .container_members, "First token for container member: {s}", .{@tagName(analyzer.tokens[first].id)}); + const member_node_index: Node.Index = switch (analyzer.tokens[first].id) { + .fixed_keyword_comptime => switch (analyzer.tokens[analyzer.token_i + 1].id) { + .left_brace => blk: { + analyzer.token_i += 1; + const comptime_block = try analyzer.block(.{ .is_comptime = true }); + + break :blk try analyzer.addNode(.{ + .id = .@"comptime", + .token = first, + .left = comptime_block, + .right = Node.Index.invalid, + }); + }, + else => |foo| @panic(@tagName(foo)), + }, + .identifier => blk: { + analyzer.token_i += 1; + _ = try analyzer.expectToken(.colon); + + const field_type = try analyzer.typeExpression(); + + const field_default_node = if (analyzer.tokens[analyzer.token_i].id == .equal) b: { + analyzer.token_i += 1; + break :b try analyzer.expression(); + } else Node.Index.invalid; + + _ = try analyzer.expectToken(.comma); + + const field_node = try analyzer.addNode(.{ + .id = .container_field, + .token = first, + .left = field_type, + .right = field_default_node, + }); + + break :blk field_node; + }, + .fixed_keyword_const, .fixed_keyword_var => try analyzer.symbolDeclaration(), + else => |t| @panic(@tagName(t)), + }; + + logln(.parser, .container_members, "Container member {s}", .{@tagName(analyzer.nodes.items[member_node_index.unwrap()].id)}); + + try list.append(analyzer.allocator, member_node_index); + } + + return list; + } }; const Members = struct { @@ -1358,6 +1625,8 @@ const Members = struct { right: Node.Index, }; +// Here it is assumed that left brace is consumed + pub fn analyze(allocator: Allocator, tokens: []const Token, source_file: []const u8, file_index: File.Index) !Result { const start = std.time.Instant.now() catch unreachable; var analyzer = Analyzer{ @@ -1376,31 +1645,16 @@ pub fn analyze(allocator: Allocator, tokens: []const Token, source_file: []const assert(node_index.value == 0); assert(!node_index.invalid); - logln(.parser, .main_node, "Start Parsing file root members\n", .{}); const members = try analyzer.containerMembers(); - logln(.parser, .main_node, "End Parsing file root members\n", .{}); + assert(analyzer.token_i == analyzer.tokens.len); - switch (members.len) { - 0 => analyzer.nodes.items[0].id = .main_zero, - 1 => { - analyzer.nodes.items[0].id = .main_one; - analyzer.nodes.items[0].left = members.left; - }, - 2 => { - analyzer.nodes.items[0].id = .main_two; - analyzer.nodes.items[0].left = members.left; - analyzer.nodes.items[0].right = members.right; - }, - else => { - analyzer.nodes.items[0].id = .main; - analyzer.nodes.items[0].left = members.left; - }, - } + const node_list = try analyzer.nodeList(members); + + analyzer.nodes.items[0].id = .main; + analyzer.nodes.items[0].left = node_list; const end = std.time.Instant.now() catch unreachable; - analyzer.temporal_node_heap.clearAndFree(allocator); - return .{ .nodes = analyzer.nodes, .node_lists = analyzer.node_lists, diff --git a/bootstrap/main.zig b/bootstrap/main.zig index 8b4d474..5f18c83 100644 --- a/bootstrap/main.zig +++ b/bootstrap/main.zig @@ -5,9 +5,10 @@ const Compilation = @import("Compilation.zig"); pub const panic = Compilation.panic; pub fn main() !void { - const allocator = std.heap.page_allocator; + const GPA = std.heap.GeneralPurposeAllocator(.{}); + var gpa = GPA{}; - try Compilation.init(allocator); + try Compilation.init(gpa.allocator()); } test { diff --git a/build.zig b/build.zig index 2edd38f..f977860 100644 --- a/build.zig +++ b/build.zig @@ -29,6 +29,7 @@ pub fn build(b: *std.Build) !void { const debug_command = switch (@import("builtin").os.tag) { .linux => blk: { const result = b.addSystemCommand(&.{"gf2"}); + result.addArgs(&.{ "-ex", "set disassembly-flavor intel" }); result.addArg("-ex=r"); result.addArgs(&.{ "-ex", "up" }); result.addArg("--args"); diff --git a/lib/std/builtin.nat b/lib/std/builtin.nat index 835cfdf..7c41626 100644 --- a/lib/std/builtin.nat +++ b/lib/std/builtin.nat @@ -14,3 +14,8 @@ const Abi = enum{ gnu, msvc, }; + +const CallingConvention = enum{ + system_v, + naked, +}; diff --git a/lib/std/os.nat b/lib/std/os.nat index 4b4ffd3..a15a118 100644 --- a/lib/std/os.nat +++ b/lib/std/os.nat @@ -5,13 +5,13 @@ const system = switch (current) { .windows => windows, }; -const write = fn (file_descriptor: FileDescriptor, bytes_ptr: [@]const u8, bytes_len: usize) ssize { +const write = fn (file_descriptor: FileDescriptor, bytes_ptr: [&]const u8, bytes_len: usize) ssize { switch (current) { .linux => return #syscall(1, file_descriptor, #cast(bytes_ptr), bytes_len), .macos => return macos.write(file_descriptor, #cast(bytes_ptr), bytes_len), .windows => { var written_bytes: u32 = 0; - if (windows.WriteFile(file_descriptor, bytes_ptr, bytes_len, @written_bytes, false) != 0) { + if (windows.WriteFile(file_descriptor, bytes_ptr, bytes_len, written_bytes.&, false) != 0) { return written_bytes; } else { unreachable; @@ -22,7 +22,7 @@ const write = fn (file_descriptor: FileDescriptor, bytes_ptr: [@]const u8, bytes const FileDescriptor = system.FileDescriptor; -const print = fn(bytes_ptr: [@]const u8, bytes_len: usize) void { +const print = fn(bytes_ptr: [&]const u8, bytes_len: usize) void { const file_descriptor = switch (current) { .linux, .macos => 2, .windows => windows.GetStdHandle(windows.STD_OUTPUT_HANDLE), @@ -41,6 +41,38 @@ const exit = fn(exit_code: s32) noreturn { unreachable; } +const ProtectionFlags = struct(u32){ + read: bool, + write: bool, + execute: bool, +}; + +const MapFlags = struct(u32){ + reserve: bool, + commit: bool, +}; + +const allocate_virtual_memory = fn(address: ?[&]u8, length: usize, general_protection_flags: ProtectionFlags, general_map_flags: MapFlags) ?[&]u8 { + const protection_flags = system.get_protection_flags(flags = general_protection_flags); + const map_flags = system.get_map_flags(flags = general_map_flags); + + const result = switch (#import("builtin").os) { + .linux => linux.mmap(address, length, protection_flags, map_flags, fd = -1, offset = 0), + else => #error("OS not supported"), + }; + + return result; +} + +const free_virtual_memory = fn(bytes_ptr: [&]const u8, bytes_len: usize) bool { + const result = switch (#import("builtin").os) { + .linux => linux.munmap(bytes_ptr, bytes_len), + else => #error("OS not supported"), + }; + + return result; +} + const linux = #import("os/linux.nat"); const macos = #import("os/macos.nat"); const windows = #import("os/windows.nat"); diff --git a/lib/std/os/linux.nat b/lib/std/os/linux.nat index 3183d18..22e8aa6 100644 --- a/lib/std/os/linux.nat +++ b/lib/std/os/linux.nat @@ -1 +1,44 @@ +const std = #import("std"); + const FileDescriptor = s32; + +const ProtectionFlags = struct(u32) { + read: bool, + write: bool, + execute: bool, +}; + +const MapFlags = struct(u32){ + shared: bool, + private: bool, + reserved: u2 = 0, + fixed: bool, + anonymous: bool, +}; + +const get_protection_flags = fn(flags: std.os.ProtectionFlags) ProtectionFlags { + return ProtectionFlags{ + .read = flags.read, + .write = flags.write, + .execute = flags.execute, + }; +} + +const get_map_flags = fn(flags: std.os.MapFlags) MapFlags{ + return MapFlags{ + .shared = false, + .private = true, + .fixed = false, + .anonymous = true, + }; +} + +const mmap = fn(address: ?[&]u8, length: usize, protection_flags: ProtectionFlags, map_flags: MapFlags, fd: s32, offset: u64) ?[&]u8 { + const result = #syscall(9, #cast(address), length, #cast(protection_flags), #cast(map_flags), fd, offset); + return #cast(result); +} + +const munmap = fn(bytes_ptr: [&]const u8, bytes_len: usize) bool { + const result: ssize = #syscall(11, #cast(bytes_ptr), bytes_len); + return result == 0; +} diff --git a/lib/std/os/macos.nat b/lib/std/os/macos.nat index 7300cab..abec54e 100644 --- a/lib/std/os/macos.nat +++ b/lib/std/os/macos.nat @@ -1,3 +1,3 @@ const FileDescriptor = s32; -const write = fn (file_descriptor: FileDescriptor, bytes_ptr: [@]const u8, bytes_len: usize) ssize extern; +const write = fn (file_descriptor: FileDescriptor, bytes_ptr: [&]const u8, bytes_len: usize) ssize extern; const exit = fn (exit_code: u32) noreturn extern; diff --git a/lib/std/start.nat b/lib/std/start.nat index f169441..2d35fff 100644 --- a/lib/std/start.nat +++ b/lib/std/start.nat @@ -3,7 +3,15 @@ comptime { _ = _start; } -const _start = fn () noreturn { +const _start = fn () noreturn export cc(.naked) { + #asm({ + xor ebp, ebp; + and rsp, 0xfffffffffffffff0; + call {start}; + }); +} + +const start = fn() noreturn export { const result = #import("main").main(); std.os.exit(exit_code = result); } diff --git a/lib/std/std.nat b/lib/std/std.nat index 6215821..fd4e741 100644 --- a/lib/std/std.nat +++ b/lib/std/std.nat @@ -6,3 +6,83 @@ const builtin = #import("builtin.nat"); const os = #import("os.nat"); const print = os.print; const start = #import("start.nat"); + +const assert = fn(ok: bool) void { + if (!ok) { + unreachable; + } +} + +const Allocator = struct { + handler: &const fn(allocator: &Allocator, old_ptr: ?[&]const u8, old_size: usize, new_size: usize, alignment: u16) ?[&]u8, + + const allocate = fn (allocator: &Allocator, size: usize, alignment: u16) ?[]u8 { + if (allocator.handler(allocator, old_ptr = null, old_size = 0, new_size = size, alignment)) |result| { + return result[0..size]; + } else { + return null; + } + } + + const free = fn (allocator: &Allocator, bytes_ptr: [&]const u8, bytes_len: usize) bool { + if (allocator.handler(allocator, old_ptr = bytes_ptr, old_size = bytes_len, new_size = 0, alignment = 0)) |_| { + return true; + } else { + return false; + } + } +}; + +const PageAllocator = struct{ + allocator: Allocator = .{ + .handler = handler.&, + }, + + const allocate = fn (a: &PageAllocator, size: usize, alignment: u16) ?[]u8 { + const result = a.allocator.allocate(size, alignment); + return result; + } + + const free = fn (a: &PageAllocator, bytes_ptr: [&]const u8, bytes_len: usize) bool { + const result = a.allocator.free(bytes_ptr, bytes_len); + return result; + } + + const handler = fn (allocator: &Allocator, maybe_old_ptr: ?[&]const u8, old_size: usize, new_size: usize, alignment: u16) ?[&]u8{ + var maybe_new_ptr: ?[&]u8 = null; + if (new_size > 0) { + const general_protection_flags = os.ProtectionFlags{ + .read = true, + .write = true, + .execute = false, + }; + const general_map_flags = os.MapFlags{ + .reserve = true, + .commit = true, + }; + + maybe_new_ptr = os.allocate_virtual_memory(address = null, length = new_size, general_protection_flags, general_map_flags); + } + + if (maybe_old_ptr) |old_ptr| { + if (maybe_new_ptr) |new_ptr| { + unreachable; + } + + const result = os.free_virtual_memory(bytes_ptr = old_ptr, bytes_len = old_size); + if (result) { + return #cast(old_ptr); + } else { + return null; + } + } else { + return maybe_new_ptr; + } + } + + const getAllocator = fn(page_allocator: &PageAllocator) &Allocator { + return page_allocator.allocator.&; + } +}; + +var page_allocator = PageAllocator{}; diff --git a/src/main.nat b/src/main.nat new file mode 100644 index 0000000..f5c63bd --- /dev/null +++ b/src/main.nat @@ -0,0 +1,19 @@ +const std = #import("std"); + +const main = fn() s32 { + const size = 0x1000; + if (std.page_allocator.allocate(size, alignment = 12)) |result| { + result[0] = 0; + std.print(bytes_ptr = "Allocation succeeded. Freeing...\n", bytes_len = 33); + if (std.page_allocator.free(bytes_ptr = result.ptr, bytes_len = result.len)) { + std.print(bytes_ptr = "Memory freed successfully\n", bytes_len = 26); + return 0; + } else { + std.print(bytes_ptr = "Memory freed with errors\n", bytes_len = 25); + return 1; + } + } else { + std.print(bytes_ptr = "Allocation failed!\n", bytes_len = 19); + return 1; + } +}