add type resolving for structs and fn params

- add a printer for the symbol table
 - add error contexts to type pass
This commit is contained in:
Luna 2019-09-25 00:26:30 -03:00
parent 8afab8e4ed
commit e2cca03d52
6 changed files with 266 additions and 22 deletions

View file

@ -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) {

View file

@ -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,
}
}
}

View file

@ -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);
}

View file

@ -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,
},
});
}
};

View file

@ -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);

View file

@ -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;