Compare commits

...

3 Commits

Author SHA1 Message Date
Luna 7def9abc5a add "variable metadata"
this is inserted in the analysis pass into a map from expr ptrs to
metadata in the compilation context itself, this enables codegen to
fetch that metadata with the expr pointer

the other approach was embedding it into the variable expr itself (as
seen by VariableExpr), but that causes a compiler crash
2019-09-27 17:08:01 -03:00
Luna 362fc7e3ef analysis: add name type resolution
this only adds resolution for the types. for the actual metadata, such
as e.g parameters, that isn't available, so codegen isn't helped by
this.
2019-09-27 15:12:30 -03:00
Luna 4346636cfa analysis: add check for call expr's arguments 2019-09-27 14:31:59 -03:00
5 changed files with 116 additions and 8 deletions

View File

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

View File

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

View File

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

View File

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

View File

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