185 lines
5.5 KiB
Zig
185 lines
5.5 KiB
Zig
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,
|
|
|
|
// opaque unsolved identifier
|
|
OpaqueType,
|
|
|
|
// solved custom type
|
|
Struct,
|
|
Enum,
|
|
};
|
|
|
|
pub const SymbolUnderlyingType = union(SymbolUnderlyingTypeEnum) {
|
|
Integer32: void,
|
|
Integer64: void,
|
|
Bool: void,
|
|
OpaqueType: []const u8,
|
|
|
|
Struct: []const u8,
|
|
Enum: []const u8,
|
|
};
|
|
|
|
// functions, for our purposes, other than symbols, have:
|
|
// - a return type
|
|
// - TODO parameters
|
|
pub const FunctionSymbol = struct {
|
|
decl: ast.FnDecl,
|
|
return_type: SymbolUnderlyingType,
|
|
|
|
/// Parameters for a function are also a table instead of an ArrayList
|
|
/// because we want to resolve identifiers to them.
|
|
parameters: UnderlyingTypeMap,
|
|
symbols: SymbolTable,
|
|
|
|
/// Find a given identifier in the function. Can resolve to either a parameter
|
|
pub fn findSymbol(self: *const @This(), identifier: []const u8) ?SymbolData {
|
|
// try to find it on overall variable symbols
|
|
|
|
// 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;
|
|
}
|
|
};
|
|
|
|
// structs are hashmaps pointing to SymbolUnderlyingType
|
|
pub const UnderlyingTypeMap = std.hash_map.StringHashMap(SymbolUnderlyingType);
|
|
|
|
// enums have maps from fields to u32s
|
|
pub const IdentifierMap = std.StringHashMap(u32);
|
|
|
|
// TODO const
|
|
pub const SymbolType = enum {
|
|
Function,
|
|
Struct,
|
|
Enum,
|
|
Variable,
|
|
Const,
|
|
};
|
|
|
|
pub const SymbolData = union(SymbolType) {
|
|
Function: FunctionSymbol,
|
|
Struct: UnderlyingTypeMap,
|
|
Enum: IdentifierMap,
|
|
|
|
// variables (parameters and variables), for the type system
|
|
// only have types
|
|
Variable: SymbolUnderlyingType,
|
|
Const: SymbolUnderlyingType,
|
|
};
|
|
|
|
const builtin_type_identifiers = [_][]const u8{ "i32", "i64", "bool" };
|
|
|
|
const builtin_types = [_]SymbolUnderlyingTypeEnum{ .Integer32, .Integer64, .Bool };
|
|
|
|
/// Represents the context for a full compiler run.
|
|
/// This is used to manage the symbol table for the compilation unit, etc.
|
|
pub const CompilationContext = struct {
|
|
allocator: *std.mem.Allocator,
|
|
symbol_table: SymbolTable,
|
|
|
|
pub fn init(allocator: *std.mem.Allocator) CompilationContext {
|
|
return CompilationContext{
|
|
.allocator = allocator,
|
|
.symbol_table = SymbolTable.init(allocator),
|
|
};
|
|
}
|
|
|
|
/// Solve a given type as a string into a SymbolUnderlyingTypeEnum
|
|
/// This does not help if you want a full SymbolUnderlyingType, use
|
|
/// solveType() for that.
|
|
pub fn solveTypeEnum(
|
|
self: *@This(),
|
|
typ_ident: []const u8,
|
|
) SymbolUnderlyingTypeEnum {
|
|
inline for (builtin_type_identifiers) |typ, idx| {
|
|
if (std.mem.eql(u8, typ, typ_ident)) return builtin_types[idx];
|
|
}
|
|
|
|
return .OpaqueType;
|
|
}
|
|
|
|
/// Solve a given type string into a full fleged SymbolUnderlyingType.
|
|
/// This always works, since resolution into struct/enum custom types
|
|
/// become the fallback.
|
|
pub fn solveType(
|
|
self: *@This(),
|
|
typ_ident: []const u8,
|
|
) SymbolUnderlyingType {
|
|
const typ_enum_val = self.solveTypeEnum(typ_ident);
|
|
|
|
return switch (typ_enum_val) {
|
|
.Integer32 => SymbolUnderlyingType{ .Integer32 = {} },
|
|
.Integer64 => SymbolUnderlyingType{ .Integer64 = {} },
|
|
.Bool => SymbolUnderlyingType{ .Bool = {} },
|
|
.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{
|
|
.decl = decl,
|
|
.return_type = ret_type,
|
|
.parameters = type_map,
|
|
.symbols = symbols,
|
|
},
|
|
});
|
|
}
|
|
|
|
pub fn insertEnum(self: *@This(), enu: ast.Enum) !void {
|
|
var ident_map = IdentifierMap.init(self.allocator);
|
|
errdefer ident_map.deinit();
|
|
|
|
// TODO change this when enums get support for custom values
|
|
for (enu.fields.toSlice()) |token, idx| {
|
|
_ = try ident_map.put(token.lexeme, @intCast(u32, idx));
|
|
}
|
|
|
|
_ = try self.symbol_table.put(enu.name.lexeme, SymbolData{
|
|
.Enum = ident_map,
|
|
});
|
|
}
|
|
|
|
pub fn insertConst(self: *@This(), constdecl: ast.SingleConst, typ: SymbolUnderlyingType) !void {
|
|
_ = try self.symbol_table.put(constdecl.name.lexeme, SymbolData{ .Const = typ });
|
|
}
|
|
};
|