const std = @import("std"); const tok = @import("tokenizer.zig"); const Ast = @import("Ast.zig"); const assert = std.debug.assert; const Token = tok.Token; const Tokenizer = tok.Tokenizer; const Parse = @This(); gpa: std.mem.Allocator, arena: std.mem.Allocator, tokenizer: Tokenizer, token: Token, scratch: std.ArrayListUnmanaged(*Ast.Node), grammar_stack: std.ArrayListUnmanaged(ScanContext), block_stack: std.ArrayListUnmanaged(State), choice_stack: std.ArrayListUnmanaged(State), errors: std.ArrayListUnmanaged(Ast.Error), panic_mode: bool, flags: u32, max_parse_depth: usize, max_argument_count: usize, knot_offset: usize = 0, pub const Error = error{ ParseError, OutOfMemory, }; pub const State = struct { level: usize, scratch_offset: usize, source_offset: usize, }; pub const StmtContext = struct { tag: Tag, is_block_created: bool = false, expression_node: ?*Ast.Node = null, level: usize = 0, blocks_top: usize, choices_top: usize, scratch_top: usize, pub const Tag = enum { block, conditional, }; }; pub const ScanContext = struct { grammar: Tokenizer.Grammar, source_offset: usize, }; const Precedence = enum { none, assign, logical_or, logical_and, comparison, term, factor, }; pub fn deinit(p: *Parse) void { p.scratch.deinit(p.gpa); p.grammar_stack.deinit(p.gpa); p.block_stack.deinit(p.gpa); p.choice_stack.deinit(p.gpa); p.errors.deinit(p.gpa); p.* = undefined; } fn makeStmtContext(p: *Parse, tag: StmtContext.Tag, node: ?*Ast.Node) StmtContext { const context: StmtContext = .{ .tag = tag, .expression_node = node, .blocks_top = p.block_stack.items.len, .choices_top = p.choice_stack.items.len, .scratch_top = p.scratch.items.len, }; return context; } fn getTokenPrefixType(tag: Token.Tag) Ast.Node.Tag { return switch (tag) { .keyword_not, .exclaimation_mark => .logical_not_expr, .minus => .negate_expr, else => .invalid, }; } fn getTokenInfixType(tag: Token.Tag) Ast.Node.Tag { return switch (tag) { .percentage, .keyword_mod => .mod_expr, .plus => .add_expr, .minus => .subtract_expr, .star => .multiply_expr, .slash => .divide_expr, // .question_mark => .contains_expr, // .equal => .assign_stmt, .ampersand_ampersand, .keyword_and => .logical_and_expr, .pipe_pipe, .keyword_or => .logical_or_expr, .equal_equal => .logical_equality_expr, .not_equal => .logical_inequality_expr, .less_than => .logical_lesser_expr, .greater_than => .logical_greater_expr, .less_than_equal => .logical_lesser_or_equal_expr, .greater_than_equal => .logical_greater_or_equal_expr, else => .invalid, }; } fn getBindingPower(tag: Token.Tag) Precedence { return switch (tag) { .ampersand_ampersand, .keyword_and => .logical_and, .pipe_pipe, .keyword_or => .logical_or, .equal_equal, .not_equal => .comparison, .less_than, .less_than_equal => .comparison, .greater_than, .greater_than_equal => .comparison, .question_mark => .comparison, .plus, .minus => .term, .star, .slash, .percentage, .keyword_mod => .factor, .equal => .assign, else => .none, }; } fn getBranchTag(tag: Token.Tag) Ast.Node.Tag { return switch (tag) { .star => .choice_star_stmt, .plus => .choice_plus_stmt, else => .invalid, }; } fn fail(p: *Parse, tag: Ast.Error.Tag, token: Token) error{ ParseError, OutOfMemory } { if (p.panic_mode) return error.ParseError; p.panic_mode = true; const err: Ast.Error = .{ .tag = tag, .loc = .{ .start = token.loc.start, .end = token.loc.end, }, }; try p.errors.append(p.gpa, err); return error.ParseError; } fn checkToken(p: *const Parse, tag: Token.Tag) bool { return p.token.tag == tag; } fn checkTokenInSet(p: *const Parse, tag_set: []const Token.Tag) bool { if (p.checkToken(.eof)) return true; for (tag_set) |tag| { if (p.checkToken(tag)) return true; } return false; } fn nextToken(p: *Parse) Token { assert(p.grammar_stack.items.len > 0); const token = p.token; const context = p.grammar_stack.getLast(); p.token = p.tokenizer.next(context.grammar); return token; } fn eatToken(p: *Parse, tag: Token.Tag) void { if (p.checkToken(tag)) _ = p.nextToken(); } fn eatTokenLooped(p: *Parse, tag: Token.Tag, ignore_whitespace: bool) usize { var count: usize = 0; while (p.checkToken(tag)) : (count += 1) { _ = p.nextToken(); if (ignore_whitespace) p.eatToken(.whitespace); } return count; } fn expectToken(p: *Parse, tag: Token.Tag, skip_whitespace: bool) Error!Token { if (skip_whitespace) p.eatToken(.whitespace); if (!p.checkToken(tag)) { std.debug.print("Expected token '{any}', got '{any}'\n", .{ tag, p.token.tag }); return p.fail(.unexpected_token, p.token); } return p.nextToken(); } fn expectNewline(p: *Parse) Error!Token { if (!p.checkToken(.eof) and !p.checkToken(.newline)) { return p.fail(.expected_newline, p.token); } return p.token; } fn synchronize(p: *Parse) void { p.panic_mode = false; while (true) { switch (p.token.tag) { .eof, .newline, .right_brace, .right_paren => break, else => _ = p.nextToken(), } } } fn pushGrammar(p: *Parse, grammar: Tokenizer.Grammar) error{OutOfMemory}!void { const context: ScanContext = .{ .grammar = grammar, .source_offset = p.tokenizer.index, }; return p.grammar_stack.append(p.gpa, context); } fn popGrammar(p: *Parse) void { _ = p.grammar_stack.pop() orelse @panic("BUG: Grammar mode stack popped when empty!"); } fn rewindGrammar(p: *Parse) void { const grammar = p.grammar_stack.getLast(); const offset = grammar.source_offset; assert(offset <= p.tokenizer.index); p.tokenizer.index = offset; } fn isScratchEmpty(p: *Parse, context: *const StmtContext) bool { return context.scratch_top == p.scratch.items.len; } fn peekScratch(p: *Parse, context: *const StmtContext) *Ast.Node { assert(context.scratch_top < p.scratch.items.len); return p.scratch.getLast(); } fn popScratch(p: *Parse, context: *const StmtContext) *Ast.Node { assert(context.scratch_top < p.scratch.items.len); return p.scratch.pop() orelse @panic("BUG: Scratch buffer popped when empty!"); } fn nodeListFromScratch(p: *Parse, start_offset: usize, end_offset: usize) Error![]*Ast.Node { const span = end_offset - start_offset; assert(span > 0); const list = try p.arena.alloc(*Ast.Node, span); defer p.scratch.shrinkRetainingCapacity(start_offset); var li: usize = 0; var i: usize = start_offset; while (i < end_offset) : (i += 1) { list[li] = p.scratch.items[i]; li += 1; } return list; } fn makeNodeSequence( p: *Parse, context: *const StmtContext, tag: Ast.Node.Tag, loc: Ast.Node.Span, scratch_offset: usize, ) Error!*Ast.Node { var list: ?[]*Ast.Node = null; if (!p.isScratchEmpty(context)) { list = try p.nodeListFromScratch(scratch_offset, p.scratch.items.len); } return .createList(p.arena, tag, loc, list); } fn isBlockStackEmpty(p: *Parse, context: *const StmtContext) bool { return context.blocks_top == p.block_stack.items.len; } fn peekBlockStack(p: *Parse, context: *const StmtContext) State { assert(context.blocks_top < p.block_stack.items.len); return p.block_stack.getLast(); } fn popBlockStack(p: *Parse, context: *const StmtContext) State { assert(context.blocks_top < p.block_stack.items.len); return p.block_stack.pop() orelse @panic("Bug: Block stack popped when empty!"); } fn isChoiceStackEmpty(p: *Parse, context: *const StmtContext) bool { return context.choices_top == p.choice_stack.items.len; } fn peekChoiceStack(p: *Parse, context: *const StmtContext) State { assert(context.choices_top < p.choice_stack.items.len); return p.choice_stack.getLast(); } fn popChoiceStack(p: *Parse, context: *const StmtContext) State { assert(context.choices_top < p.choice_stack.items.len); return p.choice_stack.pop() orelse @panic("Bug: Choice stack popped when empty!"); } fn fixupBlock(p: *Parse, context: *StmtContext, node: *Ast.Node) !*Ast.Node { if (!p.isScratchEmpty(context)) { const stmt = p.peekScratch(context); switch (stmt.tag) { .choice_star_stmt, .choice_plus_stmt, .switch_case, .if_branch, .else_branch, => { stmt.data.bin.rhs = node; return p.popScratch(context); }, else => {}, } } context.is_block_created = true; return node; } fn collectBlock(p: *Parse, context: *StmtContext, level: usize) Error!?*Ast.Node { if (p.isBlockStackEmpty(context)) return null; const block = p.peekBlockStack(context); if (block.level < level) return null; const span_start = block.source_offset; var span_end: usize = 0; if (!p.isScratchEmpty(context)) { const last = p.peekScratch(context); span_end = last.loc.end; } else { span_end = span_start; } var node = try p.makeNodeSequence(context, .block_stmt, .{ .start = span_start, .end = span_end, }, block.scratch_offset); node = try p.fixupBlock(context, node); _ = p.popBlockStack(context); return node; } fn collectContext( p: *Parse, context: *StmtContext, level: usize, should_gather: bool, ) Error!?*Ast.Node { // The level of the current choice should always be greater then the // level for the current block. Choice statements must have non-zero // levels, while blocks can levels greater than or equal to zero. // // Choice statement levels need not follow a sequentially increasing order. // When collecting choice branches, statements with levels less than the // previous statement will be included in the same enclosing choice if no // previous levels exist. while (!p.isChoiceStackEmpty(context)) { assert(!p.isBlockStackEmpty(context)); const choice_state = p.peekChoiceStack(context); if (choice_state.level <= level) break; _ = p.popChoiceStack(context); if (!p.isBlockStackEmpty(context)) { const block_state = p.peekBlockStack(context); if (choice_state.level <= block_state.level) { const node = try p.collectBlock(context, block_state.level); if (node) |n| try p.scratch.append(p.gpa, n); } } if (!should_gather) { if (!p.isChoiceStackEmpty(context)) { const prev_choice = p.peekChoiceStack(context); if (level > prev_choice.level) { try p.choice_stack.append(p.gpa, .{ .level = level, .scratch_offset = choice_state.scratch_offset, .source_offset = choice_state.source_offset, }); break; } } else if (level > 0) { try p.choice_stack.append(p.gpa, .{ .level = level, .scratch_offset = choice_state.scratch_offset, .source_offset = choice_state.source_offset, }); break; } } const node = try p.makeNodeSequence(context, .choice_stmt, .{ .start = choice_state.source_offset, .end = p.token.loc.start, }, choice_state.scratch_offset); try p.scratch.append(p.gpa, node); } if (!should_gather) return p.collectBlock(context, level); if (!p.isScratchEmpty(context)) return p.popScratch(context); return null; } fn collectStitch(p: *Parse, context: *StmtContext) Error!?*Ast.Node { const node = try p.collectContext(context, 0, false); if (p.isScratchEmpty(context)) return node; const proto = p.peekScratch(context); const tag: Ast.Node.Tag = switch (proto.tag) { .stitch_prototype => .stitch_decl, .function_prototype => .function_decl, else => return node, }; _ = p.popScratch(context); return .createBinary(p.arena, tag, .{ .start = proto.loc.start, .end = if (node) |n| n.loc.end else proto.loc.end, }, proto, node); } fn collectKnot(p: *Parse, context: *StmtContext) Error!?*Ast.Node { if (p.isScratchEmpty(context)) return null; const child = try p.collectStitch(context); if (child) |n| try p.scratch.append(p.gpa, n); const proto = p.scratch.items[p.knot_offset]; if (proto.tag != .knot_prototype) return null; const list = try p.nodeListFromScratch(p.knot_offset + 1, p.scratch.items.len); defer _ = p.popScratch(context); return .createKnot(p.arena, .knot_decl, .{ .start = proto.loc.start, .end = if (child) |n| n.loc.end else proto.loc.end, }, proto, list); } fn handleChoiceBranch(p: *Parse, context: *StmtContext, node: *Ast.Node) !void { const level = context.level; if (p.isBlockStackEmpty(context)) { // Always start with a block level of zero. try p.block_stack.append(p.gpa, .{ .level = 0, .scratch_offset = p.scratch.items.len, .source_offset = node.loc.start, }); } if (p.isChoiceStackEmpty(context)) { try p.choice_stack.append(p.gpa, .{ .level = level, .scratch_offset = p.scratch.items.len, .source_offset = node.loc.start, }); } else { const choice_state = p.peekChoiceStack(context); const block_state = p.peekBlockStack(context); if (level > choice_state.level) { if (block_state.level < choice_state.level) { try p.block_stack.append(p.gpa, .{ .level = choice_state.level, .scratch_offset = p.scratch.items.len, .source_offset = p.token.loc.start, }); } try p.choice_stack.append(p.gpa, .{ .level = level, .scratch_offset = p.scratch.items.len, .source_offset = node.loc.start, }); } else if (level == choice_state.level) { const t_node = try p.collectBlock(context, level); if (t_node) |n| try p.scratch.append(p.gpa, n); } else { const t_node = try p.collectContext(context, level, false); if (t_node) |n| try p.scratch.append(p.gpa, n); } } } fn handleGatherPoint(p: *Parse, context: *StmtContext, node: **Ast.Node) !void { const main_token = p.token; const level = context.level; if (p.isBlockStackEmpty(context)) { assert(p.isChoiceStackEmpty(context)); try p.block_stack.append(p.gpa, .{ .level = 0, .scratch_offset = p.scratch.items.len, .source_offset = node.*.loc.start, }); } // Gather points terminate compound statements at the appropriate level. if (!p.isChoiceStackEmpty(context)) { const choice_state = p.peekChoiceStack(context); const block_state = p.peekBlockStack(context); if (level > choice_state.level) { if (block_state.level != choice_state.level) { try p.block_stack.append(p.gpa, .{ .level = choice_state.level, .scratch_offset = p.scratch.items.len, .source_offset = node.*.loc.start, }); } } else if (!p.isScratchEmpty(context)) { const tmp = (try p.collectContext(context, level - 1, true)) orelse @panic("FUCK!"); if (tmp.tag == .choice_stmt) { node.* = try Ast.Node.createBinary(p.arena, .gathered_stmt, .{ .start = tmp.loc.start, .end = main_token.loc.start, }, tmp, node.*); } if (!p.isBlockStackEmpty(context)) { const b = p.peekBlockStack(context); if (b.level == level) { const tmp_2 = try p.collectBlock(context, level); if (tmp_2) |n| try p.scratch.append(p.gpa, n); } } } } } fn handleContentStmt(p: *Parse, context: *StmtContext, node: *Ast.Node) !void { if (p.isBlockStackEmpty(context)) { try p.block_stack.append(p.gpa, .{ .level = 0, .scratch_offset = p.scratch.items.len, .source_offset = node.loc.start, }); } if (!p.isChoiceStackEmpty(context)) { const block_state = p.peekBlockStack(context); const choice_state = p.peekChoiceStack(context); if (block_state.level != choice_state.level) { try p.block_stack.append(p.gpa, .{ .level = choice_state.level, .scratch_offset = p.scratch.items.len, .source_offset = node.loc.start, }); } } } fn handleConditionalBranch(p: *Parse, context: *StmtContext) Error!void { const node = try p.collectContext(context, 0, false); if (node) |n| try p.scratch.append(p.gpa, n); } fn handleKnotDecl(p: *Parse, context: *StmtContext) !void { const node = try p.collectKnot(context); if (node) |n| try p.scratch.append(p.gpa, n); p.knot_offset = p.scratch.items.len; } fn handleStitchDecl(p: *Parse, context: *StmtContext) !void { const node = try p.collectStitch(context); if (node) |n| try p.scratch.append(p.gpa, n); } fn handleFunctionDecl(p: *Parse, context: *StmtContext) !void { return p.handleStitchDecl(context); } fn expectExpr(p: *Parse) Error!*Ast.Node { const token = p.token; const node = try parseInfixExpr(p, null, .none); if (node) |n| return n; return p.fail(.expected_expression, token); } fn parseAtom(p: *Parse, tag: Ast.Node.Tag) Error!*Ast.Node { const main_token = p.nextToken(); return .createLeaf(p.arena, tag, .{ .start = main_token.loc.start, .end = main_token.loc.end, }); } fn parseIdentifier(p: *Parse) Error!*Ast.Node { return parseAtom(p, .identifier); } fn expectIdentifier(p: *Parse) Error!*Ast.Node { if (!p.checkToken(.identifier)) { return p.fail(.expected_identifier, p.token); } return parseIdentifier(p); } fn parsePrimaryExpr(p: *Parse) Error!?*Ast.Node { switch (p.token.tag) { .number_literal => return parseAtom(p, .number_literal), .keyword_true => return parseAtom(p, .true_literal), .keyword_false => return parseAtom(p, .false_literal), .identifier => return parseIdentifierExpr(p), .double_quote => return parseStringExpr(p), .left_paren => { _ = p.nextToken(); const node = try parseInfixExpr(p, null, .none); if (node == null) return null; _ = try p.expectToken(.right_paren, true); return node; }, else => return null, } } fn parsePrefixExpr(p: *Parse) Error!?*Ast.Node { switch (p.token.tag) { .keyword_not, .minus, .exclaimation_mark => { const main_token = p.nextToken(); const lhs = try parsePrefixExpr(p); if (lhs == null) return null; const tag = getTokenPrefixType(main_token.tag); return .createBinary(p.arena, tag, .{ .start = main_token.loc.start, .end = if (lhs) |n| n.loc.end else p.token.loc.end, }, lhs, null); }, else => return parsePrimaryExpr(p), } } fn parseInfixExpr( p: *Parse, prev_node: ?*Ast.Node, precedence: Precedence, ) Error!?*Ast.Node { var lhs = prev_node; if (lhs == null) { lhs = try parsePrefixExpr(p); if (lhs == null) return lhs; } while (true) { var next_node: ?*Ast.Node = null; const token = p.token; const token_precedence = getBindingPower(token.tag); if (@intFromEnum(token_precedence) > @intFromEnum(precedence)) { _ = p.nextToken(); next_node = try parseInfixExpr(p, null, token_precedence); if (next_node) |rhs| { const tag = getTokenInfixType(token.tag); lhs = try Ast.Node.createBinary(p.arena, tag, .{ .start = if (lhs) |n| n.loc.start else token.loc.start, .end = rhs.loc.end, }, lhs, rhs); } else return null; } else break; } return lhs; } fn parseExpression(p: *Parse) Error!?*Ast.Node { return parseInfixExpr(p, null, .none); } fn parseStringExpr(p: *Parse) Error!*Ast.Node { assert(p.token.tag == .double_quote); const main_token = p.nextToken(); while (true) switch (p.token.tag) { .double_quote, .newline, .eof => break, else => _ = p.nextToken(), }; const last_token = try p.expectToken(.double_quote, true); const expr = try Ast.Node.createLeaf(p.arena, .string_literal, .{ .start = main_token.loc.end, .end = last_token.loc.start, }); return .createBinary(p.arena, .string_expr, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, expr, null); } fn parseContentString(p: *Parse, token_set: []const Token.Tag) Error!?*Ast.Node { const main_token = p.token; while (!p.checkTokenInSet(token_set)) _ = p.nextToken(); return .createLeaf(p.arena, if (main_token.loc.start == p.token.loc.start) .empty_string else .string_literal, .{ .start = main_token.loc.start, .end = p.token.loc.start, }); } fn parseExprStmt(p: *Parse, lhs: ?*Ast.Node) Error!*Ast.Node { const main_token = p.token; const node = try parseInfixExpr(p, lhs, .none); _ = try p.expectNewline(); return .createBinary(p.arena, .expr_stmt, .{ .start = if (lhs) |n| n.loc.start else main_token.loc.start, .end = p.token.loc.start, }, node, null); } fn parseAssignStmt(p: *Parse) Error!*Ast.Node { const main_token = p.token; const lhs = try parseIdentifierExpr(p); if (!p.checkToken(.equal)) return parseExprStmt(p, lhs); _ = p.nextToken(); const rhs = try p.expectExpr(); _ = try p.expectNewline(); return .createBinary(p.arena, .assign_stmt, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, lhs, rhs); } fn parseTempDecl(p: *Parse) Error!*Ast.Node { const main_token = p.nextToken(); const lhs = try p.expectIdentifier(); _ = try p.expectToken(.equal, true); const rhs = try p.expectExpr(); _ = try p.expectNewline(); return .createBinary(p.arena, .temp_decl, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, lhs, rhs); } fn parseTildeStmt(p: *Parse) Error!*Ast.Node { try p.pushGrammar(.expression); defer p.popGrammar(); _ = p.nextToken(); const node = switch (p.token.tag) { .keyword_temp => try parseTempDecl(p), .keyword_return => try parseReturnStmt(p), .identifier => try parseAssignStmt(p), else => try parseExprStmt(p, null), }; return node; } fn parseReturnStmt(p: *Parse) Error!*Ast.Node { var node: ?*Ast.Node = null; const main_token = p.nextToken(); if (!p.checkToken(.newline) and !p.checkToken(.eof)) { node = try parseInfixExpr(p, null, .none); } _ = try p.expectNewline(); return .createBinary(p.arena, .return_stmt, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, node, null); } fn parseIdentifierExpr(p: *Parse) Error!*Ast.Node { var lhs = try p.expectIdentifier(); while (true) { switch (p.token.tag) { .dot => { _ = p.nextToken(); const rhs = try p.expectIdentifier(); lhs = try Ast.Node.createBinary(p.arena, .selector_expr, .{ .start = lhs.loc.start, .end = p.token.loc.start, }, lhs, rhs); }, .left_paren => { const rhs = try parseArgumentList(p); return .createBinary(p.arena, .call_expr, .{ .start = lhs.loc.start, .end = p.token.loc.start, }, lhs, rhs); }, else => return lhs, } } } fn parseDivertExpr(p: *Parse) Error!*Ast.Node { const main_token = p.nextToken(); const node = try parseIdentifierExpr(p); return .createBinary(p.arena, .divert_expr, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, node, null); } fn parseDivertStmt(p: *Parse) Error!*Ast.Node { try p.pushGrammar(.expression); defer p.popGrammar(); const main_token = p.token; const node = try parseDivertExpr(p); _ = try p.expectNewline(); return .createBinary(p.arena, .divert_stmt, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, node, null); } fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node { const token_set = [_]Token.Tag{ .left_brace, .left_bracket, .right_brace, .right_bracket, .right_arrow, .newline, .eof, }; const main_token = p.token; var lhs: ?*Ast.Node = null; var mhs: ?*Ast.Node = null; var rhs: ?*Ast.Node = null; lhs = try parseContentString(p, &token_set); if (lhs) |n| { if (n.tag != .empty_string) { n.tag = .choice_start_expr; } } if (p.checkToken(.left_bracket)) { _ = p.nextToken(); p.eatToken(.whitespace); if (!p.checkToken(.right_bracket)) { mhs = try parseContentString(p, &token_set); if (mhs) |n| { if (n.tag != .empty_string) { n.tag = .choice_option_expr; } } } _ = try p.expectToken(.right_bracket, false); if (!p.checkTokenInSet(&token_set)) { rhs = try parseContentString(p, &token_set); if (rhs) |n| { if (n.tag != .empty_string) { n.tag = .choice_inner_expr; } } } } return .createChoice(p.arena, .choice_expr, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, lhs, mhs, rhs); } fn parseChoiceStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { const main_token = p.token; const level = p.eatTokenLooped(main_token.tag, true); const node = try parseChoiceExpr(p); const span_end = if (node) |n| n.loc.end else p.token.loc.start; context.level = level; if (p.checkToken(.newline)) _ = p.nextToken(); return .createBinary(p.arena, getBranchTag(main_token.tag), .{ .start = main_token.loc.start, .end = span_end, }, node, null); } fn parseGatherPointStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { const main_token = p.token; const level = p.eatTokenLooped(main_token.tag, true); const span_end = p.token.loc.start; context.level = level; p.eatToken(.whitespace); p.eatToken(.newline); return .createBinary(p.arena, .gather_point_stmt, .{ .start = main_token.loc.start, .end = span_end, }, null, null); } fn parseConditional(p: *Parse, main_token: Token, expr: ?*Ast.Node) Error!?*Ast.Node { var context = p.makeStmtContext(.conditional, expr); p.eatToken(.whitespace); while (!p.checkToken(.eof) and !p.checkToken(.right_brace)) { const node = parseStmt(p, &context) catch |err| switch (err) { error.ParseError => { if (p.panic_mode) p.synchronize(); p.eatToken(.newline); continue; }, else => |e| return e, }; try p.scratch.append(p.gpa, node); } const end_token = try p.expectToken(.right_brace, true); const node = try p.collectContext(&context, 0, false); if (node) |n| try p.scratch.append(p.gpa, n); const list = try p.nodeListFromScratch(context.scratch_top, p.scratch.items.len); return .createSwitch(p.arena, if (expr != null and !context.is_block_created) .switch_stmt else if (expr == null and !context.is_block_created) .multi_if_stmt else .if_stmt, .{ .start = main_token.loc.start, .end = end_token.loc.start, }, expr, list); } fn parseInlineIf(p: *Parse, main_token: Token, lhs: ?*Ast.Node) Error!?*Ast.Node { const token_set = [_]Token.Tag{ .left_brace, .right_brace, .right_arrow, .glue, .newline, .eof, }; const scratch_top = p.scratch.items.len; p.eatToken(.whitespace); const content_node = try parseContentExpr(p, &token_set); const end_token = try p.expectToken(.right_brace, true); if (content_node) |n| try p.scratch.append(p.gpa, n); const list = try p.nodeListFromScratch(scratch_top, p.scratch.items.len); return try .createSwitch(p.arena, .if_stmt, .{ .start = main_token.loc.start, .end = end_token.loc.end, }, lhs, list); } fn parseLbraceExpr(p: *Parse) Error!?*Ast.Node { const lbrace_token = p.token; const lhs = n: { try p.pushGrammar(.expression); defer p.popGrammar(); _ = p.nextToken(); const node = try parseExpression(p); break :n node; }; if (lhs == null) { try p.pushGrammar(.content); defer p.popGrammar(); _ = try p.expectToken(.newline, true); return parseConditional(p, lbrace_token, null); } if (p.checkToken(.right_brace)) { const rbrace_token = p.nextToken(); return .createBinary(p.arena, .inline_logic_expr, .{ .start = lbrace_token.loc.start, .end = rbrace_token.loc.end, }, lhs, null); } _ = try p.expectToken(.colon, true); if (p.checkToken(.newline)) { _ = p.nextToken(); return parseConditional(p, lbrace_token, lhs); } else { _ = p.nextToken(); return parseInlineIf(p, lbrace_token, lhs); } } fn parseContentExpr(p: *Parse, token_set: []const Token.Tag) Error!?*Ast.Node { const main_token = p.token; const context = makeStmtContext(p, .block, null); while (true) { var node: ?*Ast.Node = null; if (!p.checkTokenInSet(token_set)) { node = try parseContentString(p, token_set); } else switch (p.token.tag) { .eof, .newline, .right_brace => break, .left_brace => node = try parseLbraceExpr(p), .right_arrow => node = try parseDivertStmt(p), //.INK_TT_GLUE => node = ink_parse_glue(p), else => { return p.fail(.unexpected_token, p.token); }, } if (node) |n| try p.scratch.append(p.gpa, n); } return p.makeNodeSequence(&context, .content, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, context.scratch_top); } fn parseContentStmt(p: *Parse) Error!*Ast.Node { const token_set = [_]Token.Tag{ .left_brace, .right_brace, .right_arrow, .glue, .newline, .eof, }; const main_token = p.token; const node = try parseContentExpr(p, &token_set); const end_token = try p.expectNewline(); return .createBinary(p.arena, .content_stmt, .{ .start = main_token.loc.start, .end = end_token.loc.start, }, node, null); } fn parseParameterDecl(p: *Parse) Error!*Ast.Node { const tag: Ast.Node.Tag = if (p.checkToken(.keyword_ref)) blk: { _ = p.nextToken(); break :blk .ref_parameter_decl; } else .parameter_decl; const node = try p.expectIdentifier(); node.tag = tag; return node; } fn parseParameterList(p: *Parse) Error!?*Ast.Node { const context = p.makeStmtContext(.block, null); const main_token = try p.expectToken(.left_paren, true); if (!p.checkToken(.right_paren)) { var cnt: usize = 0; while (true) : (cnt += 1) { if (cnt == p.max_argument_count) { return p.fail(.too_many_parameters, p.token); } const node = try parseParameterDecl(p); try p.scratch.append(p.gpa, node); if (p.checkToken(.comma)) { _ = p.nextToken(); } else break; } } _ = try p.expectToken(.right_paren, true); return p.makeNodeSequence(&context, .parameter_list, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, context.scratch_top); } fn parseArgumentList(p: *Parse) Error!?*Ast.Node { const context = p.makeStmtContext(.block, null); const main_token = try p.expectToken(.left_paren, true); if (!p.checkToken(.right_paren)) { var cnt: usize = 0; while (true) : (cnt += 1) { if (cnt == p.max_argument_count) { return p.fail(.too_many_arguments, p.token); } const node = try parseInfixExpr(p, null, .none); if (node) |n| try p.scratch.append(p.gpa, n); if (p.checkToken(.comma)) { _ = p.nextToken(); } else break; } } _ = try p.expectToken(.right_paren, false); return p.makeNodeSequence(&context, .argument_list, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, context.scratch_top); } fn parseConditionalBranch(p: *Parse, tag: Ast.Node.Tag) Error!?*Ast.Node { const main_token = p.token; var node_tag = tag; var node: ?*Ast.Node = null; if (p.checkToken(.keyword_else)) { _ = p.nextToken(); node_tag = .else_branch; } else { node = try parseExpression(p); if (node == null) return null; } if (!p.checkToken(.colon)) return null; _ = p.nextToken(); if (p.checkToken(.newline)) _ = p.nextToken(); return .createBinary(p.arena, node_tag, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, node, null); } fn parseConditionalStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { try p.pushGrammar(.expression); defer p.popGrammar(); _ = p.nextToken(); const node = try parseConditionalBranch(p, if (context.expression_node) |_| .switch_case else .if_branch); if (node) |n| return n; p.rewindGrammar(); try p.pushGrammar(.content); defer p.popGrammar(); _ = p.nextToken(); return parseGatherPointStmt(p, context); } fn parseVar(p: *Parse, tag: Ast.Node.Tag) Error!*Ast.Node { try p.pushGrammar(.expression); defer p.popGrammar(); const main_token = p.nextToken(); const lhs = try p.expectIdentifier(); _ = try p.expectToken(.equal, true); const rhs = try p.expectExpr(); const last_token = try p.expectNewline(); return .createBinary(p.arena, tag, .{ .start = main_token.loc.start, .end = last_token.loc.start, }, lhs, rhs); } fn parseVarDecl(p: *Parse) Error!*Ast.Node { assert(p.token.tag == .keyword_var); return parseVar(p, .var_decl); } fn parseConstDecl(p: *Parse) Error!*Ast.Node { assert(p.token.tag == .keyword_const); return parseVar(p, .const_decl); } fn parseKnotDecl(p: *Parse) Error!*Ast.Node { var tag: Ast.Node.Tag = .stitch_prototype; var lhs: ?*Ast.Node = null; var rhs: ?*Ast.Node = null; const main_token = p.nextToken(); try p.pushGrammar(.expression); defer p.popGrammar(); p.eatToken(.whitespace); if (p.checkToken(.equal)) { tag = .knot_prototype; while (p.checkToken(.equal)) { _ = p.nextToken(); } } if (p.checkToken(.keyword_function)) { _ = p.nextToken(); tag = .function_prototype; } lhs = try p.expectIdentifier(); if (p.checkToken(.left_paren)) { rhs = try parseParameterList(p); } while (p.checkToken(.equal) or p.checkToken(.equal_equal)) { _ = p.nextToken(); } _ = try p.expectNewline(); return .createBinary(p.arena, tag, .{ .start = main_token.loc.start, .end = p.token.loc.start, }, lhs, rhs); } fn parseStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node { p.eatToken(.whitespace); var node = switch (p.token.tag) { .star, .plus => try parseChoiceStmt(p, context), .minus => switch (context.tag) { .block => try parseGatherPointStmt(p, context), .conditional => try parseConditionalStmt(p, context), }, .equal, .equal_equal => switch (context.tag) { .block => try parseKnotDecl(p), else => try parseContentStmt(p), }, .tilde => try parseTildeStmt(p), .right_arrow => try parseDivertStmt(p), .right_brace => { const token = p.nextToken(); return p.fail(.unexpected_token, token); }, .keyword_const => try parseConstDecl(p), .keyword_var => try parseVarDecl(p), else => try parseContentStmt(p), }; p.eatToken(.newline); p.eatToken(.whitespace); switch (node.tag) { .if_branch, .else_branch, .switch_case => try p.handleConditionalBranch(context), .choice_star_stmt => try p.handleChoiceBranch(context, node), .choice_plus_stmt => try p.handleChoiceBranch(context, node), .gather_point_stmt => try p.handleGatherPoint(context, &node), .knot_prototype => try p.handleKnotDecl(context), .stitch_prototype => try p.handleStitchDecl(context), .function_prototype => try p.handleFunctionDecl(context), else => try p.handleContentStmt(context, node), } return node; } pub fn parseFile(p: *Parse) Error!*Ast.Node { var context = p.makeStmtContext(.block, null); try p.pushGrammar(.content); defer p.popGrammar(); const main_token = p.nextToken(); while (!p.checkToken(.eof)) { const node = parseStmt(p, &context) catch |err| switch (err) { error.ParseError => { if (p.panic_mode) p.synchronize(); p.eatToken(.newline); continue; }, error.OutOfMemory => @panic("Out of memory!"), }; try p.scratch.append(p.gpa, node); } const node = try p.collectKnot(&context); if (node) |n| try p.scratch.append(p.gpa, n); return p.makeNodeSequence(&context, .file, .{ .start = main_token.loc.start, .end = p.token.loc.end, }, context.scratch_top); }