const std = @import("std"); const scanners = @import("scanners.zig"); const main = @import("main.zig"); const ast = @import("ast.zig"); const tokens = @import("tokens.zig"); const ereport = @import("errors.zig"); const printer = @import("ast_printer.zig"); const Allocator = std.mem.Allocator; const Scanner = scanners.Scanner; const Token = tokens.Token; const TokenType = tokens.TokenType; pub const ParseError = error{ CompileError, UnknownOperator, InvalidInteger, }; const Node = ast.Node; const Expr = ast.Expr; const Stmt = ast.Stmt; const TokenList = std.ArrayList(Token); const operator_tokens = [_][]const u8{ "+", "-", "*", "/", "%", ">", ">=", "<", "<=", "==", }; const operator_values = [_]ast.BinaryOperator{ .Add, .Sub, .Mul, .Div, .Mod, .Greater, .GreaterEqual, .Less, .LessEqual, .Equal, }; fn toBinaryOperator(op: Token) !ast.BinaryOperator { const lex = op.lexeme; inline for (operator_tokens) |op_str, idx| { if (std.mem.eql(u8, lex, op_str)) return operator_values[idx]; } // special cases: && + and => .And and || + or => .Or if (std.mem.eql(u8, lex, "&&") or std.mem.eql(u8, lex, "and")) return .And; if (std.mem.eql(u8, lex, "||") or std.mem.eql(u8, lex, "or")) return .Or; return ParseError.UnknownOperator; } const unary_operator_tokens = [_][]const u8{ "!", "-", }; const unary_operators = [_]ast.UnaryOperator{ .Not, .Negate, }; fn toUnaryOperator(op: Token) !ast.UnaryOperator { const lex = op.lexeme; inline for (unary_operator_tokens) |op_str, idx| { if (std.mem.eql(u8, lex, op_str)) return unary_operators[idx]; } return ParseError.UnknownOperator; } pub const Parser = struct { allocator: *Allocator, scanner: *Scanner, tokens: TokenList, hadError: bool = false, err_ctx: ?[]const u8 = null, pub fn init(allocator: *Allocator, scanner: *Scanner) Parser { return Parser{ .allocator = allocator, .scanner = scanner, .tokens = TokenList.init(allocator), }; } pub fn deinit(self: *@This()) void { self.tokens.deinit(); } fn setErrContext(self: *Parser, comptime fmt: ?[]const u8, args: anytype) void { if (fmt == null) { self.err_ctx = null; return; } var buf = self.allocator.alloc(u8, 256) catch unreachable; self.err_ctx = std.fmt.bufPrint(buf, fmt.?, args) catch unreachable; } fn doError(self: *Parser, comptime fmt: []const u8, args: anytype) ParseError { self.hadError = true; std.debug.warn("parser error at line {}", .{self.scanner.line}); if (self.err_ctx) |ctx| { std.debug.warn(" on {}", .{ctx}); } std.debug.warn("\n\t", .{}); std.debug.warn(fmt, args); std.debug.warn("\n", .{}); return ParseError.CompileError; } fn synchronize(self: *Parser) !void { // TODO is it nextToken()? _ = try self.nextToken(); while (!self.isAtEnd()) { if (self.previous().typ == .Semicolon) return; // TODO add more "stmt"-"starting" tokens here switch (self.peek().typ) { .Fn => return, else => {}, } _ = try self.nextToken(); } } fn peek(self: *Parser) Token { return self.tokens.items[self.tokens.items.len - 1]; } fn previous(self: *Parser) Token { return self.tokens.items[self.tokens.items.len - 2]; } fn tokenError(self: *Parser, token: Token, msg: []const u8) ParseError { std.debug.warn("ctx: '{}'\n", .{self.err_ctx}); if (token.typ == .EOF) { ereport.report(token.line, " at end", self.err_ctx, msg); } else { ereport.reportFmt(token.line, self.err_ctx, " at '{}': {}", .{ token.lexeme, msg, }); } return ParseError.CompileError; } fn isAtEnd(self: *Parser) bool { return self.peek().typ == .EOF; } fn check(self: *Parser, typ: TokenType) bool { if (self.isAtEnd()) return false; return self.peek().typ == typ; } fn nextToken(self: *Parser) !Token { var token: Token = undefined; while (true) { var next_token_opt = try self.scanner.nextToken(); if (next_token_opt) |token_nice| { token = token_nice; break; } } try self.tokens.append(token); std.debug.warn("skip to {}\n", .{token}); return token; } /// Consume the current token type, then walk to the next token. /// Returns the consumed token. fn consume(self: *Parser, ttype: TokenType, comptime msg: []const u8) !Token { if (self.check(ttype)) { var tok = self.peek(); _ = try self.nextToken(); return tok; } return self.tokenError(self.peek(), msg); } /// Consume the current token. Gives default error messages fn consumeSingle(self: *Parser, ttype: TokenType) !Token { if (self.check(ttype)) { var cur = self.peek(); _ = try self.nextToken(); return cur; } // TODO maybe this could be entirely comptime? var buf_main: [1000]u8 = undefined; var buf = try std.fmt.bufPrint(&buf_main, "expected {}, got {}", .{ ttype, self.peek().typ, }); return self.tokenError(self.peek(), buf); } /// check() against multiple tokens fn compareAnyOf(self: *@This(), ttypes: []const TokenType) bool { for (ttypes) |typ| { if (self.check(typ)) return true; } return false; } // TODO maybe move helper functions to ast_helper.zig? fn mkConstDecl(self: *Parser, consts: ast.ConstList) !*ast.Node { var node = try self.allocator.create(Node); node.* = Node{ .ConstDecl = consts }; return node; } fn mkBlock(self: *Parser, stmts: ast.StmtList) !*ast.Node { var node = try self.allocator.create(Node); node.* = Node{ .Block = stmts }; return node; } fn mkStmtExpr(self: *Parser, expr: *Expr) !*Stmt { var stmt = try self.allocator.create(Stmt); stmt.* = Stmt{ .Expr = expr }; return stmt; } fn mkGrouping(self: *Parser, expr: *Expr) !*ast.Expr { var grouping = try self.allocator.create(Expr); grouping.* = Expr{ .Grouping = expr }; return grouping; } fn mkUnary(self: *Parser, op: ast.UnaryOperator, right: *Expr) !*Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Unary = ast.UnaryExpr{ .op = op, .right = right, }, }; return expr; } fn mkBinary(self: *Parser, left: *Expr, op: ast.BinaryOperator, right: *Expr) !*Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Binary = ast.BinaryExpr{ .left = left, .op = op, .right = right, }, }; return expr; } fn mkAssign(self: *Parser, name: Token, value: *Expr) !*Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Assign = ast.AssignExpr{ .name = name, .value = value, }, }; return expr; } fn mkCall(self: *@This(), callee: *Expr, paren: Token, args: ast.ExprList) !*Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Call = ast.CallExpr{ .callee = callee, .paren = paren, .arguments = args, }, }; return expr; } fn mkStructExpr(self: *@This(), name: Token, args: ast.StructInitList) !*Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Struct = ast.StructExpr{ .name = name, .inits = args, }, }; return expr; } fn mkGet(self: *@This(), target: *Expr, name: Token) !*Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Get = ast.GetExpr{ .target = target, .name = name, }, }; return expr; } fn mkSet(self: *@This(), struc: *Expr, field: Token, value: *Expr) !*Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Set = ast.SetExpr{ .struc = struc, .field = field, .value = value, }, }; return expr; } fn mkBool(self: *Parser, val: bool) !*ast.Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Literal = ast.LiteralExpr{ .Bool = val, }, }; return expr; } fn mkInt32(self: *Parser, val: i32) !*ast.Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Literal = ast.LiteralExpr{ .Integer32 = val, }, }; return expr; } fn mkInt64(self: *Parser, val: i64) !*ast.Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Literal = ast.LiteralExpr{ .Integer64 = val, }, }; return expr; } fn mkFloat(self: *Parser, val: []const u8) !*ast.Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Literal = ast.LiteralExpr{ .Float = val, }, }; return expr; } fn mkString(self: *Parser, val: []const u8) !*ast.Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Literal = ast.LiteralExpr{ .String = val, }, }; return expr; } fn mkArray(self: *Parser, exprs: ast.ExprList) !*ast.Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Literal = ast.LiteralExpr{ .Array = exprs, }, }; return expr; } fn mkVariable(self: *Parser, tok: Token) !*ast.Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Variable = tok }; return expr; } pub fn parse(self: *Parser) !?*ast.Node { var root = try Node.mkRoot(self.allocator); var token_opt: ?Token = null; while (true) { if (token_opt == null) { token_opt = try self.nextToken(); } else { token_opt = self.peek(); } var token = token_opt.?; if (token.typ == .EOF) break; var node = self.parseTopDecl() catch |err| { if (err == ParseError.CompileError) return null; return err; }; try root.Root.append(node.*); } if (self.hadError) { return error.ParseError; } return root; } /// Copy a token with a different lexeme. fn mkToken(self: *@This(), ttype: TokenType, lexeme: []const u8, line: usize) !Token { const owned_lexeme = try std.mem.dupe(self.allocator, u8, lexeme); return Token{ .typ = ttype, .lexeme = owned_lexeme, .line = line, }; } fn parseFnDecl(self: *@This()) !*Node { var param_list = ast.ParamList.init(self.allocator); errdefer param_list.deinit(); var method: ?*ast.MethodData = null; _ = try self.consumeSingle(.Fn); if (self.check(.LeftParen)) { method = try self.parsePreMethod(); } const orig_name = try self.consumeSingle(.Identifier); const name = if (std.mem.eql(u8, orig_name.lexeme, "main")) blk: { break :blk try self.mkToken(.Identifier, "__rayoko_main", orig_name.line); } else blk: { break :blk orig_name; }; self.setErrContext("function {}", .{name.lexeme}); _ = try self.consumeSingle(.LeftParen); while (self.peek().typ != .RightParen) { const param_name = try self.consumeSingle(.Identifier); _ = try self.consumeSingle(.Colon); const param_type = try self.consumeSingle(.Identifier); try param_list.append(ast.ParamDecl{ .name = param_name, .typ = param_type, }); if (self.check(.Comma)) _ = try self.consumeSingle(.Comma); } _ = try self.consumeSingle(.RightParen); // the return type is default void if a type // is not provided var return_type: Token = undefined; if (self.check(.Identifier)) { return_type = try self.consumeSingle(.Identifier); } else { return_type = try self.mkToken(.Identifier, "void", name.line); } var block_node = try self.parseBlock(); return try Node.mkFnDecl( self.allocator, name, param_list, return_type, block_node.Block, method, ); } /// parse the (v T) part of the method (defined here /// as a premethod) fn parsePreMethod(self: *@This()) !?*ast.MethodData { _ = try self.consumeSingle(.LeftParen); const variable = try self.consumeSingle(.Identifier); const typ = try self.consumeSingle(.Identifier); _ = try self.consumeSingle(.RightParen); // create method data and assign the values we got into it var method = try self.allocator.create(ast.MethodData); method.* = ast.MethodData{ .variable = variable, .typ = typ, }; return method; } fn parseConstDecl(self: *@This()) !*Node { var consts = ast.ConstList.init(self.allocator); errdefer consts.deinit(); self.setErrContext("const", .{}); _ = try self.consumeSingle(.Const); _ = try self.consumeSingle(.LeftParen); while (self.peek().typ != .RightParen) { const const_name = try self.consumeSingle(.Identifier); self.setErrContext("const {}", .{const_name}); _ = try self.consumeSingle(.Equal); // const declarations dont have type, a future type system must // check the output type of the expression and assign it to the // const later on. var expr = try self.parseExpr(); try consts.append(ast.SingleConst{ .name = const_name, .expr = expr, }); } _ = try self.consumeSingle(.RightParen); return self.mkConstDecl(consts); } fn parseStructDecl(self: *@This()) !*Node { var fields = ast.FieldList.init(self.allocator); errdefer fields.deinit(); self.setErrContext("struct", .{}); _ = try self.consumeSingle(.Struct); var name = try self.consumeSingle(.Identifier); self.setErrContext("struct {}", .{name}); _ = try self.consumeSingle(.LeftBrace); while (!self.check(.RightBrace)) { const field_name = try self.consumeSingle(.Identifier); self.setErrContext("struct {} field {}", .{ name, field_name }); const field_type = try self.consumeSingle(.Identifier); try fields.append(ast.StructField{ .name = field_name, .typ = field_type, }); } _ = try self.consumeSingle(.RightBrace); return Node.mkStructDecl(self.allocator, name, fields); } fn parseEnumDecl(self: *@This()) !*Node { var fields = ast.TokenList.init(self.allocator); errdefer fields.deinit(); self.setErrContext("enum", .{}); _ = try self.consumeSingle(.Enum); const name = try self.consumeSingle(.Identifier); self.setErrContext("enum {}", .{name}); _ = try self.consumeSingle(.LeftBrace); while (!self.check(.RightBrace)) { try fields.append(try self.consumeSingle(.Identifier)); } _ = try self.consumeSingle(.RightBrace); return try Node.mkEnumDecl(self.allocator, name, fields); } fn parseTopDecl(self: *@This()) !*Node { self.setErrContext(null, .{}); return switch (self.peek().typ) { .Fn => try self.parseFnDecl(), .Const => try self.parseConstDecl(), .Struct => try self.parseStructDecl(), .Enum => try self.parseEnumDecl(), else => |typ| blk: { return self.doError("expected Fn, Const, Struct, got {}\n", .{typ}); }, }; } fn parseBlockInternal(self: *@This(), comptime T: type) !T { var stmts = T.init(self.allocator); errdefer stmts.deinit(); _ = try self.consumeSingle(.LeftBrace); while (self.peek().typ != .RightBrace) { var stmt = try self.parseStmt(); printer.printStmt(0, stmt); if (self.check(.Semicolon)) _ = try self.consumeSingle(.Semicolon); try stmts.append(stmt.*); } _ = try self.consumeSingle(.RightBrace); return stmts; } 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(), .Println => try self.parsePrintln(), .Return => try self.parseReturn(), else => try self.parseStmtExpr(), }; } 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); return try self.mkBlock(stmts); } /// parse blocks inside statements fn parseStmtBlock(self: *@This()) !ast.Block { var block = try self.parseBlockInternal(ast.Block); return block; } fn parseIfStmt(self: *@This()) !*Stmt { _ = try self.consumeSingle(.If); _ = try self.consumeSingle(.LeftParen); var condition = try self.parseExpr(); _ = try self.consumeSingle(.RightParen); const then_branch = try self.parseStmtBlock(); var else_branch: ?ast.Block = null; if (self.check(.Else)) { _ = try self.consumeSingle(.Else); else_branch = try self.parseStmtBlock(); } return try Stmt.mkIfStmt( self.allocator, condition, then_branch, else_branch, ); } fn parseForStmt(self: *@This()) !*Stmt { // There are two types of for in vig's V subset: // - for x in y // - for idx, x in y _ = try self.consumeSingle(.For); var index_var: ?Token = null; var value_var: Token = undefined; const subject_1 = try self.consumeSingle(.Identifier); if (self.check(.Comma)) { _ = try self.consumeSingle(.Comma); const subject_2 = try self.consumeSingle(.Identifier); index_var = subject_1; value_var = subject_2; } else { value_var = subject_1; } _ = try self.consumeSingle(.In); // MUST be identifier var array = try self.consumeSingle(.Identifier); var block = try self.parseStmtBlock(); return try Stmt.mkFor( self.allocator, index_var, value_var, array, block, ); } fn parseLoop(self: *@This()) !*Stmt { _ = try self.consumeSingle(.Loop); var expr: ?*Expr = null; var body: ast.Block = undefined; // 'loop {' = infinite loop if (self.check(.LeftBrace)) { body = try self.parseStmtBlock(); } else { expr = try self.parseExpr(); body = try self.parseStmtBlock(); } return try Stmt.mkLoop(self.allocator, expr, body); } fn parseReturn(self: *@This()) !*Stmt { const tok = try self.consumeSingle(.Return); // TODO if (self.check(.Semicolon)) insert a void literal? const expr = try self.parseExpr(); return try Stmt.mkReturn(self.allocator, tok, expr); } fn parsePrintln(self: *@This()) !*Stmt { _ = try self.consumeSingle(.Println); _ = try self.consumeSingle(.LeftParen); var expr = try self.parseExpr(); _ = try self.consumeSingle(.RightParen); return try Stmt.mkPrintln(self.allocator, expr); } fn parseStmtExpr(self: *@This()) !*Stmt { var expr = try self.parseExpr(); return try self.mkStmtExpr(expr); } fn parseExpr(self: *@This()) anyerror!*Expr { return try self.parseAssignment(); } fn parseAssignment(self: *@This()) anyerror!*Expr { // there can be two assignments coming out of this function: // - an assignment to a variable with = // - an update to a variable with +=, -= var expr = try self.parseOr(); if (self.compareAnyOf(&[_]TokenType{ .Equal, .PlusEqual, .MinusEqual, .StarEqual, .SlashEqual, })) { return try self.finishAssignment(expr); } return expr; } fn finishAssignment( self: *@This(), expr: *Expr, ) !*Expr { var op_tok = self.peek(); _ = try self.nextToken(); var value = try self.parseAssignment(); // expr can be a (Variable|Set) // op_tok can be one of two categories: // - Equal, parses to (Assign|Set) // - Inplace (+=, -=, *=, /=), parses to (Assign|Set) // however, when dealing with inplace operators, we want those to parse // to an assign/set with a binary expression with the undelying operator // instead of the infix operator. // e.g 'x += 1' parses down to 'x = x + 1' // TODO do we add %=? const binop: ?ast.BinaryOperator = switch (op_tok.typ) { .PlusEqual => ast.BinaryOperator.Add, .MinusEqual => .Sub, .StarEqual => .Mul, .SlashEqual => .Div, else => null, }; switch (expr.*) { .Variable => { switch (op_tok.typ) { .Equal => return try self.mkAssign(expr.Variable, value), .PlusEqual, .MinusEqual, .StarEqual, .SlashEqual => { return try self.mkAssign( expr.Variable, try self.mkBinary(expr, binop.?, value), ); }, else => unreachable, } }, .Get => |get| { switch (op_tok.typ) { // TODO remove .ColonEqual from language .ColonEqual => { return self.doError("can not initialize struct field", .{}); }, .Equal => return try self.mkSet(get.target, get.name, value), .PlusEqual, .MinusEqual, .StarEqual, .SlashEqual => { return try self.mkSet( get.target, get.name, try self.mkBinary(expr, binop.?, value), ); }, else => unreachable, } }, else => |expr_typ| { return self.doError("Invalid assignment target {}", .{expr_typ}); }, } } fn parseOr(self: *@This()) !*Expr { var expr = try self.parseAnd(); while (self.check(.Or)) { var op = self.peek(); _ = try self.nextToken(); var right = try self.parseAnd(); expr = try self.mkBinary(expr, try toBinaryOperator(op), right); } return expr; } fn parseAnd(self: *@This()) !*Expr { var expr = try self.parseEquality(); while (self.check(.And)) { var op = self.peek(); _ = try self.nextToken(); var right = try self.parseEquality(); expr = try self.mkBinary(expr, try toBinaryOperator(op), right); } return expr; } fn parseEquality(self: *@This()) !*Expr { var expr = try self.parseComparison(); while (self.check(.EqualEqual)) { var op = self.peek(); _ = try self.nextToken(); var right = try self.parseComparison(); expr = try self.mkBinary(expr, .Equal, right); } return expr; } fn parseComparison(self: *@This()) !*Expr { var expr = try self.parseAddition(); while (self.compareAnyOf(&[_]TokenType{ .Greater, .GreaterEqual, .Less, .LessEqual, })) { var op = self.peek(); _ = try self.nextToken(); var right = try self.parseAddition(); expr = try self.mkBinary(expr, try toBinaryOperator(op), right); } return expr; } fn parseAddition(self: *@This()) !*Expr { var expr = try self.parseMultiplication(); while (self.compareAnyOf(&[_]TokenType{ .Minus, .Plus, })) { var op = self.peek(); _ = try self.nextToken(); var right = try self.parseMultiplication(); expr = try self.mkBinary(expr, try toBinaryOperator(op), right); } return expr; } fn parseMultiplication(self: *@This()) !*Expr { var expr = try self.parseUnary(); while (self.compareAnyOf(&[_]TokenType{ .Star, .Slash, })) { var op = self.peek(); _ = try self.nextToken(); var right = try self.parseUnary(); expr = try self.mkBinary(expr, try toBinaryOperator(op), right); } return expr; } fn parseUnary(self: *@This()) anyerror!*Expr { if (self.compareAnyOf(&[_]TokenType{ .Bang, .Minus })) { var op = self.peek(); _ = try self.nextToken(); var right = try self.parseCall(); return try self.mkUnary(try toUnaryOperator(op), right); } var expr = try self.parseCall(); return expr; } /// Parse either: /// - A function call /// - A struct initialization (Point.{...}) /// - A struct Get expression (p.x) fn parseCall(self: *@This()) !*Expr { var expr = try self.parsePrimary(); while (true) { if (self.check(.LeftParen)) { _ = try self.consumeSingle(.LeftParen); expr = try self.finishCall(expr); } else if (self.check(.Dot)) { _ = try self.consumeSingle(.Dot); if (self.check(.LeftBrace)) { _ = try self.consumeSingle(.LeftBrace); expr = try self.finishStructVal(expr); } else { var name = try self.consume( .Identifier, "Expect property name after '.'", ); expr = try self.mkGet(expr, name); } } else { //return self.tokenError(self.peek(), "Expect expression."); break; } } return expr; } fn finishCall(self: *@This(), callee: *Expr) !*Expr { var args = ast.ExprList.init(self.allocator); errdefer args.deinit(); if (!self.check(.RightParen)) { // emulating do-while really badly var arg = try self.parseExpr(); try args.append(arg.*); while (self.check(.Comma)) { _ = try self.consumeSingle(.Comma); arg = try self.parseExpr(); try args.append(arg.*); } } var paren = try self.consume(.RightParen, "Expected ')' after arguments"); return self.mkCall(callee, paren, args); } fn finishStructVal(self: *@This(), expr: *Expr) !*Expr { // {a: 10 b: 10} // for this to work properly, must be Variable, since its a type. const expr_type = @as(ast.ExprType, expr.*); if (expr_type != .Variable) { return self.doError("Expected variable for struct type, got {}", .{expr_type}); } var inits = ast.StructInitList.init(self.allocator); errdefer inits.deinit(); while (!self.check(.RightBrace)) { const field_name = try self.consumeSingle(.Identifier); // TODO check .Comma for the quick initialization {val,val,val} _ = try self.consumeSingle(.Colon); const field_value = try self.parseExpr(); try inits.append(ast.StructInit{ .field = field_name, .expr = field_value, }); } _ = try self.consumeSingle(.RightBrace); return try self.mkStructExpr(expr.Variable, inits); } fn parsePrimary(self: *@This()) !*Expr { const curtype = self.peek().typ; const lexeme = self.peek().lexeme; var expr = switch (curtype) { .False => try self.mkBool(false), .True => try self.mkBool(true), .Integer => blk: { // hacky, but it works. the old approach was doing // parseInt(i64) on the catch block of parseInt(i32) var i32_num_opt: ?i32 = std.fmt.parseInt(i32, lexeme, 10) catch null; var i64_num: i64 = std.fmt.parseInt(i64, lexeme, 10) catch |err| { return self.doError("Invalid integer (not 32bit or 64bit) '{}': {}", .{ lexeme, err, }); }; if (i32_num_opt) |i32_num| { break :blk try self.mkInt32(i32_num); } else { break :blk try self.mkInt64(i64_num); } // old one //break :blk try self.mkInt32(std.fmt.parseInt(i32, lexeme, 10) catch |err32| { // break :blk try self.mkInt64(std.fmt.parseInt(i64, lexeme, 10) catch |err64| { // return self.doError( // "Invalid integer (not 32bit or 64bit) '{}': {}", // lexeme, // err64, // ); // }); //}); }, // TODO std.fmt.parseFloat .Float => try self.mkFloat(lexeme), .String => try self.mkString(lexeme), .Identifier => try self.mkVariable(self.peek()), // type checking for arrays happens at later stages .LeftSquare => { _ = try self.consumeSingle(.LeftSquare); var exprs = ast.ExprList.init(self.allocator); errdefer exprs.deinit(); while (!self.check(.RightSquare)) { try exprs.append((try self.parseExpr()).*); if (self.check(.Comma)) _ = try self.consumeSingle(.Comma); } _ = try self.consumeSingle(.RightSquare); return try self.mkArray(exprs); }, .LeftParen => { _ = try self.nextToken(); var expr = try self.parseExpr(); _ = try self.consume(.RightParen, "Expected ')' after expression"); // for groupings, we don't want to skip tokens as we already // consumed RightParen. return try self.mkGrouping(expr); }, else => blk: { return self.doError("expected literal, got {}", .{curtype}); }, }; _ = try self.nextToken(); return expr; } };