166 lines
5.3 KiB
Zig
166 lines
5.3 KiB
Zig
const std = @import("std");
|
|
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 {
|
|
switch (node.*) {
|
|
.Root => unreachable,
|
|
.FnDecl => |decl| {
|
|
self.setErrToken(decl.return_type);
|
|
self.setErrContext("function {}", decl.func_name.lexeme);
|
|
var ret_type = self.resolveGlobalType(ctx, decl.return_type.lexeme);
|
|
|
|
std.debug.warn("resolved 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.?);
|
|
}
|
|
|
|
// 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 => {},
|
|
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
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| {
|
|
try self.nodePass(&ctx, &slice[idx]);
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
};
|