diff --git a/examples/hello.ry b/examples/hello.ry index 4b651a7..a8cfd08 100644 --- a/examples/hello.ry +++ b/examples/hello.ry @@ -1,5 +1,18 @@ // import std; +struct B { + field i32 +} + +struct Awoo { + b B + other_field i32 +} + +fn test_function() Awoo { + return 1; +} + fn multwo(num: i32, double_flag: bool) i32 { // TODO resolve expr variables if (true) { diff --git a/src/ast_printer.zig b/src/ast_printer.zig index 4f9ced9..f354971 100644 --- a/src/ast_printer.zig +++ b/src/ast_printer.zig @@ -3,6 +3,7 @@ const tokens = @import("tokens.zig"); const Token = tokens.Token; usingnamespace @import("ast.zig"); +usingnamespace @import("comp_ctx.zig"); const warn = std.debug.warn; @@ -335,3 +336,66 @@ pub fn printStmt(ident: usize, stmt: *const Stmt) void { else => std.debug.warn("UnknownStmt-{}", @tagName(stmt.*)), } } + +// very bad but be like that +fn retWithName(prefix: []const u8, inner: []const u8) []const u8 { + var ret_nam_buf = std.heap.direct_allocator.alloc(u8, 256) catch unreachable; + return std.fmt.bufPrint(ret_nam_buf[0..], "{}({})", prefix, inner) catch unreachable; +} + +fn prettyType(typ: SymbolUnderlyingType) []const u8 { + return switch (typ) { + .Integer32 => "i32", + .Integer64 => "i64", + .Bool => "bool", + + .OpaqueType => |ident| retWithName("opaque", ident), + .Struct => |ident| retWithName("struct", ident), + + else => unreachable, + }; +} + +pub fn printContext(ctx: CompilationContext) void { + var it = ctx.symbol_table.iterator(); + + while (it.next()) |kv| { + switch (kv.value) { + .Function => |fn_sym| { + std.debug.warn( + "function {} returns {}\n", + kv.key, + prettyType(fn_sym.return_type), + ); + + var param_it = fn_sym.parameters.iterator(); + while (param_it.next()) |param_kv| { + std.debug.warn( + "\tparameter {} typ {}\n", + param_kv.key, + prettyType(param_kv.value), + ); + } + }, + + .Struct => |typemap| { + std.debug.warn("struct '{}'\n", kv.key); + var map_it = typemap.iterator(); + while (map_it.next()) |map_kv| { + std.debug.warn( + "\tfield {} type {}\n", + map_kv.key, + prettyType(map_kv.value), + ); + } + }, + + .Variable => std.debug.warn( + "variable {} type {}\n", + kv.key, + kv.value, + ), + else => unreachable, + } + } +} diff --git a/src/codegen.zig b/src/codegen.zig index dba8564..56837d4 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -256,7 +256,7 @@ pub const Codegen = struct { }, else => { - std.debug.warn("got unhandled Node {}\n", node.*); + std.debug.warn("TODO handle node type {}\n", @tagName(node.*)); unreachable; }, } @@ -270,7 +270,7 @@ pub const Codegen = struct { defer llvm.LLVMDisposeModule(mod); for (root.Root.toSlice()) |child| { - std.debug.warn("cgen: gen child {}\n", child); + std.debug.warn("cgen: gen {}\n", @tagName(child)); try self.genNode(mod, &child); } diff --git a/src/comp_ctx.zig b/src/comp_ctx.zig index d8888c2..0646b22 100644 --- a/src/comp_ctx.zig +++ b/src/comp_ctx.zig @@ -1,21 +1,32 @@ const std = @import("std"); +const ast = @import("ast.zig"); pub const CompilationError = error{TypeError}; pub const SymbolTable = std.hash_map.StringHashMap(SymbolData); +pub const TypeList = std.ArrayList(SymbolUnderlyingType); pub const SymbolUnderlyingTypeEnum = enum { Integer32, Integer64, Bool, - CustomType, + + // opaque unsolved identifier + OpaqueType, + + // solved custom type + Struct, + Enum, }; pub const SymbolUnderlyingType = union(SymbolUnderlyingTypeEnum) { Integer32: void, Integer64: void, Bool: void, - CustomType: []const u8, + OpaqueType: []const u8, + + Struct: []const u8, + Enum: []const u8, }; // functions, for our purposes, other than symbols, have: @@ -26,18 +37,20 @@ pub const FunctionSymbol = struct { /// Parameters for a function are also a table instead of an ArrayList /// because we want to resolve identifiers to them. - parameters: SymbolTable, + parameters: UnderlyingTypeMap, symbols: SymbolTable, /// Find a given identifier in the function. Can resolve to either a parameter - pub fn findSymbol(self: *const FuncionSymbol, identifier: []const u8) ?Symbol { + pub fn findSymbol(self: *const @This(), identifier: []const u8) ?SymbolData { // try to find it on overall variable symbols - var var_sym = self.symbols.get(identifier); - if (var_sym != null) return var_sym; - var param_sym = self.parameters.get(identifier); - if (param_sym != null) return param_sym; + // TODO + //var var_sym = self.symbols.get(identifier); + //if (var_sym != null) return var_sym.?.value; + + //var param_sym = self.parameters.get(identifier); + //if (param_sym != null) return param_sym.?.value; return null; } }; @@ -94,7 +107,7 @@ pub const CompilationContext = struct { if (std.mem.eql(u8, typ, typ_ident)) return builtin_types[idx]; } - return .CustomType; + return .OpaqueType; } /// Solve a given type string into a full fleged SymbolUnderlyingType. @@ -110,7 +123,40 @@ pub const CompilationContext = struct { .Integer32 => SymbolUnderlyingType{ .Integer32 = {} }, .Integer64 => SymbolUnderlyingType{ .Integer64 = {} }, .Bool => SymbolUnderlyingType{ .Bool = {} }, - .CustomType => SymbolUnderlyingType{ .CustomType = typ_ident }, + .OpaqueType => SymbolUnderlyingType{ .OpaqueType = typ_ident }, + else => unreachable, }; } + + pub fn insertStruct(self: *@This(), struc: ast.Struct, field_types: TypeList) !void { + var type_map = UnderlyingTypeMap.init(self.allocator); + + for (struc.fields.toSlice()) |field, idx| { + _ = try type_map.put(field.name.lexeme, field_types.at(idx)); + } + + _ = try self.symbol_table.put(struc.name.lexeme, SymbolData{ .Struct = type_map }); + } + + pub fn insertFn( + self: *@This(), + decl: ast.FnDecl, + ret_type: SymbolUnderlyingType, + param_types: TypeList, + symbols: SymbolTable, + ) !void { + var type_map = UnderlyingTypeMap.init(self.allocator); + + for (decl.params.toSlice()) |param, idx| { + _ = try type_map.put(param.name.lexeme, param_types.at(idx)); + } + + _ = try self.symbol_table.put(decl.func_name.lexeme, SymbolData{ + .Function = FunctionSymbol{ + .return_type = ret_type, + .parameters = type_map, + .symbols = symbols, + }, + }); + } }; diff --git a/src/main.zig b/src/main.zig index a8dd326..7f94c8a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -51,7 +51,10 @@ pub fn run(allocator: *std.mem.Allocator, slice: []const u8) !Result { printer.printNode(root, 0); var solver = types.TypeSolver.init(allocator); - var ctx = solver.pass(root); + var ctx = try solver.pass(root); + + std.debug.warn("symbol table\n"); + printer.printContext(ctx); var cgen = codegen.Codegen.init(allocator); try cgen.gen(root); diff --git a/src/types.zig b/src/types.zig index b4084f1..609cf4d 100644 --- a/src/types.zig +++ b/src/types.zig @@ -3,44 +3,162 @@ const ast = @import("ast.zig"); const comp = @import("comp_ctx.zig"); +const CompileError = @import("codegen.zig").CompileError; +const Token = @import("tokens.zig").Token; + +const SymbolUnderlyingType = comp.SymbolUnderlyingType; + pub const TypeSolver = struct { allocator: *std.mem.Allocator, + // error handling + err_ctx: ?[]const u8 = null, + err_tok: ?Token = null, + hadError: bool = false, + pub fn init(allocator: *std.mem.Allocator) TypeSolver { return TypeSolver{ .allocator = allocator }; } + fn setErrContext(self: *@This(), comptime fmt: ?[]const u8, args: ...) void { + if (fmt == null) { + self.err_ctx = null; + return; + } + + // TODO allocate buffer on init() and use it + var buf = self.allocator.alloc(u8, 256) catch unreachable; + self.err_ctx = std.fmt.bufPrint(buf, fmt.?, args) catch unreachable; + } + + fn setErrToken(self: *@This(), tok: ?Token) void { + self.err_tok = tok; + } + + fn doError(self: *@This(), comptime fmt: []const u8, args: ...) void { + self.hadError = true; + + std.debug.warn("type error"); + if (self.err_tok) |tok| { + std.debug.warn(" at line {}", tok.line); + } + + if (self.err_ctx) |ctx| { + std.debug.warn(" on {}", ctx); + } + + std.debug.warn("\n\t"); + std.debug.warn(fmt, args); + std.debug.warn("\n"); + } + + /// Resolve a type in global scope + fn resolveGlobalType( + self: *@This(), + ctx: *comp.CompilationContext, + identifier: []const u8, + ) ?SymbolUnderlyingType { + // assume the identifier references a builtin + var typ = ctx.solveType(identifier); + + switch (typ) { + .OpaqueType => |val| { + // solve for opaque so it isnt opaque + var sym = ctx.symbol_table.get(val); + if (sym != null) + return switch (sym.?.value) { + .Struct => SymbolUnderlyingType{ .Struct = val }, + .Enum => SymbolUnderlyingType{ .Enum = val }, + + else => blk: { + self.doError( + "expected struct or enum for type '{}', got {}", + val, + sym, + ); + break :blk null; + }, + }; + + self.doError("Unknown type: '{}'", val); + return null; + }, + + else => return typ, + } + } + pub fn nodePass( self: *@This(), ctx: *comp.CompilationContext, node: *ast.Node, - ) void { + ) !void { switch (node.*) { .Root => unreachable, .FnDecl => |decl| { - var ret_type = ctx.solveType(decl.return_type.lexeme); + self.setErrToken(decl.return_type); + self.setErrContext("function {}", decl.func_name.lexeme); + var ret_type = self.resolveGlobalType(ctx, decl.return_type.lexeme); - // TODO maybe solve when custom? + std.debug.warn("resolved fn {} type: {}\n", decl.func_name.lexeme, ret_type); - std.debug.warn("fn {} type: {}\n", decl.func_name.lexeme, ret_type); + var parameters = comp.TypeList.init(self.allocator); + for (decl.params.toSlice()) |param| { + var param_type = self.resolveGlobalType(ctx, param.typ.lexeme); + if (param_type == null) continue; + try parameters.append(param_type.?); + } - // ctx.insertFn(decl.name.lexeme, ret_type); + // TODO symbols and scope resolution, that's + // its own can of worms + var symbols = comp.SymbolTable.init(self.allocator); + + // TODO go through body, resolve statements, expressions + // and everything else + + if (ret_type != null and parameters.len == decl.params.len) { + try ctx.insertFn(decl, ret_type.?, parameters, symbols); + } }, + .Struct => |struc| { + self.setErrToken(struc.name); + self.setErrContext("struct {}", struc.name.lexeme); + + var types = comp.TypeList.init(self.allocator); + + for (struc.fields.toSlice()) |field| { + self.setErrToken(field.name); + var field_type = self.resolveGlobalType(ctx, field.typ.lexeme); + if (field_type == null) continue; + try types.append(field_type.?); + } + + // only determine struct as fully resolved + // when length of declared types == length of resolved types + + // we don't return type errors from the main loop so we can + // keep going and find more type errors + if (types.len == struc.fields.len) + try ctx.insertStruct(struc, types); + }, + + // TODO enums are u32 + //.Enum => {}, + // TODO infer type of expr in const //.ConstDecl => {}, - //.Struct => {}, - //.Enum => {}, + else => unreachable, } } - pub fn pass(self: *@This(), root: *ast.Node) comp.CompilationContext { + pub fn pass(self: *@This(), root: *ast.Node) !comp.CompilationContext { var ctx = comp.CompilationContext.init(self.allocator); var slice = root.Root.toSlice(); for (slice) |_, idx| { - self.nodePass(&ctx, &slice[idx]); + try self.nodePass(&ctx, &slice[idx]); } return ctx;