diff --git a/examples/hello.ry b/examples/hello.ry index 886e348..3c0c53c 100644 --- a/examples/hello.ry +++ b/examples/hello.ry @@ -3,7 +3,7 @@ const ( ) fn f() i32 { - //var a = 3; + var a = 3; return 2; } diff --git a/src/ast.zig b/src/ast.zig index 7a5c4cd..cb166d1 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -128,7 +128,7 @@ pub const StructExpr = struct { }; pub const GetExpr = struct { - target: *Expr, + struc: *Expr, name: Token, }; diff --git a/src/ast_printer.zig b/src/ast_printer.zig index ac42790..82ecfde 100644 --- a/src/ast_printer.zig +++ b/src/ast_printer.zig @@ -272,7 +272,7 @@ pub fn printExpr(expr: *const Expr) void { .Get => |get| { warn("("); - printExpr(get.target); + printExpr(get.struc); warn(".{})", get.name.lexeme); }, @@ -369,19 +369,6 @@ 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 { var it = ctx.symbol_table.iterator(); @@ -402,10 +389,6 @@ pub fn printContext(ctx: CompilationContext) void { prettyType(param_kv.value), ); } - - // go through scopes - std.debug.warn("scope info:\n"); - printScope(fn_sym.scope, 1); }, .Struct => |typemap| { diff --git a/src/codegen.zig b/src/codegen.zig index d481a7f..012373c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -121,9 +121,10 @@ pub const Codegen = struct { }, .Get => |get| { - var target = get.target.*; + // TODO rename get.struc to get.target + var struc = get.struc.*; - switch (target) { + switch (struc) { .Variable => |vari| { // first, we must check if the target is a type // and emit accordingly @@ -157,7 +158,7 @@ pub const Codegen = struct { }, else => { - std.debug.warn("Invalid get target: {}\n", ast.ExprType(target)); + std.debug.warn("Invalid get target: {}\n", ast.ExprType(struc)); return CompileError.EmitError; }, } diff --git a/src/comp_ctx.zig b/src/comp_ctx.zig index e645dd9..f223bf9 100644 --- a/src/comp_ctx.zig +++ b/src/comp_ctx.zig @@ -29,41 +29,15 @@ pub const SymbolUnderlyingType = union(SymbolUnderlyingTypeEnum) { Enum: []const u8, }; -pub const Environment = std.StringHashMap(SymbolUnderlyingType); -pub const ScopeList = std.ArrayList(*Scope); -pub const Scope = struct { - 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(); - } +pub const Scope = std.StringHashMap(SymbolUnderlyingType); +pub const Environment = struct { + parent: *Environment, + scope: Scope, }; // functions, for our purposes, other than symbols, have: // - a return type -// - parameters +// - TODO parameters pub const FunctionSymbol = struct { decl: ast.FnDecl, return_type: SymbolUnderlyingType, @@ -71,7 +45,7 @@ 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, - scope: *Scope, + env: *Environment, /// Find a given identifier in the function. Can resolve to either a parameter pub fn findSymbol(self: *const @This(), identifier: []const u8) ?SymbolData { @@ -124,9 +98,6 @@ pub const CompilationContext = struct { allocator: *std.mem.Allocator, symbol_table: SymbolTable, - current_function: ?*FunctionSymbol = null, - current_scope: ?*Scope = null, - pub fn init(allocator: *std.mem.Allocator) CompilationContext { return CompilationContext{ .allocator = allocator, @@ -134,30 +105,6 @@ 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 /// This does not help if you want a full SymbolUnderlyingType, use /// solveType() for that. @@ -205,7 +152,7 @@ pub const CompilationContext = struct { decl: ast.FnDecl, ret_type: SymbolUnderlyingType, param_types: TypeList, - scope: *Scope, + symbols: SymbolTable, ) !void { var type_map = UnderlyingTypeMap.init(self.allocator); @@ -218,7 +165,7 @@ pub const CompilationContext = struct { .decl = decl, .return_type = ret_type, .parameters = type_map, - .scope = scope, + .symbols = symbols, }, }); } diff --git a/src/parsers.zig b/src/parsers.zig index 2e278cc..04f9bf1 100644 --- a/src/parsers.zig +++ b/src/parsers.zig @@ -320,11 +320,11 @@ pub const Parser = struct { return expr; } - fn mkGet(self: *@This(), target: *Expr, name: Token) !*Expr { + fn mkGet(self: *@This(), struc: *Expr, name: Token) !*Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Get = ast.GetExpr{ - .target = target, + .struc = struc, .name = name, }, }; @@ -906,11 +906,11 @@ pub const Parser = struct { return self.doError("can not initialize struct field"); }, - .Equal => return try self.mkSet(get.target, get.name, value), + .Equal => return try self.mkSet(get.struc, get.name, value), .PlusEqual, .MinusEqual, .StarEqual, .SlashEqual => { return try self.mkSet( - get.target, + get.struc, get.name, try self.mkBinary(expr, binop.?, value), ); diff --git a/src/types.zig b/src/types.zig index dfe384b..0015b2c 100644 --- a/src/types.zig +++ b/src/types.zig @@ -97,8 +97,8 @@ pub const TypeSolver = struct { ) anyerror!SymbolUnderlyingType { switch (expr.*) { .Binary => |binary| { - var left_type = try self.resolveExprType(ctx, binary.left); - var right_type = try self.resolveExprType(ctx, binary.right); + var left_type = self.resolveExprType(ctx, binary.left); + var right_type = self.resolveExprType(ctx, binary.right); return switch (binary.op) { // all numeric operations return numeric types @@ -114,7 +114,7 @@ pub const TypeSolver = struct { // for now, unary operators only have .Not .Unary => |unary| { - var right_type = try self.resolveExprType(ctx, unary.right); + var right_type = self.resolveExprType(ctx, unary.right); return switch (unary.op) { .Negate => right_type, .Not => right_type, @@ -159,43 +159,9 @@ pub const TypeSolver = struct { return func_sym.return_type; }, - // TODO analysis for .Variable + // TODO variable resolution - .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; - }, - } - }, + // TODO Get (for structs and enums) else => { std.debug.warn("TODO resolve expr {}\n", ast.ExprType(expr.*)); @@ -208,15 +174,14 @@ pub const TypeSolver = struct { self: *@This(), ctx: *comp.CompilationContext, stmt: ast.Stmt, - ) anyerror!void { + ) !void { switch (stmt) { // There are no side-effects to the type system when the statement // is just an expression or a println. we just resolve it // to ensure we dont have type errors. - .Expr, .Println => |expr_ptr| { - _ = try self.resolveExprType(ctx, expr_ptr); - }, + .Expr => |expr_ptr| try self.resolveExprType(ctx, expr_ptr), + .Println => |expr_ptr| try self.resolveExprType(ctx, expr_ptr), // VarDecl means we check the type of the expression and // insert it into the context, however we need to know a pointer @@ -224,49 +189,21 @@ pub const TypeSolver = struct { // so it should be implicit into the context. .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 // values, however, we must ensure that the expression type // matches the function type (must fetch from context, or we could // pull a hack with err contexts, lol) - .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"), + .Return => @panic("TODO return"), else => unreachable, } @@ -296,24 +233,12 @@ pub const TypeSolver = struct { try parameters.append(param_type.?); } - // 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); + // TODO scopes: bump scope for (decl.body.toSlice()) |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 symbols and scope resolution, that's @@ -324,7 +249,7 @@ pub const TypeSolver = struct { // and everything else if (ret_type != null and parameters.len == decl.params.len) { - try ctx.insertFn(decl, ret_type.?, parameters, scope); + try ctx.insertFn(decl, ret_type.?, parameters, symbols); } },