diff --git a/examples/hello.ry b/examples/hello.ry index 6e79137..9be2bf2 100644 --- a/examples/hello.ry +++ b/examples/hello.ry @@ -42,6 +42,10 @@ fn multwo(num: i32, double_flag: bool) i32 { } } +fn multwo_with_one(b: i32) i32 { + return multwo(b, false) + b; +} + fn add(a: i32, b: i32) i32 { return 69 + 69; } diff --git a/src/analysis.zig b/src/analysis.zig index 31e9762..f5c4897 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -186,6 +186,8 @@ pub const TypeSolver = struct { // TODO make return type optional and so, skip exprs that // fail to be fully resolved, instead of returning CompileError + + // TODO make the expr ptr a const since we want to implicit cast things pub fn resolveExprType( self: *@This(), ctx: *comp.CompilationContext, @@ -200,7 +202,6 @@ pub const TypeSolver = struct { // all numeric operations return numeric types .Add, .Sub, .Mul, .Div, .Mod => left_type, - // TODO check left and right as numeric .Greater, .GreaterEqual, .Less, .LessEqual => blk: { try self.expectSymUnTypeNumeric(left_type); try self.expectSymUnTypeNumeric(right_type); @@ -226,7 +227,8 @@ pub const TypeSolver = struct { return switch (literal) { .Bool => SymbolUnderlyingType{ .Bool = {} }, - // TODO determine its i64 depending of parseInt results + // TODO recast Integer32 as Integer64 if the type we're + // checking into is Integer64, but not the other way. .Integer32 => SymbolUnderlyingType{ .Integer32 = {} }, .Integer64 => SymbolUnderlyingType{ .Integer64 = {} }, .Float => SymbolUnderlyingType{ .Double = {} }, @@ -256,13 +258,31 @@ pub const TypeSolver = struct { var symbol = try ctx.fetchGlobalSymbol(func_name, .Function); var func_sym = symbol.Function; - // TODO check parameter type mismatches between - // call.arguments and func_sym.parameters + for (call.arguments.toSlice()) |arg_expr, idx| { + var param_type = func_sym.parameter_list.at(idx); + var arg_type = try self.resolveExprType(ctx, &arg_expr); + + self.expectSymUnTypeEqual(arg_type, param_type) catch { + self.doError( + "Expected parameter {} to be {}, got {}", + idx, + @tagName(comp.SymbolUnderlyingTypeEnum(param_type)), + @tagName(comp.SymbolUnderlyingTypeEnum(arg_type)), + ); + + return CompileError.TypeError; + }; + } return func_sym.return_type; }, - // TODO analysis for .Variable + .Variable => |vari| { + self.setErrToken(vari); + var metadata = try ctx.resolveVarType(vari.lexeme); + try ctx.insertMetadata(expr, metadata); + return metadata.typ; + }, .Get => |get| { var target = get.target.*; diff --git a/src/ast.zig b/src/ast.zig index e1e3205..02093ed 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -141,6 +141,11 @@ pub const SetExpr = struct { value: *Expr, }; +pub const VariableExpr = struct { + tok: Token, + metadata: ?*comp.VariableMetadata = null, +}; + pub const Expr = union(ExprType) { Assign: AssignExpr, @@ -150,6 +155,7 @@ pub const Expr = union(ExprType) { Struct: StructExpr, Variable: Token, + Grouping: *Expr, Call: CallExpr, diff --git a/src/comp_ctx.zig b/src/comp_ctx.zig index 0a407e3..e50a5e9 100644 --- a/src/comp_ctx.zig +++ b/src/comp_ctx.zig @@ -1,7 +1,10 @@ const std = @import("std"); const ast = @import("ast.zig"); -pub const CompilationError = error{TypeError}; +pub const CompilationError = error{ + TypeError, + UnknownName, +}; pub const SymbolTable = std.hash_map.StringHashMap(SymbolData); pub const TypeList = std.ArrayList(SymbolUnderlyingType); @@ -76,6 +79,8 @@ 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: UnderlyingTypeMap, + parameter_list: TypeList, + scope: *Scope, /// Find a given identifier in the function. Can resolve to either a parameter @@ -123,6 +128,31 @@ const builtin_type_identifiers = [_][]const u8{ "i32", "i64", "bool" }; const builtin_types = [_]SymbolUnderlyingTypeEnum{ .Integer32, .Integer64, .Bool }; +const Using = enum { + Scope, + Function, +}; + +pub const VariableMetadata = struct { + typ: SymbolUnderlyingType, + + using: Using, + + from_scope: ?*Scope = null, + from_function: ?*FunctionSymbol = null, + + pub fn withScope(scope: *Scope, typ: SymbolUnderlyingType) VariableMetadata { + return VariableMetadata{ .typ = typ, .from_scope = scope, .using = .Scope }; + } + + pub fn withParam(func: *FunctionSymbol, typ: SymbolUnderlyingType) VariableMetadata { + return VariableMetadata{ .typ = typ, .from_function = func, .using = .Function }; + } +}; + +// TODO rm const? +pub const VariableMetadataMap = std.AutoHashMap(*const ast.Expr, VariableMetadata); + /// 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 { @@ -132,13 +162,21 @@ pub const CompilationContext = struct { cur_function: ?*FunctionSymbol = null, current_scope: ?*Scope = null, + metadata_map: VariableMetadataMap, + pub fn init(allocator: *std.mem.Allocator) CompilationContext { return CompilationContext{ .allocator = allocator, .symbol_table = SymbolTable.init(allocator), + .metadata_map = VariableMetadataMap.init(allocator), }; } + pub fn deinit(self: *@This()) void { + self.symbol_table.deinit(); + self.metadata_map.deinit(); + } + /// Create a new scope out of the current one and set it as the current. pub fn bumpScope(self: *@This(), scope_id: ?[]const u8) !void { if (self.current_scope == null) { @@ -239,6 +277,7 @@ pub const CompilationContext = struct { .decl = decl, .return_type = ret_type, .parameters = type_map, + .parameter_list = param_types, .scope = scope, }, }); @@ -286,4 +325,43 @@ pub const CompilationContext = struct { return sym_kv.?.value; } + + fn resolveVarTypeInScope( + self: *@This(), + scope_opt: ?*Scope, + name: []const u8, + ) ?VariableMetadata { + if (scope_opt == null) return null; + var scope = scope_opt.?; + + var kv_opt = scope.env.get(name); + if (kv_opt) |kv| { + return VariableMetadata.withScope(scope, kv.value); + } else { + return self.resolveVarTypeInScope(scope.parent, name); + } + } + + /// Resolve a given name's type, assuming it is a variable. + pub fn resolveVarType(self: *@This(), name: []const u8) !VariableMetadata { + var var_type: ?VariableMetadata = null; + + if (self.current_scope) |scope| { + var_type = self.resolveVarTypeInScope(scope, name); + if (var_type) |typ| return typ; + } + + if (self.cur_function) |cur_function| { + var kv_opt = cur_function.parameters.get(name); + if (kv_opt) |kv| return VariableMetadata.withParam(cur_function, kv.value); + } + + std.debug.warn("Unknown name {}\n", name); + return CompilationError.UnknownName; + } + + pub fn insertMetadata(self: *@This(), ptr: *const ast.Expr, metadata: VariableMetadata) !void { + std.debug.assert(ast.ExprType(ptr.*) == .Variable); + _ = try self.metadata_map.put(ptr, metadata); + } }; diff --git a/src/parsers.zig b/src/parsers.zig index 3bb8572..026d11e 100644 --- a/src/parsers.zig +++ b/src/parsers.zig @@ -412,9 +412,9 @@ pub const Parser = struct { return expr; } - fn mkVariable(self: *Parser, variable: Token) !*ast.Expr { + fn mkVariable(self: *Parser, tok: Token) !*ast.Expr { var expr = try self.allocator.create(Expr); - expr.* = Expr{ .Variable = variable }; + expr.* = Expr{ .Variable = tok }; return expr; }