feat: choice expression nodes extended to full content parsing

This commit is contained in:
Brett Broadhurst 2026-03-31 09:37:38 -06:00
parent 066369cc13
commit aad95a75ee
Failed to generate hash of commit
8 changed files with 380 additions and 165 deletions

View file

@ -186,7 +186,6 @@ fn eatTokenLooped(p: *Parse, tag: Token.Tag, ignore_whitespace: bool) usize {
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();
@ -809,22 +808,42 @@ fn parseDivertStmt(p: *Parse) Error!*Ast.Node {
fn parseChoiceExpr(p: *Parse) Error!?*Ast.Node {
const main_token = p.token;
var lhs: ?*Ast.Node = null;
var mhs: ?*Ast.Node = null;
var rhs: ?*Ast.Node = null;
lhs = try parseContent(p, .{ .ignore_brackets = false });
const lhs = try parseContentList(p, .{ .ignore_brackets = false });
if (lhs) |nodes| {
const last = nodes[nodes.len - 1];
if (last.data.content.trailing_divert != null) {
const end_token = try p.expectNewline();
return .createChoice(p.arena, .choice_expr, .{
.start = main_token.loc.start,
.end = end_token.loc.start,
}, lhs, null, null);
}
}
if (p.checkToken(.left_bracket)) {
_ = p.nextToken();
p.eatToken(.whitespace);
mhs = try parseContent(p, .{ .ignore_brackets = false });
const mhs = try parseContentList(p, .{ .ignore_brackets = false });
if (mhs) |nodes| {
const last = nodes[nodes.len - 1];
if (last.data.content.trailing_divert) |_| {
return p.fail(.unexpected_token, p.token);
}
}
_ = try p.expectToken(.right_bracket, false);
rhs = try parseContent(p, .{});
const rhs = try parseContentList(p, .{ .ignore_brackets = false });
return .createChoice(p.arena, .choice_expr, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, lhs, mhs, rhs);
}
return .createChoice(p.arena, .choice_expr, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, lhs, mhs, rhs);
}, lhs, null, null);
}
fn parseChoiceStmt(p: *Parse, context: *StmtContext) Error!*Ast.Node {
@ -888,15 +907,19 @@ fn parseConditional(p: *Parse, main_token: Token, expr: ?*Ast.Node) Error!?*Ast.
}, expr, list);
}
fn parseInlineIf(p: *Parse, main_token: Token, lhs: ?*Ast.Node) Error!?*Ast.Node {
fn parseInlineIf(p: *Parse, main_token: Token, lhs: ?*Ast.Node) Error!*Ast.Node {
p.eatToken(.whitespace);
const content = try parseContent(p, .{});
const content_node = switch (try parseContent(p, .{}, false)) {
.node, .split => |n| n,
.none => null,
};
const end_token = try p.expectToken(.right_brace, true);
return .createBinary(p.arena, .inline_if_stmt, .{
.start = main_token.loc.start,
.end = end_token.loc.end,
}, lhs, content);
}, lhs, content_node);
}
fn parseLbraceExpr(p: *Parse) Error!?*Ast.Node {
@ -935,11 +958,31 @@ fn parseLbraceExpr(p: *Parse) Error!?*Ast.Node {
const ContentOptions = struct {
ignore_brackets: bool = true,
ignore_parens: bool = true,
skip_leading_whitespace: bool = false,
skip_trailing_whitespace: bool = false,
};
const ContentResult = union(enum) {
node: *Ast.Node,
split: *Ast.Node,
none,
};
// TODO: This function is getting to be a mess. Refactor if possible.
fn parseContentString(p: *Parse, options: ContentOptions) Error!?*Ast.Node {
const main_token = p.token;
var end_pos = main_token.loc.start;
if (options.skip_leading_whitespace) {
while (p.token.tag == .whitespace) _ = p.nextToken();
switch (p.token.tag) {
.eof, .newline, .glue => return null,
else => {},
}
}
const start_pos = p.token.loc.start;
end_pos = start_pos;
while (true) {
switch (p.token.tag) {
@ -951,78 +994,172 @@ fn parseContentString(p: *Parse, options: ContentOptions) Error!?*Ast.Node {
.right_brace,
.glue,
=> break,
.left_bracket, .right_bracket => |tag| if (!options.ignore_brackets)
break
else
p.eatToken(tag),
else => |tag| p.eatToken(tag),
.left_bracket, .right_bracket => {
if (!options.ignore_brackets) break;
end_pos = p.token.loc.end;
_ = p.nextToken();
},
.whitespace => {
// TODO: Test for this.
if (options.skip_trailing_whitespace) {
const ws_end = p.token.loc.end;
_ = p.nextToken();
if (p.token.tag == .glue) break;
end_pos = ws_end;
} else {
end_pos = p.token.loc.end;
_ = p.nextToken();
}
},
else => {
end_pos = p.token.loc.end;
_ = p.nextToken();
},
}
}
if (main_token.loc.start == p.token.loc.start) return null;
if (start_pos == end_pos)
return null;
return .createLeaf(p.arena, .string_literal, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
.start = start_pos,
.end = end_pos,
});
}
fn parseContent(p: *Parse, options: ContentOptions) Error!?*Ast.Node {
// TODO: This function is getting to be a mess. Refactor if possible.
fn parseContent(
p: *Parse,
options: ContentOptions,
leading_glue: bool,
) Error!ContentResult {
const main_token = p.token;
const scratch_top = p.scratch.items.len;
var leading_glue = false;
var trailing_divert: ?*Ast.Node = null;
var trailing_glue = false;
var has_internal_leading_glue = false;
var is_split = false;
if (p.token.tag == .glue) {
leading_glue = true;
has_internal_leading_glue = true;
_ = p.nextToken();
}
if (options.skip_leading_whitespace and !has_internal_leading_glue) {
while (p.token.tag == .whitespace) _ = p.nextToken();
}
const effective_leading_glue = leading_glue or has_internal_leading_glue;
loop: while (true) {
const node: ?*Ast.Node = switch (p.token.tag) {
.eof, .newline, .left_arrow, .right_brace => break,
.eof,
.newline,
.left_arrow,
.right_brace,
=> break,
.left_brace => try parseLbraceExpr(p),
.right_arrow => try parseDivertExpr(p),
.glue => blk: {
while (true) {
_ = p.nextToken();
switch (p.token.tag) {
.whitespace => continue,
.eof, .newline, .right_brace => {
trailing_glue = true;
break :loop;
},
else => break :blk null,
}
.right_arrow => {
trailing_divert = try parseDivertExpr(p);
break :loop;
},
.glue => {
_ = p.nextToken();
switch (p.token.tag) {
.eof, .newline, .right_brace => {
trailing_glue = true;
break :loop;
},
else => {
is_split = true;
break :loop;
},
}
},
else => |tag| blk: {
switch (tag) {
.left_bracket, .right_bracket => if (!options.ignore_brackets) break,
else => {},
if (tag == .left_bracket or tag == .right_bracket) {
if (!options.ignore_brackets) break;
}
break :blk try parseContentString(p, options);
break :blk try parseContentString(p, .{
.ignore_brackets = options.ignore_brackets,
.skip_leading_whitespace = false,
.skip_trailing_whitespace = true,
});
},
};
if (node) |n| try p.scratch.append(p.gpa, n);
}
if (main_token.loc.start == p.token.loc.start) return null;
return .createContent(p.arena, .content, .{
if (main_token.loc.start == p.token.loc.start) return .none;
const items = try p.makeNodeSliceFromScratch(scratch_top);
const node = try Ast.Node.createContent(p.arena, .content, .{
.start = main_token.loc.start,
.end = p.token.loc.start,
}, .{
.items = try p.makeNodeSliceFromScratch(scratch_top),
.leading_glue = leading_glue,
.items = items,
.leading_glue = effective_leading_glue,
.trailing_glue = trailing_glue,
.trailing_divert = trailing_divert,
});
return if (is_split) .{ .split = node } else .{ .node = node };
}
fn parseContentList(
p: *Parse,
options: ContentOptions,
) Error!?[]*Ast.Node {
const scratch_top = p.scratch.items.len;
var has_leading_glue = false;
var is_first = true;
while (true) {
const segment_options: ContentOptions = .{
.ignore_brackets = options.ignore_brackets,
.skip_leading_whitespace = is_first and options.skip_leading_whitespace,
.skip_trailing_whitespace = true,
};
switch (try parseContent(p, segment_options, has_leading_glue)) {
.node => |n| {
try p.scratch.append(p.gpa, n);
break;
},
.split => |n| {
try p.scratch.append(p.gpa, n);
has_leading_glue = true;
},
.none => break,
}
is_first = false;
}
const items = try p.makeNodeSliceFromScratch(scratch_top);
return if (items.len > 0) items else null;
}
fn parseContentStmt(p: *Parse) Error!*Ast.Node {
const main_token = p.token;
const node = try parseContent(p, .{});
const context = makeStmtContext(p, .block, null);
var has_leading_glue = false;
while (true) {
switch (try parseContent(p, .{}, has_leading_glue)) {
.node => |n| {
try p.scratch.append(p.gpa, n);
break;
},
.split => |n| {
try p.scratch.append(p.gpa, n);
has_leading_glue = true;
},
.none => break,
}
}
const end_token = try p.expectNewline();
return .createBinary(p.arena, .content_stmt, .{
return p.makeNodeSlice(&context, .content_stmt, .{
.start = main_token.loc.start,
.end = end_token.loc.start,
}, node, null);
});
}
fn parseParameterDecl(p: *Parse) Error!*Ast.Node {