diff --git a/examples/hello.ry b/examples/hello.ry index b8aec3a..3c0c53c 100644 --- a/examples/hello.ry +++ b/examples/hello.ry @@ -1,6 +1,9 @@ -// import std; +const ( + test_var = 1 + 3 +) fn f() i32 { + var a = 3; return 2; } @@ -8,10 +11,6 @@ fn f2() i32 { return f() + 2; } -const ( - piss = 1 + 3 -) - enum B { a b diff --git a/src/ast.zig b/src/ast.zig index c68eefe..cb166d1 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -97,9 +97,6 @@ pub const AssignExpr = struct { pub const ExprType = enum { Assign, - // vardecls as expressions is a hack - VarDecl, - Binary, Unary, Literal, @@ -112,11 +109,6 @@ pub const ExprType = enum { Set, }; -pub const VarDecl = struct { - assign: AssignExpr, - mutable: bool = false, -}; - pub const CallExpr = struct { callee: *Expr, paren: Token, @@ -148,7 +140,6 @@ pub const SetExpr = struct { pub const Expr = union(ExprType) { Assign: AssignExpr, - VarDecl: VarDecl, Binary: BinaryExpr, Unary: UnaryExpr, @@ -181,10 +172,16 @@ pub const ForStmt = struct { block: Block, }; +pub const VarDeclStmt = struct { + name: Token, + value: *Expr, +}; + pub const Stmt = union(enum) { Expr: *Expr, Println: *Expr, + VarDecl: VarDeclStmt, If: IfStmt, Loop: LoopStmt, For: ForStmt, @@ -261,6 +258,18 @@ pub const Stmt = union(enum) { return stmt; } + + pub fn mkVarDecl(allocator: *std.mem.Allocator, name: Token, value: *Expr) !*Stmt { + var stmt = try allocator.create(Stmt); + stmt.* = Stmt{ + .VarDecl = VarDeclStmt{ + .name = name, + .value = value, + }, + }; + + return stmt; + } }; pub const FieldList = std.ArrayList(StructField); diff --git a/src/ast_printer.zig b/src/ast_printer.zig index 0e9b549..82ecfde 100644 --- a/src/ast_printer.zig +++ b/src/ast_printer.zig @@ -239,18 +239,6 @@ pub fn printExpr(expr: *const Expr) void { .Variable => |token| std.debug.warn("{}", token.lexeme), - .VarDecl => |decl| { - if (decl.mutable) { - std.debug.warn("(mut "); - } else { - std.debug.warn("("); - } - - std.debug.warn("let {} ", decl.assign.name.lexeme); - printExpr(decl.assign.value); - std.debug.warn(")"); - }, - .Assign => |assign| { std.debug.warn("(set "); std.debug.warn("{} ", assign.name.lexeme); @@ -305,6 +293,12 @@ pub fn printStmt(ident: usize, stmt: *const Stmt) void { .Println => |expr| printSimpleOp("println", expr), .Expr => |expr| printExpr(expr), + .VarDecl => |decl| { + std.debug.warn("(let {} ", decl.name.lexeme); + printExpr(decl.value); + std.debug.warn(")"); + }, + .If => |ifstmt| { std.debug.warn("(if "); printExpr(ifstmt.condition); diff --git a/src/codegen.zig b/src/codegen.zig index 1be2e5a..012373c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -79,8 +79,6 @@ pub const Codegen = struct { // TODO VarDecl add things to the symbol table // TODO Assign modify symbol table - - // TODO Calls fetch symbol table, check arity of it at codegen level return switch (expr.*) { // TODO handle all literals, construct llvm values for them @@ -168,7 +166,6 @@ pub const Codegen = struct { .Call => |call| { const name = call.callee.*.Variable.lexeme; - //var sym = try self.ctx.fetchGlobalSymbol(func_name, .Function); var llvm_func = self.llvm_table.get(name); if (llvm_func == null) { @@ -176,7 +173,6 @@ pub const Codegen = struct { return CompileError.EmitError; } - // TODO args var args = LLVMValueList.init(self.allocator); errdefer args.deinit(); @@ -196,6 +192,22 @@ pub const Codegen = struct { ); }, + // TODO finish this + .Assign => |assign| { + // TODO find assign.name on the "parent context", we should have + // a way to do name resolution that is completely relative to + // where we currently are, and go up in scope. so that we find + // the LLVMValueRef. + + // we will also need to repeat the step for the type resolver + + //var typ = self.findCurrent(assign.name); + var assign_expr = try self.emitExpr(builder, assign.value); + + // TODO rm null + return llvm.LLVMBuildStore(builder, null, assign_expr); + }, + else => { std.debug.warn("Got unexpected expr {}\n", ast.ExprType(expr.*)); return CompileError.EmitError; @@ -290,7 +302,7 @@ pub const Codegen = struct { }, else => { - std.debug.warn("Got unexpected statement {}\n", stmt.*); + std.debug.warn("Got unexpected stmt {}\n", stmt.*); return CompileError.EmitError; }, } @@ -342,7 +354,6 @@ pub const Codegen = struct { llvm.LLVMPositionBuilderAtEnd(builder, entry); for (decl.body.toSlice()) |stmt| { - // TODO custom function context for us try self.emitStmt(builder, &stmt); } diff --git a/src/comp_ctx.zig b/src/comp_ctx.zig index 827c460..f223bf9 100644 --- a/src/comp_ctx.zig +++ b/src/comp_ctx.zig @@ -29,6 +29,12 @@ pub const SymbolUnderlyingType = union(SymbolUnderlyingTypeEnum) { Enum: []const u8, }; +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 // - TODO parameters @@ -39,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, - symbols: SymbolTable, + 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 { diff --git a/src/parsers.zig b/src/parsers.zig index 361fc66..04f9bf1 100644 --- a/src/parsers.zig +++ b/src/parsers.zig @@ -295,21 +295,6 @@ pub const Parser = struct { return expr; } - fn mkVarDecl(self: *@This(), name: Token, value: *Expr, mutable: bool) !*Expr { - var vardecl = try self.allocator.create(Expr); - vardecl.* = Expr{ - .VarDecl = ast.VarDecl{ - .assign = ast.AssignExpr{ - .name = name, - .value = value, - }, - .mutable = mutable, - }, - }; - - return vardecl; - } - fn mkCall(self: *@This(), callee: *Expr, paren: Token, args: ast.ExprList) !*Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ @@ -716,6 +701,7 @@ pub const Parser = struct { fn parseStmt(self: *@This()) anyerror!*Stmt { return switch (self.peek().typ) { + .Var => try self.parseVarDecl(), .If => try self.parseIfStmt(), .Loop => try self.parseLoop(), .For => try self.parseForStmt(), @@ -725,6 +711,14 @@ pub const Parser = struct { }; } + fn parseVarDecl(self: *@This()) !*Stmt { + _ = try self.consumeSingle(.Var); + var name = try self.consumeSingle(.Identifier); + _ = try self.consumeSingle(.Equal); + var value = try self.parseExpr(); + return try Stmt.mkVarDecl(self.allocator, name, value); + } + /// Parse a list of statements. fn parseBlock(self: *@This()) !*Node { var stmts = try self.parseBlockInternal(ast.StmtList); @@ -840,8 +834,8 @@ pub const Parser = struct { fn parseAssignment(self: *@This()) anyerror!*Expr { // there can be two assignments coming out of this function: - // - a mutable/immutable variable declaration with := - // - an assignment to a variable with =, +=, -= + // - an assignment to a variable with = + // - an update to a variable with +=, -= // one is a statement, other is an expression. since the normal result // of this is an Expr, we wrap variable assignments in an Expr as well. @@ -855,7 +849,7 @@ pub const Parser = struct { var expr = try self.parseOr(); if (self.compareAnyOf(&[_]TokenType{ - .ColonEqual, .Equal, .PlusEqual, .MinusEqual, .StarEqual, + .Equal, .PlusEqual, .MinusEqual, .StarEqual, .SlashEqual, })) { return try self.finishAssignment(expr, mutable); @@ -871,8 +865,7 @@ pub const Parser = struct { var value = try self.parseAssignment(); // expr can be a (Variable|Set) - // op_tok can be one of three categories: - // - ColonEqual, parses to VarDecl (only when expr = Variable) + // op_tok can be one of two categories: // - Equal, parses to (Assign|Set) // - Inplace (+=, -=, *=, /=), parses to (Assign|Set) @@ -893,7 +886,6 @@ pub const Parser = struct { switch (expr.*) { .Variable => { switch (op_tok.typ) { - .ColonEqual => return try self.mkVarDecl(expr.Variable, value, mutable), .Equal => return try self.mkAssign(expr.Variable, value), .PlusEqual, .MinusEqual, .StarEqual, .SlashEqual => { @@ -909,6 +901,7 @@ pub const Parser = struct { .Get => |get| { switch (op_tok.typ) { + // TODO remove .ColonEqual from language .ColonEqual => { return self.doError("can not initialize struct field"); }, diff --git a/src/scanners.zig b/src/scanners.zig index 5271235..24f2116 100644 --- a/src/scanners.zig +++ b/src/scanners.zig @@ -54,6 +54,7 @@ const keywords = [_][]const u8{ "pub", "and", "or", + "var", }; const keyword_ttypes = [_]TokenType{ @@ -86,9 +87,12 @@ const keyword_ttypes = [_]TokenType{ .Pub, .And, .Or, + .Var, }; fn getKeyword(keyword: []const u8) ?TokenType { + std.debug.assert(keywords.len == keyword_ttypes.len); + for (keywords) |kw, idx| { if (std.mem.eql(u8, keyword, kw)) { return keyword_ttypes[idx]; diff --git a/src/tokens.zig b/src/tokens.zig index 1df9b37..75aa8f9 100644 --- a/src/tokens.zig +++ b/src/tokens.zig @@ -77,6 +77,7 @@ pub const TokenType = enum { Println, Pub, + Var, EOF, }; diff --git a/src/types.zig b/src/types.zig index 8afa4c3..0015b2c 100644 --- a/src/types.zig +++ b/src/types.zig @@ -170,6 +170,45 @@ pub const TypeSolver = struct { } } + pub fn stmtPass( + self: *@This(), + ctx: *comp.CompilationContext, + stmt: ast.Stmt, + ) !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 => |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 + // to where we are, scope-wise, we don't have that info here, + // 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 => @panic("TODO return"), + + else => unreachable, + } + } + pub fn nodePass( self: *@This(), ctx: *comp.CompilationContext, @@ -194,6 +233,14 @@ pub const TypeSolver = struct { try parameters.append(param_type.?); } + // TODO scopes: bump scope + + for (decl.body.toSlice()) |stmt| { + try self.stmtPass(ctx, stmt); + } + + // TODO scopes: down scope + // TODO symbols and scope resolution, that's // its own can of worms var symbols = comp.SymbolTable.init(self.allocator);