ink/src/Parse.zig

1261 lines
38 KiB
Zig

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);
}