diff --git a/examples/hello.v b/examples/hello.v deleted file mode 100644 index 46cc8fd..0000000 --- a/examples/hello.v +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println("hello world!"); -} diff --git a/src/ast.zig b/src/ast.zig index 4de4f03..c9d44e8 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -1,25 +1,103 @@ -const std = @import("std"); const tokens = @import("tokens.zig"); const Token = tokens.Token; -pub const NodeList = std.ArrayList(*Node); - -pub const NodeType = enum { - Root, - FnDecl, +pub const ExprType = enum { + Unary, + Binary, + Grouping, + Number, + Bool, + Nil, + String, }; -pub const FnDecl = struct { - func_name: []const u8, +pub const Expr = union(ExprType) { + Unary: UnaryExpr, + Binary: BinaryExpr, + Grouping: Grouping, + Number: Number, + Bool: Bool, + Nil: Nil, + String: String, }; -pub const Node = union(NodeType) { - Root: NodeList, - FnDecl: FnDecl, +pub const UnaryExpr = struct { + operator: Token, + right: *Expr, }; -pub fn mkRoot(allocator: *std.mem.Allocator) !*Node { - var node = try allocator.create(Node); - node.* = Node{ .Root = NodeList.init(allocator) }; - return node; +pub fn mkUnary(operator: Token, right: *Expr) Expr { + return Expr{ + .Unary = UnaryExpr{ + .operator = operator, + .right = right, + }, + }; } + +pub const BinaryExpr = struct { + left: *Expr, + operator: Token, + right: *Expr, +}; + +pub fn mkBinary(left: *Expr, operator: Token, right: *Expr) Expr { + return Expr{ + .Binary = BinaryExpr{ + .left = left, + .operator = operator, + .right = right, + }, + }; +} + +pub const Grouping = struct { + expression: *Expr, +}; + +pub fn mkGrouping(expression: *Expr) Expr { + return Expr{ + .Grouping = Grouping{ + .expression = expression, + }, + }; +} + +/// Represents the default number literals in V. +pub const NumberType = enum { + Integer32, + Integer64, + Unsigned32, + Unsigned64, + Float32, + Float64, +}; + +/// "translation" of V number types to Zig number types for nicer +/// representation. +pub const Number = union(NumberType) { + Integer32: i32, + Integer64: i64, + Unsigned32: u32, + Unsigned64: u64, + Float32: f32, + Float64: f64, +}; + +pub fn mkNum(comptime T: type, num: T) Expr { + var expr = switch (T) { + i32 => Expr{ .Number = Number{ .Integer32 = num } }, + i64 => Expr{ .Number = Number{ .Integer64 = num } }, + u32 => Expr{ .Number = Number{ .Unsigned32 = num } }, + u64 => Expr{ .Number = Number{ .Unsigned64 = num } }, + f32 => Expr{ .Number = Number{ .Float32 = num } }, + f64 => Expr{ .Number = Number{ .Float64 = num } }, + else => unreachable, + }; + + return expr; +} + +pub const Bool = bool; +pub const Nil = void; +pub const String = []u8; diff --git a/src/ast_printer.zig b/src/ast_printer.zig new file mode 100644 index 0000000..ae1a9b7 --- /dev/null +++ b/src/ast_printer.zig @@ -0,0 +1,32 @@ +const std = @import("std"); +const ast = @import("ast.zig"); + +fn parenthesize(name: []const u8, exprs: []*ast.Expr) void { + std.debug.warn("({}", name); + + for (exprs) |expr| { + std.debug.warn(" "); + printAst(expr); + } + + std.debug.warn(")"); +} + +pub fn printAst(ast_expr: *ast.Expr) void { + switch (ast_expr.*) { + .Binary => |expr| parenthesize(expr.operator.lexeme, &[]*ast.Expr{ expr.left, expr.right }), + .Grouping => |expr| parenthesize("group", &[]*ast.Expr{expr.expression}), + .Unary => |expr| parenthesize(expr.operator.lexeme, &[]*ast.Expr{expr.right}), + .Number => |ast_num| { + switch (ast_num) { + .Integer32 => |num| std.debug.warn("{}", num), + .Integer64 => |num| std.debug.warn("{}", num), + .Unsigned32 => |num| std.debug.warn("{}", num), + .Unsigned64 => |num| std.debug.warn("{}", num), + .Float32 => |num| std.debug.warn("{}", num), + .Float64 => |num| std.debug.warn("{}", num), + } + }, + else => unreachable, + } +} diff --git a/src/main.zig b/src/main.zig index bc07abc..96c4c58 100644 --- a/src/main.zig +++ b/src/main.zig @@ -67,7 +67,8 @@ fn runPrompt(allocator: *Allocator) !void { } pub fn main() anyerror!void { - var arena = std.heap.ArenaAllocator.init(std.heap.direct_allocator); + var da = std.heap.DirectAllocator.init(); + var arena = std.heap.ArenaAllocator.init(&da.allocator); defer arena.deinit(); var allocator = &arena.allocator; diff --git a/src/parser.zig b/src/parser.zig index e43588e..2e84369 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -10,7 +10,8 @@ const Scanner = scanners.Scanner; const Token = tokens.Token; const TokenType = tokens.TokenType; const Result = main.Result; -const Node = ast.Node; + +const Expr = ast.Expr; pub const Parser = struct { allocator: *Allocator, @@ -23,7 +24,7 @@ pub const Parser = struct { return Parser{ .allocator = allocator, .scanner = scanner }; } - fn doError(self: *Parser, comptime fmt: []const u8, args: ...) Result!void { + fn doError(self: *Parser, comptime fmt: []const u8, args: ...) !void { std.debug.warn("parser error at line {}\n\t", self.scanner.line); std.debug.warn(fmt, args); std.debug.warn("\n"); @@ -49,6 +50,20 @@ pub const Parser = struct { return Result.CompileError; } + fn synchronize(self: *Parser) void { + _ = self.advance(); + while (!self.isAtEnd()) { + if (self.previous().ttype == .Semicolon) return; + + switch (self.peek().ttype) { + .Struct, .Fn, .For, .If, .Return => return, + else => {}, + } + + _ = self.advance(); + } + } + fn isAtEnd(self: *Parser) bool { return self.peek().ttype == .EOF; } @@ -90,50 +105,150 @@ pub const Parser = struct { return Result.CompileError; } - fn mkFnDecl(self: *Parser, name: []const u8) !*ast.Node { - var node = try self.allocator.create(Node.FnDecl); - node.* = Node.FnDecl{ .name = name }; - return node; - } + fn equality(self: *Parser) !Expr { + var expr = try self.comparison(); - fn functionDecl(self: *Parser) !*ast.Node { - // get the name - var name = try self.consume(.Identifier, "expected function name"); - return try self.mkFnDecl(name); - } + while (self.match(&[]TokenType{ TokenType.BangEqual, TokenType.EqualEqual })) { + var operator = self.previous(); + var right = try self.comparison(); - fn processToken(self: *Parser, token: Token) Result!ast.Node { - switch (token.ttype) { - //.Fn => try self.functionDecl(), - else => blk: { - try self.doError("TODO handle {}\n", token.ttype); - return Result.CompileError; - }, + expr = ast.mkBinary(&expr, operator, &right); } + + return expr; } - pub fn parse(self: *Parser) !*ast.Node { + fn comparison(self: *Parser) !Expr { + var expr = try self.addition(); + + while (self.match(&[]TokenType{ + TokenType.Greater, + TokenType.GreaterEqual, + TokenType.Less, + TokenType.LessEqual, + })) { + var operator = self.previous(); + var right = try self.addition(); + expr = ast.mkBinary(&expr, operator, &right); + } + + return expr; + } + + fn addition(self: *Parser) !Expr { + var expr = try self.multiplication(); + + while (self.match(&[]TokenType{ TokenType.Minus, TokenType.Plus })) { + var operator = self.previous(); + var right = try self.multiplication(); + expr = ast.mkBinary(&expr, operator, &right); + } + + return expr; + } + + fn multiplication(self: *Parser) anyerror!Expr { + var expr = try self.unary(); + + while (self.match(&[]TokenType{ TokenType.Slash, TokenType.Star })) { + var operator = self.previous(); + var right = try self.unary(); + expr = ast.mkBinary(&expr, operator, &right); + } + + return expr; + } + + fn unary(self: *Parser) anyerror!Expr { + if (self.match(&[]TokenType{ TokenType.Bang, TokenType.Minus })) { + var operator = self.previous(); + var right = try self.unary(); + return ast.mkUnary(operator, &right); + } + + return try self.primary(); + } + + fn doInt(self: *Parser) !Expr { + var token = self.previous(); + + // try to parse it as an i32 first, if that fails, do i64. + + var num_32 = std.fmt.parseInt(i32, token.lexeme, 10) catch |pi_err| { + if (pi_err == error.Overflow) { + var num_64 = std.fmt.parseInt(i64, token.lexeme, 10) catch |pi_err2| { + if (pi_err2 == error.Overflow) { + try self.tokenError(token, "Failed to parse number: overflow"); + return Result.CompileError; + } else { + return pi_err2; + } + }; + + return ast.mkNum(i64, num_64); + } else { + return pi_err; + } + }; + + return ast.mkNum(i32, num_32); + } + + fn primary(self: *Parser) !Expr { + if (self.matchSingle(TokenType.False)) return ast.Expr{ .Bool = false }; + if (self.matchSingle(TokenType.True)) return ast.Expr{ .Bool = true }; + if (self.matchSingle(TokenType.None)) return ast.Expr{ .Nil = {} }; + + if (self.matchSingle(TokenType.Integer)) { + return try self.doInt(); + } + + if (self.matchSingle(TokenType.String)) { + var lexeme = self.previous().lexeme; + var slice = try self.allocator.alloc(u8, lexeme.len); + std.mem.copy(u8, slice, lexeme); + + return ast.Expr{ .String = slice }; + } + + if (self.matchSingle(TokenType.LeftParen)) { + var expr = try self.expression(); + _ = try self.consume(.RightParen, "Expect ')' after expression"); + + return ast.mkGrouping(&expr); + } + + try self.tokenError(self.peek(), "Expect expression"); + return Result.CompileError; + } + + fn expression(self: *Parser) !Expr { + return try self.equality(); + } + + pub fn parse(self: *Parser) !?*Expr { self.tokens = try self.allocator.alloc(Token, 0); var i: usize = 0; - var root = try ast.mkRoot(self.allocator); while (true) { var tok_opt = try self.scanner.nextToken(); - if (tok_opt) |token| { self.tokens = try self.allocator.realloc(self.tokens, i + 1); self.tokens[i] = token; i += 1; if (token.ttype == .EOF) break; - - var node = try self.processToken(token); - try root.Root.append(&node); - } else { - continue; } } - return root; + std.debug.warn("{} tokens\n", i); + + //return Expr{ .Number = ast.Number{ .Integer32 = 69 } }; + //return self.root; + var expr = self.expression() catch |parse_err| { + return null; + }; + + return &expr; } }; diff --git a/src/runner.zig b/src/runner.zig index e0f6493..c33eef5 100644 --- a/src/runner.zig +++ b/src/runner.zig @@ -44,12 +44,22 @@ pub const Runner = struct { scanner = scanners.Scanner.init(self.allocator, code); var parser = Parser.init(self.allocator, &scanner); - var root = try parser.parse(); + var expr_opt = try parser.parse(); - var it = root.Root.iterator(); + //var expr = ast.mkBinary( + // &ast.mkUnary( + // tokens.Token{ .ttype = .Minus, .lexeme = "-", .line = 1 }, + // &ast.mkNum(i32, 123), + // ), + // tokens.Token{ .ttype = .Star, .lexeme = "*", .line = 1 }, + // &ast.mkGrouping(&ast.mkNum(f32, 45.67)), + //); - while (it.next()) |node| { - std.debug.warn("{}\n", node.*); + if (expr_opt) |expr_ptr| { + printer.printAst(expr_ptr); + std.debug.warn("\n"); + } else { + return Result.CompileError; } return Result.Ok; diff --git a/src/scanner.zig b/src/scanner.zig index 491f1f2..0a632ee 100644 --- a/src/scanner.zig +++ b/src/scanner.zig @@ -24,7 +24,7 @@ fn isAlphaNumeric(char: u8) bool { return isAlpha(char) or isDigit(char); } -const keywords = [_][]const u8{ +const keywords = [][]const u8{ "break", "const", "continue", @@ -51,7 +51,7 @@ const keywords = [_][]const u8{ "None", }; -const keyword_ttypes = [_]TokenType{ +const keyword_ttypes = []TokenType{ .Break, .Const, .Continue, @@ -186,17 +186,35 @@ pub const Scanner = struct { /// Peek at the next character in the scanner fn peekNext(self: *Scanner) u8 { - if (self.current + 1 > self.source.len) return 0; + if (self.current > self.source.len) return 0; return self.source[self.current]; } + /// Skip all whitespace, but increments Scanner.line when finding a newline. + fn skipWhitespace(self: *Scanner) void { + while (true) { + var c = self.peek(); + + switch (c) { + ' ', '\r', '\t' => blk: { + _ = self.advance(); + }, + '\n' => blk: { + self.line += 1; + _ = self.advance(); + }, + else => return, + } + } + } + /// Consume a number. /// Returns either an Integer or a Float token. Proper typing /// of the number (i32 i64 u32 u64 f32 f64) are for the parser. fn doNumber(self: *Scanner) Token { var ttype = TokenType.Integer; - while (isDigit(self.peekNext())) { + while (isDigit(self.peek())) { _ = self.advance(); } @@ -261,6 +279,7 @@ pub const Scanner = struct { } pub fn nextToken(self: *Scanner) !?Token { + self.skipWhitespace(); self.start = self.current; if (self.isAtEnd()) return self.makeToken(TokenType.EOF); @@ -323,12 +342,6 @@ pub const Scanner = struct { '\'' => try self.doString('\''), '"' => try self.doString('"'), - ' ', '\r', '\t' => null, - '\n' => blk: { - self.line += 1; - break :blk null; - }, - else => return ScannerError.Unexpected, };