feat: choice expression nodes extended to full content parsing
This commit is contained in:
parent
066369cc13
commit
aad95a75ee
8 changed files with 380 additions and 165 deletions
231
src/Parse.zig
231
src/Parse.zig
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue