Compare commits
5 commits
8065d0d905
...
fc9f5d9ce0
Author | SHA1 | Date | |
---|---|---|---|
fc9f5d9ce0 | |||
64e39a6f1e | |||
97c2437d97 | |||
e91c2dfdaf | |||
ae27995eb6 |
7 changed files with 184 additions and 40 deletions
|
@ -3,7 +3,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
fn f() i32 {
|
fn f() i32 {
|
||||||
var a = 3;
|
//var a = 3;
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ pub const StructExpr = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const GetExpr = struct {
|
pub const GetExpr = struct {
|
||||||
struc: *Expr,
|
target: *Expr,
|
||||||
name: Token,
|
name: Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -272,7 +272,7 @@ pub fn printExpr(expr: *const Expr) void {
|
||||||
|
|
||||||
.Get => |get| {
|
.Get => |get| {
|
||||||
warn("(");
|
warn("(");
|
||||||
printExpr(get.struc);
|
printExpr(get.target);
|
||||||
warn(".{})", get.name.lexeme);
|
warn(".{})", get.name.lexeme);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -369,6 +369,19 @@ fn prettyType(typ: SymbolUnderlyingType) []const u8 {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn printScope(scope: *Scope, ident: usize) void {
|
||||||
|
print(ident, "scope at addr {}\n", &scope);
|
||||||
|
|
||||||
|
var it = scope.env.iterator();
|
||||||
|
while (it.next()) |kv| {
|
||||||
|
print(ident, "sym: {}, typ: {}\n", kv.key, prettyType(kv.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (scope.children.toSlice()) |child| {
|
||||||
|
printScope(scope, ident + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn printContext(ctx: CompilationContext) void {
|
pub fn printContext(ctx: CompilationContext) void {
|
||||||
var it = ctx.symbol_table.iterator();
|
var it = ctx.symbol_table.iterator();
|
||||||
|
|
||||||
|
@ -389,6 +402,10 @@ pub fn printContext(ctx: CompilationContext) void {
|
||||||
prettyType(param_kv.value),
|
prettyType(param_kv.value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// go through scopes
|
||||||
|
std.debug.warn("scope info:\n");
|
||||||
|
printScope(fn_sym.scope, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
.Struct => |typemap| {
|
.Struct => |typemap| {
|
||||||
|
|
|
@ -121,10 +121,9 @@ pub const Codegen = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
.Get => |get| {
|
.Get => |get| {
|
||||||
// TODO rename get.struc to get.target
|
var target = get.target.*;
|
||||||
var struc = get.struc.*;
|
|
||||||
|
|
||||||
switch (struc) {
|
switch (target) {
|
||||||
.Variable => |vari| {
|
.Variable => |vari| {
|
||||||
// first, we must check if the target is a type
|
// first, we must check if the target is a type
|
||||||
// and emit accordingly
|
// and emit accordingly
|
||||||
|
@ -158,7 +157,7 @@ pub const Codegen = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
else => {
|
else => {
|
||||||
std.debug.warn("Invalid get target: {}\n", ast.ExprType(struc));
|
std.debug.warn("Invalid get target: {}\n", ast.ExprType(target));
|
||||||
return CompileError.EmitError;
|
return CompileError.EmitError;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,15 +29,41 @@ pub const SymbolUnderlyingType = union(SymbolUnderlyingTypeEnum) {
|
||||||
Enum: []const u8,
|
Enum: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Scope = std.StringHashMap(SymbolUnderlyingType);
|
pub const Environment = std.StringHashMap(SymbolUnderlyingType);
|
||||||
pub const Environment = struct {
|
pub const ScopeList = std.ArrayList(*Scope);
|
||||||
parent: *Environment,
|
pub const Scope = struct {
|
||||||
scope: Scope,
|
parent: ?*Scope,
|
||||||
|
env: Environment,
|
||||||
|
|
||||||
|
/// Used for debug information.
|
||||||
|
children: ScopeList,
|
||||||
|
|
||||||
|
allocator: *std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn create(allocator: *std.mem.Allocator, parent: ?*Scope) !*Scope {
|
||||||
|
var scope = try allocator.create(Scope);
|
||||||
|
|
||||||
|
scope.* = Scope{
|
||||||
|
.parent = parent,
|
||||||
|
.env = Environment.init(allocator),
|
||||||
|
.children = ScopeList.init(allocator),
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createChild(self: *@This()) !*Scope {
|
||||||
|
return try @This().create(self.allocator, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const @This()) void {
|
||||||
|
self.env.deinit();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// functions, for our purposes, other than symbols, have:
|
// functions, for our purposes, other than symbols, have:
|
||||||
// - a return type
|
// - a return type
|
||||||
// - TODO parameters
|
// - parameters
|
||||||
pub const FunctionSymbol = struct {
|
pub const FunctionSymbol = struct {
|
||||||
decl: ast.FnDecl,
|
decl: ast.FnDecl,
|
||||||
return_type: SymbolUnderlyingType,
|
return_type: SymbolUnderlyingType,
|
||||||
|
@ -45,7 +71,7 @@ pub const FunctionSymbol = struct {
|
||||||
/// Parameters for a function are also a table instead of an ArrayList
|
/// Parameters for a function are also a table instead of an ArrayList
|
||||||
/// because we want to resolve identifiers to them.
|
/// because we want to resolve identifiers to them.
|
||||||
parameters: UnderlyingTypeMap,
|
parameters: UnderlyingTypeMap,
|
||||||
env: *Environment,
|
scope: *Scope,
|
||||||
|
|
||||||
/// Find a given identifier in the function. Can resolve to either a parameter
|
/// Find a given identifier in the function. Can resolve to either a parameter
|
||||||
pub fn findSymbol(self: *const @This(), identifier: []const u8) ?SymbolData {
|
pub fn findSymbol(self: *const @This(), identifier: []const u8) ?SymbolData {
|
||||||
|
@ -98,6 +124,9 @@ pub const CompilationContext = struct {
|
||||||
allocator: *std.mem.Allocator,
|
allocator: *std.mem.Allocator,
|
||||||
symbol_table: SymbolTable,
|
symbol_table: SymbolTable,
|
||||||
|
|
||||||
|
current_function: ?*FunctionSymbol = null,
|
||||||
|
current_scope: ?*Scope = null,
|
||||||
|
|
||||||
pub fn init(allocator: *std.mem.Allocator) CompilationContext {
|
pub fn init(allocator: *std.mem.Allocator) CompilationContext {
|
||||||
return CompilationContext{
|
return CompilationContext{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
|
@ -105,6 +134,30 @@ pub const CompilationContext = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new scope out of the current one and set it as the current.
|
||||||
|
pub fn bumpScope(self: *@This()) !void {
|
||||||
|
if (self.current_scope == null) {
|
||||||
|
@panic("can't bump scope from null");
|
||||||
|
}
|
||||||
|
|
||||||
|
var child = try self.current_scope.?.createChild();
|
||||||
|
self.current_scope = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a given scope as the current scope.
|
||||||
|
pub fn setScope(self: *@This(), scope: *Scope) void {
|
||||||
|
self.current_scope = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "dump" the current scope, making the new current scope be its parent.
|
||||||
|
pub fn dumpScope(self: *@This()) void {
|
||||||
|
if (self.current_scope == null) {
|
||||||
|
@panic("can't dump scope from null");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_scope = self.current_scope.?.parent;
|
||||||
|
}
|
||||||
|
|
||||||
/// Solve a given type as a string into a SymbolUnderlyingTypeEnum
|
/// Solve a given type as a string into a SymbolUnderlyingTypeEnum
|
||||||
/// This does not help if you want a full SymbolUnderlyingType, use
|
/// This does not help if you want a full SymbolUnderlyingType, use
|
||||||
/// solveType() for that.
|
/// solveType() for that.
|
||||||
|
@ -152,7 +205,7 @@ pub const CompilationContext = struct {
|
||||||
decl: ast.FnDecl,
|
decl: ast.FnDecl,
|
||||||
ret_type: SymbolUnderlyingType,
|
ret_type: SymbolUnderlyingType,
|
||||||
param_types: TypeList,
|
param_types: TypeList,
|
||||||
symbols: SymbolTable,
|
scope: *Scope,
|
||||||
) !void {
|
) !void {
|
||||||
var type_map = UnderlyingTypeMap.init(self.allocator);
|
var type_map = UnderlyingTypeMap.init(self.allocator);
|
||||||
|
|
||||||
|
@ -165,7 +218,7 @@ pub const CompilationContext = struct {
|
||||||
.decl = decl,
|
.decl = decl,
|
||||||
.return_type = ret_type,
|
.return_type = ret_type,
|
||||||
.parameters = type_map,
|
.parameters = type_map,
|
||||||
.symbols = symbols,
|
.scope = scope,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,11 +320,11 @@ pub const Parser = struct {
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mkGet(self: *@This(), struc: *Expr, name: Token) !*Expr {
|
fn mkGet(self: *@This(), target: *Expr, name: Token) !*Expr {
|
||||||
var expr = try self.allocator.create(Expr);
|
var expr = try self.allocator.create(Expr);
|
||||||
expr.* = Expr{
|
expr.* = Expr{
|
||||||
.Get = ast.GetExpr{
|
.Get = ast.GetExpr{
|
||||||
.struc = struc,
|
.target = target,
|
||||||
.name = name,
|
.name = name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -906,11 +906,11 @@ pub const Parser = struct {
|
||||||
return self.doError("can not initialize struct field");
|
return self.doError("can not initialize struct field");
|
||||||
},
|
},
|
||||||
|
|
||||||
.Equal => return try self.mkSet(get.struc, get.name, value),
|
.Equal => return try self.mkSet(get.target, get.name, value),
|
||||||
|
|
||||||
.PlusEqual, .MinusEqual, .StarEqual, .SlashEqual => {
|
.PlusEqual, .MinusEqual, .StarEqual, .SlashEqual => {
|
||||||
return try self.mkSet(
|
return try self.mkSet(
|
||||||
get.struc,
|
get.target,
|
||||||
get.name,
|
get.name,
|
||||||
try self.mkBinary(expr, binop.?, value),
|
try self.mkBinary(expr, binop.?, value),
|
||||||
);
|
);
|
||||||
|
|
117
src/types.zig
117
src/types.zig
|
@ -97,8 +97,8 @@ pub const TypeSolver = struct {
|
||||||
) anyerror!SymbolUnderlyingType {
|
) anyerror!SymbolUnderlyingType {
|
||||||
switch (expr.*) {
|
switch (expr.*) {
|
||||||
.Binary => |binary| {
|
.Binary => |binary| {
|
||||||
var left_type = self.resolveExprType(ctx, binary.left);
|
var left_type = try self.resolveExprType(ctx, binary.left);
|
||||||
var right_type = self.resolveExprType(ctx, binary.right);
|
var right_type = try self.resolveExprType(ctx, binary.right);
|
||||||
|
|
||||||
return switch (binary.op) {
|
return switch (binary.op) {
|
||||||
// all numeric operations return numeric types
|
// all numeric operations return numeric types
|
||||||
|
@ -114,7 +114,7 @@ pub const TypeSolver = struct {
|
||||||
|
|
||||||
// for now, unary operators only have .Not
|
// for now, unary operators only have .Not
|
||||||
.Unary => |unary| {
|
.Unary => |unary| {
|
||||||
var right_type = self.resolveExprType(ctx, unary.right);
|
var right_type = try self.resolveExprType(ctx, unary.right);
|
||||||
return switch (unary.op) {
|
return switch (unary.op) {
|
||||||
.Negate => right_type,
|
.Negate => right_type,
|
||||||
.Not => right_type,
|
.Not => right_type,
|
||||||
|
@ -159,9 +159,43 @@ pub const TypeSolver = struct {
|
||||||
return func_sym.return_type;
|
return func_sym.return_type;
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO variable resolution
|
// TODO analysis for .Variable
|
||||||
|
|
||||||
// TODO Get (for structs and enums)
|
.Get => |get| {
|
||||||
|
var target = get.target.*;
|
||||||
|
if (ast.ExprType(target) != .Variable) {
|
||||||
|
std.debug.warn("Expected Variable as get target, got {}\n", ast.ExprType(target));
|
||||||
|
return CompileError.TypeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lexeme = target.Variable.lexeme;
|
||||||
|
var global_typ_opt = self.resolveGlobalType(ctx, lexeme);
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - name resolution for when global_typ is null + analysis of
|
||||||
|
// the name's type
|
||||||
|
// - analysis for structs
|
||||||
|
|
||||||
|
if (global_typ_opt == null) @panic("TODO name resolution");
|
||||||
|
|
||||||
|
var global_typ = global_typ_opt.?;
|
||||||
|
|
||||||
|
switch (global_typ) {
|
||||||
|
|
||||||
|
// TODO we need to fetch the given
|
||||||
|
// struct field (on get.name) type and return it
|
||||||
|
.Struct => @panic("TODO analysis of struct"),
|
||||||
|
.Enum => return global_typ,
|
||||||
|
else => {
|
||||||
|
std.debug.warn(
|
||||||
|
"Expected Struct/Enum as get target, got {}\n",
|
||||||
|
comp.SymbolUnderlyingTypeEnum(global_typ),
|
||||||
|
);
|
||||||
|
|
||||||
|
return CompileError.TypeError;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
else => {
|
else => {
|
||||||
std.debug.warn("TODO resolve expr {}\n", ast.ExprType(expr.*));
|
std.debug.warn("TODO resolve expr {}\n", ast.ExprType(expr.*));
|
||||||
|
@ -174,14 +208,15 @@ pub const TypeSolver = struct {
|
||||||
self: *@This(),
|
self: *@This(),
|
||||||
ctx: *comp.CompilationContext,
|
ctx: *comp.CompilationContext,
|
||||||
stmt: ast.Stmt,
|
stmt: ast.Stmt,
|
||||||
) !void {
|
) anyerror!void {
|
||||||
switch (stmt) {
|
switch (stmt) {
|
||||||
|
|
||||||
// There are no side-effects to the type system when the statement
|
// There are no side-effects to the type system when the statement
|
||||||
// is just an expression or a println. we just resolve it
|
// is just an expression or a println. we just resolve it
|
||||||
// to ensure we dont have type errors.
|
// to ensure we dont have type errors.
|
||||||
.Expr => |expr_ptr| try self.resolveExprType(ctx, expr_ptr),
|
.Expr, .Println => |expr_ptr| {
|
||||||
.Println => |expr_ptr| try self.resolveExprType(ctx, expr_ptr),
|
_ = try self.resolveExprType(ctx, expr_ptr);
|
||||||
|
},
|
||||||
|
|
||||||
// VarDecl means we check the type of the expression and
|
// VarDecl means we check the type of the expression and
|
||||||
// insert it into the context, however we need to know a pointer
|
// insert it into the context, however we need to know a pointer
|
||||||
|
@ -189,21 +224,49 @@ pub const TypeSolver = struct {
|
||||||
// so it should be implicit into the context.
|
// so it should be implicit into the context.
|
||||||
.VarDecl => @panic("TODO vardecl"),
|
.VarDecl => @panic("TODO vardecl"),
|
||||||
|
|
||||||
// If create two scopes for each branch of the if
|
|
||||||
.If => @panic("TODO ifstmt"),
|
|
||||||
|
|
||||||
// Loop (creates 1 scope) asserts that the expression
|
|
||||||
// type is a bool
|
|
||||||
.Loop => @panic("TODO loop"),
|
|
||||||
|
|
||||||
// For (creates 1 scope) receives arrays, which we dont have yet
|
|
||||||
.For => @panic("TODO for"),
|
|
||||||
|
|
||||||
// Returns dont cause any type system things as they deal with
|
// Returns dont cause any type system things as they deal with
|
||||||
// values, however, we must ensure that the expression type
|
// values, however, we must ensure that the expression type
|
||||||
// matches the function type (must fetch from context, or we could
|
// matches the function type (must fetch from context, or we could
|
||||||
// pull a hack with err contexts, lol)
|
// pull a hack with err contexts, lol)
|
||||||
.Return => @panic("TODO return"),
|
.Return => |ret| {
|
||||||
|
var ret_stmt_type = try self.resolveExprType(ctx, ret.value);
|
||||||
|
// TODO check if ret_stmt_type == ctx.cur_function.return_type
|
||||||
|
},
|
||||||
|
|
||||||
|
// If create two scopes for each branch of the if
|
||||||
|
.If => |ifstmt| {
|
||||||
|
_ = try self.resolveExprType(ctx, ifstmt.condition);
|
||||||
|
|
||||||
|
// TODO assert condition's type is bool
|
||||||
|
|
||||||
|
// TODO bump-dump scope
|
||||||
|
for (ifstmt.then_branch.toSlice()) |then_stmt| {
|
||||||
|
try self.stmtPass(ctx, then_stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ifstmt.else_branch) |else_branch| {
|
||||||
|
// TODO bump-dump scope
|
||||||
|
for (else_branch.toSlice()) |else_stmt| {
|
||||||
|
try self.stmtPass(ctx, else_stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Loop (creates 1 scope) asserts that the expression
|
||||||
|
// type is a bool
|
||||||
|
.Loop => |loop| {
|
||||||
|
if (loop.condition) |cond|
|
||||||
|
// TODO assert condition's type is bool
|
||||||
|
_ = try self.resolveExprType(ctx, cond);
|
||||||
|
|
||||||
|
// TODO bump-dump scope
|
||||||
|
for (loop.then_branch.toSlice()) |then_stmt| {
|
||||||
|
try self.stmtPass(ctx, then_stmt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// For (creates 1 scope) receives arrays, which we dont have yet
|
||||||
|
.For => @panic("TODO for"),
|
||||||
|
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
}
|
}
|
||||||
|
@ -233,12 +296,24 @@ pub const TypeSolver = struct {
|
||||||
try parameters.append(param_type.?);
|
try parameters.append(param_type.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO scopes: bump scope
|
// for a function, we always create a new root scope for it
|
||||||
|
// and force-set it into the current context
|
||||||
|
var scope = try comp.Scope.create(self.allocator, null);
|
||||||
|
errdefer scope.deinit();
|
||||||
|
|
||||||
|
// we must always start from a null current scope,
|
||||||
|
// functions inside functions are not allowed
|
||||||
|
std.debug.assert(ctx.current_scope == null);
|
||||||
|
ctx.setScope(scope);
|
||||||
|
|
||||||
for (decl.body.toSlice()) |stmt| {
|
for (decl.body.toSlice()) |stmt| {
|
||||||
try self.stmtPass(ctx, stmt);
|
try self.stmtPass(ctx, stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// it should be null when we dump from a function. always
|
||||||
|
ctx.dumpScope();
|
||||||
|
std.debug.assert(ctx.current_scope == null);
|
||||||
|
|
||||||
// TODO scopes: down scope
|
// TODO scopes: down scope
|
||||||
|
|
||||||
// TODO symbols and scope resolution, that's
|
// TODO symbols and scope resolution, that's
|
||||||
|
@ -249,7 +324,7 @@ pub const TypeSolver = struct {
|
||||||
// and everything else
|
// and everything else
|
||||||
|
|
||||||
if (ret_type != null and parameters.len == decl.params.len) {
|
if (ret_type != null and parameters.len == decl.params.len) {
|
||||||
try ctx.insertFn(decl, ret_type.?, parameters, symbols);
|
try ctx.insertFn(decl, ret_type.?, parameters, scope);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue