const std = @import("std"); pub const CompilationError = error{TypeError}; pub const SymbolTable = std.hash_map.StringHashMap(SymbolData); pub const SymbolUnderlyingTypeEnum = enum { Integer32, Integer64, Bool, CustomType, }; pub const SymbolUnderlyingType = union(SymbolUnderlyingTypeEnum) { Integer32: void, Integer64: void, Bool: void, CustomType: []const u8, }; // functions, for our purposes, other than symbols, have: // - a return type // - TODO parameters pub const FunctionSymbol = struct { return_type: SymbolUnderlyingType, /// Parameters for a function are also a table instead of an ArrayList /// because we want to resolve identifiers to them. parameters: SymbolTable, 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 { // 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; return null; } }; // structs are hashmaps pointing to SymbolUnderlyingType pub const UnderlyingTypeMap = std.hash_map.StringHashMap(SymbolUnderlyingType); // enums have lists of identifiers pub const IdentifierList = std.ArrayList([]const u8); // TODO const pub const SymbolType = enum { Function, Struct, Enum, Variable, }; pub const SymbolData = union(SymbolType) { Function: FunctionSymbol, Struct: UnderlyingTypeMap, Enum: IdentifierList, // variables (parameters and variables), for the type system // only have types Variable: 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 .CustomType; } /// 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 = {} }, .CustomType => SymbolUnderlyingType{ .CustomType = typ_ident }, }; } };