3091 lines
104 KiB
Zig
3091 lines
104 KiB
Zig
const std = @import("../std.zig");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const ast = std.zig.ast;
|
|
const Node = ast.Node;
|
|
const Tree = ast.Tree;
|
|
const AstError = ast.Error;
|
|
const TokenIndex = ast.TokenIndex;
|
|
const Token = std.zig.Token;
|
|
const TokenIterator = Tree.TokenList.Iterator;
|
|
|
|
pub const Error = error{ParseError} || Allocator.Error;
|
|
|
|
/// Result should be freed with tree.deinit() when there are
|
|
/// no more references to any of the tokens or nodes.
|
|
pub fn parse(allocator: *Allocator, source: []const u8) Allocator.Error!*Tree {
|
|
const tree = blk: {
|
|
// This block looks unnecessary, but is a "foot-shield" to prevent the SegmentedLists
|
|
// from being initialized with a pointer to this `arena`, which is created on
|
|
// the stack. Following code should instead refer to `&tree.arena_allocator`, a
|
|
// pointer to data which lives safely on the heap and will outlive `parse`. See:
|
|
// https://github.com/ziglang/zig/commit/cb4fb14b6e66bd213575f69eec9598be8394fae6
|
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
|
errdefer arena.deinit();
|
|
const tree = try arena.allocator.create(ast.Tree);
|
|
tree.* = .{
|
|
.source = source,
|
|
.root_node = undefined,
|
|
.arena_allocator = arena,
|
|
.tokens = undefined,
|
|
.errors = undefined,
|
|
};
|
|
break :blk tree;
|
|
};
|
|
errdefer tree.deinit();
|
|
const arena = &tree.arena_allocator.allocator;
|
|
|
|
tree.tokens = ast.Tree.TokenList.init(arena);
|
|
tree.errors = ast.Tree.ErrorList.init(arena);
|
|
|
|
var tokenizer = std.zig.Tokenizer.init(source);
|
|
while (true) {
|
|
const tree_token = try tree.tokens.addOne();
|
|
tree_token.* = tokenizer.next();
|
|
if (tree_token.id == .Eof) break;
|
|
}
|
|
var it = tree.tokens.iterator(0);
|
|
|
|
while (it.peek().?.id == .LineComment) _ = it.next();
|
|
|
|
tree.root_node = parseRoot(arena, &it, tree) catch |err| blk: {
|
|
switch (err) {
|
|
error.ParseError => {
|
|
assert(tree.errors.len != 0);
|
|
break :blk undefined;
|
|
},
|
|
error.OutOfMemory => {
|
|
return error.OutOfMemory;
|
|
},
|
|
}
|
|
};
|
|
|
|
return tree;
|
|
}
|
|
|
|
/// Root <- skip ContainerMembers eof
|
|
fn parseRoot(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!*Node.Root {
|
|
const node = try arena.create(Node.Root);
|
|
node.* = .{
|
|
.decls = try parseContainerMembers(arena, it, tree),
|
|
.eof_token = eatToken(it, .Eof) orelse {
|
|
try tree.errors.push(.{
|
|
.ExpectedContainerMembers = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
},
|
|
};
|
|
return node;
|
|
}
|
|
|
|
/// ContainerMembers
|
|
/// <- TestDecl ContainerMembers
|
|
/// / TopLevelComptime ContainerMembers
|
|
/// / KEYWORD_pub? TopLevelDecl ContainerMembers
|
|
/// / KEYWORD_pub? ContainerField COMMA ContainerMembers
|
|
/// / KEYWORD_pub? ContainerField
|
|
/// /
|
|
fn parseContainerMembers(arena: *Allocator, it: *TokenIterator, tree: *Tree) !Node.Root.DeclList {
|
|
var list = Node.Root.DeclList.init(arena);
|
|
|
|
while (true) {
|
|
if (try parseContainerDocComments(arena, it, tree)) |node| {
|
|
try list.push(node);
|
|
continue;
|
|
}
|
|
|
|
const doc_comments = try parseDocComment(arena, it, tree);
|
|
|
|
if (try parseTestDecl(arena, it, tree)) |node| {
|
|
node.cast(Node.TestDecl).?.doc_comments = doc_comments;
|
|
try list.push(node);
|
|
continue;
|
|
}
|
|
|
|
if (try parseTopLevelComptime(arena, it, tree)) |node| {
|
|
node.cast(Node.Comptime).?.doc_comments = doc_comments;
|
|
try list.push(node);
|
|
continue;
|
|
}
|
|
|
|
const visib_token = eatToken(it, .Keyword_pub);
|
|
|
|
if (try parseTopLevelDecl(arena, it, tree)) |node| {
|
|
switch (node.id) {
|
|
.FnProto => {
|
|
node.cast(Node.FnProto).?.doc_comments = doc_comments;
|
|
node.cast(Node.FnProto).?.visib_token = visib_token;
|
|
},
|
|
.VarDecl => {
|
|
node.cast(Node.VarDecl).?.doc_comments = doc_comments;
|
|
node.cast(Node.VarDecl).?.visib_token = visib_token;
|
|
},
|
|
.Use => {
|
|
node.cast(Node.Use).?.doc_comments = doc_comments;
|
|
node.cast(Node.Use).?.visib_token = visib_token;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
try list.push(node);
|
|
if (try parseAppendedDocComment(arena, it, tree, node.lastToken())) |appended_comment| {
|
|
switch (node.id) {
|
|
.FnProto => {},
|
|
.VarDecl => node.cast(Node.VarDecl).?.doc_comments = appended_comment,
|
|
.Use => node.cast(Node.Use).?.doc_comments = appended_comment,
|
|
else => unreachable,
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (visib_token != null) {
|
|
try tree.errors.push(.{
|
|
.ExpectedPubItem = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
|
|
if (try parseContainerField(arena, it, tree)) |node| {
|
|
const field = node.cast(Node.ContainerField).?;
|
|
field.doc_comments = doc_comments;
|
|
try list.push(node);
|
|
const comma = eatToken(it, .Comma) orelse break;
|
|
if (try parseAppendedDocComment(arena, it, tree, comma)) |appended_comment|
|
|
field.doc_comments = appended_comment;
|
|
continue;
|
|
}
|
|
|
|
// Dangling doc comment
|
|
if (doc_comments != null) {
|
|
try tree.errors.push(.{
|
|
.UnattachedDocComment = .{ .token = doc_comments.?.firstToken() },
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
/// Eat a multiline container doc comment
|
|
fn parseContainerDocComments(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
var lines = Node.DocComment.LineList.init(arena);
|
|
while (eatToken(it, .ContainerDocComment)) |line| {
|
|
try lines.push(line);
|
|
}
|
|
|
|
if (lines.len == 0) return null;
|
|
|
|
const node = try arena.create(Node.DocComment);
|
|
node.* = .{
|
|
.lines = lines,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// TestDecl <- KEYWORD_test STRINGLITERALSINGLE Block
|
|
fn parseTestDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const test_token = eatToken(it, .Keyword_test) orelse return null;
|
|
const name_node = try expectNode(arena, it, tree, parseStringLiteralSingle, .{
|
|
.ExpectedStringLiteral = .{ .token = it.index },
|
|
});
|
|
const block_node = try expectNode(arena, it, tree, parseBlock, .{
|
|
.ExpectedLBrace = .{ .token = it.index },
|
|
});
|
|
|
|
const test_node = try arena.create(Node.TestDecl);
|
|
test_node.* = .{
|
|
.doc_comments = null,
|
|
.test_token = test_token,
|
|
.name = name_node,
|
|
.body_node = block_node,
|
|
};
|
|
return &test_node.base;
|
|
}
|
|
|
|
/// TopLevelComptime <- KEYWORD_comptime BlockExpr
|
|
fn parseTopLevelComptime(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const tok = eatToken(it, .Keyword_comptime) orelse return null;
|
|
const lbrace = eatToken(it, .LBrace) orelse {
|
|
putBackToken(it, tok);
|
|
return null;
|
|
};
|
|
putBackToken(it, lbrace);
|
|
const block_node = try expectNode(arena, it, tree, parseBlockExpr, .{
|
|
.ExpectedLabelOrLBrace = .{ .token = it.index },
|
|
});
|
|
|
|
const comptime_node = try arena.create(Node.Comptime);
|
|
comptime_node.* = .{
|
|
.doc_comments = null,
|
|
.comptime_token = tok,
|
|
.expr = block_node,
|
|
};
|
|
return &comptime_node.base;
|
|
}
|
|
|
|
/// TopLevelDecl
|
|
/// <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / (KEYWORD_inline / KEYWORD_noinline))? FnProto (SEMICOLON / Block)
|
|
/// / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl
|
|
/// / KEYWORD_usingnamespace Expr SEMICOLON
|
|
fn parseTopLevelDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
var lib_name: ?*Node = null;
|
|
const extern_export_inline_token = blk: {
|
|
if (eatToken(it, .Keyword_export)) |token| break :blk token;
|
|
if (eatToken(it, .Keyword_extern)) |token| {
|
|
lib_name = try parseStringLiteralSingle(arena, it, tree);
|
|
break :blk token;
|
|
}
|
|
if (eatToken(it, .Keyword_inline)) |token| break :blk token;
|
|
if (eatToken(it, .Keyword_noinline)) |token| break :blk token;
|
|
break :blk null;
|
|
};
|
|
|
|
if (try parseFnProto(arena, it, tree)) |node| {
|
|
const fn_node = node.cast(Node.FnProto).?;
|
|
fn_node.*.extern_export_inline_token = extern_export_inline_token;
|
|
fn_node.*.lib_name = lib_name;
|
|
if (eatToken(it, .Semicolon)) |_| return node;
|
|
if (try parseBlock(arena, it, tree)) |body_node| {
|
|
fn_node.body_node = body_node;
|
|
return node;
|
|
}
|
|
try tree.errors.push(.{
|
|
.ExpectedSemiOrLBrace = .{ .token = it.index },
|
|
});
|
|
return null;
|
|
}
|
|
|
|
if (extern_export_inline_token) |token| {
|
|
if (tree.tokens.at(token).id == .Keyword_inline or
|
|
tree.tokens.at(token).id == .Keyword_noinline)
|
|
{
|
|
putBackToken(it, token);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const thread_local_token = eatToken(it, .Keyword_threadlocal);
|
|
|
|
if (try parseVarDecl(arena, it, tree)) |node| {
|
|
var var_decl = node.cast(Node.VarDecl).?;
|
|
var_decl.*.thread_local_token = thread_local_token;
|
|
var_decl.*.comptime_token = null;
|
|
var_decl.*.extern_export_token = extern_export_inline_token;
|
|
var_decl.*.lib_name = lib_name;
|
|
return node;
|
|
}
|
|
|
|
if (thread_local_token != null) {
|
|
try tree.errors.push(.{
|
|
.ExpectedVarDecl = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
|
|
if (extern_export_inline_token) |token| {
|
|
if (lib_name) |string_literal_node|
|
|
putBackToken(it, string_literal_node.cast(Node.StringLiteral).?.token);
|
|
putBackToken(it, token);
|
|
return null;
|
|
}
|
|
|
|
const use_node = (try parseUse(arena, it, tree)) orelse return null;
|
|
const expr_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
const semicolon_token = try expectToken(it, tree, .Semicolon);
|
|
const use_node_raw = use_node.cast(Node.Use).?;
|
|
use_node_raw.*.expr = expr_node;
|
|
use_node_raw.*.semicolon_token = semicolon_token;
|
|
|
|
return use_node;
|
|
}
|
|
|
|
/// FnProto <- FnCC? KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_var / TypeExpr)
|
|
fn parseFnProto(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const cc = parseFnCC(arena, it, tree);
|
|
const fn_token = eatToken(it, .Keyword_fn) orelse {
|
|
if (cc) |fnCC| {
|
|
if (fnCC == .Extern) {
|
|
putBackToken(it, fnCC.Extern); // 'extern' is also used in ContainerDecl
|
|
} else {
|
|
try tree.errors.push(.{
|
|
.ExpectedToken = .{ .token = it.index, .expected_id = .Keyword_fn },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
const name_token = eatToken(it, .Identifier);
|
|
const lparen = try expectToken(it, tree, .LParen);
|
|
const params = try parseParamDeclList(arena, it, tree);
|
|
const rparen = try expectToken(it, tree, .RParen);
|
|
const align_expr = try parseByteAlign(arena, it, tree);
|
|
const section_expr = try parseLinkSection(arena, it, tree);
|
|
const callconv_expr = try parseCallconv(arena, it, tree);
|
|
const exclamation_token = eatToken(it, .Bang);
|
|
|
|
const return_type_expr = (try parseVarType(arena, it, tree)) orelse
|
|
try expectNode(arena, it, tree, parseTypeExpr, .{
|
|
.ExpectedReturnType = .{ .token = it.index },
|
|
});
|
|
|
|
const return_type: Node.FnProto.ReturnType = if (exclamation_token != null)
|
|
.{
|
|
.InferErrorSet = return_type_expr,
|
|
}
|
|
else
|
|
.{
|
|
.Explicit = return_type_expr,
|
|
};
|
|
|
|
const var_args_token = if (params.len > 0)
|
|
params.at(params.len - 1).*.cast(Node.ParamDecl).?.var_args_token
|
|
else
|
|
null;
|
|
|
|
const fn_proto_node = try arena.create(Node.FnProto);
|
|
fn_proto_node.* = .{
|
|
.doc_comments = null,
|
|
.visib_token = null,
|
|
.fn_token = fn_token,
|
|
.name_token = name_token,
|
|
.params = params,
|
|
.return_type = return_type,
|
|
.var_args_token = var_args_token,
|
|
.extern_export_inline_token = null,
|
|
.cc_token = null,
|
|
.body_node = null,
|
|
.lib_name = null,
|
|
.align_expr = align_expr,
|
|
.section_expr = section_expr,
|
|
.callconv_expr = callconv_expr,
|
|
};
|
|
|
|
if (cc) |kind| {
|
|
switch (kind) {
|
|
.CC => |token| fn_proto_node.cc_token = token,
|
|
.Extern => |token| fn_proto_node.extern_export_inline_token = token,
|
|
}
|
|
}
|
|
|
|
return &fn_proto_node.base;
|
|
}
|
|
|
|
/// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON
|
|
fn parseVarDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const mut_token = eatToken(it, .Keyword_const) orelse
|
|
eatToken(it, .Keyword_var) orelse
|
|
return null;
|
|
|
|
const name_token = try expectToken(it, tree, .Identifier);
|
|
const type_node = if (eatToken(it, .Colon) != null)
|
|
try expectNode(arena, it, tree, parseTypeExpr, .{
|
|
.ExpectedTypeExpr = .{ .token = it.index },
|
|
})
|
|
else
|
|
null;
|
|
const align_node = try parseByteAlign(arena, it, tree);
|
|
const section_node = try parseLinkSection(arena, it, tree);
|
|
const eq_token = eatToken(it, .Equal);
|
|
const init_node = if (eq_token != null) blk: {
|
|
break :blk try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
} else null;
|
|
const semicolon_token = try expectToken(it, tree, .Semicolon);
|
|
|
|
const node = try arena.create(Node.VarDecl);
|
|
node.* = .{
|
|
.doc_comments = null,
|
|
.visib_token = null,
|
|
.thread_local_token = null,
|
|
.name_token = name_token,
|
|
.eq_token = eq_token,
|
|
.mut_token = mut_token,
|
|
.comptime_token = null,
|
|
.extern_export_token = null,
|
|
.lib_name = null,
|
|
.type_node = type_node,
|
|
.align_node = align_node,
|
|
.section_node = section_node,
|
|
.init_node = init_node,
|
|
.semicolon_token = semicolon_token,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// ContainerField <- KEYWORD_comptime? IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)?
|
|
fn parseContainerField(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const comptime_token = eatToken(it, .Keyword_comptime);
|
|
const name_token = eatToken(it, .Identifier) orelse {
|
|
if (comptime_token) |t| putBackToken(it, t);
|
|
return null;
|
|
};
|
|
|
|
var align_expr: ?*Node = null;
|
|
var type_expr: ?*Node = null;
|
|
if (eatToken(it, .Colon)) |_| {
|
|
if (eatToken(it, .Keyword_var)) |var_tok| {
|
|
const node = try arena.create(ast.Node.VarType);
|
|
node.* = .{ .token = var_tok };
|
|
type_expr = &node.base;
|
|
} else {
|
|
type_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
|
|
.ExpectedTypeExpr = .{ .token = it.index },
|
|
});
|
|
align_expr = try parseByteAlign(arena, it, tree);
|
|
}
|
|
}
|
|
|
|
const value_expr = if (eatToken(it, .Equal)) |_|
|
|
try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
})
|
|
else
|
|
null;
|
|
|
|
const node = try arena.create(Node.ContainerField);
|
|
node.* = .{
|
|
.doc_comments = null,
|
|
.comptime_token = comptime_token,
|
|
.name_token = name_token,
|
|
.type_expr = type_expr,
|
|
.value_expr = value_expr,
|
|
.align_expr = align_expr,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// Statement
|
|
/// <- KEYWORD_comptime? VarDecl
|
|
/// / KEYWORD_comptime BlockExprStatement
|
|
/// / KEYWORD_noasync BlockExprStatement
|
|
/// / KEYWORD_suspend (SEMICOLON / BlockExprStatement)
|
|
/// / KEYWORD_defer BlockExprStatement
|
|
/// / KEYWORD_errdefer BlockExprStatement
|
|
/// / IfStatement
|
|
/// / LabeledStatement
|
|
/// / SwitchExpr
|
|
/// / AssignExpr SEMICOLON
|
|
fn parseStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
|
|
const comptime_token = eatToken(it, .Keyword_comptime);
|
|
|
|
const var_decl_node = try parseVarDecl(arena, it, tree);
|
|
if (var_decl_node) |node| {
|
|
const var_decl = node.cast(Node.VarDecl).?;
|
|
var_decl.comptime_token = comptime_token;
|
|
return node;
|
|
}
|
|
|
|
if (comptime_token) |token| {
|
|
const block_expr = try expectNode(arena, it, tree, parseBlockExprStatement, .{
|
|
.ExpectedBlockOrAssignment = .{ .token = it.index },
|
|
});
|
|
|
|
const node = try arena.create(Node.Comptime);
|
|
node.* = .{
|
|
.doc_comments = null,
|
|
.comptime_token = token,
|
|
.expr = block_expr,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (eatToken(it, .Keyword_noasync)) |noasync_token| {
|
|
const block_expr = try expectNode(arena, it, tree, parseBlockExprStatement, .{
|
|
.ExpectedBlockOrAssignment = .{ .token = it.index },
|
|
});
|
|
|
|
const node = try arena.create(Node.Noasync);
|
|
node.* = .{
|
|
.noasync_token = noasync_token,
|
|
.expr = block_expr,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (eatToken(it, .Keyword_suspend)) |suspend_token| {
|
|
const semicolon = eatToken(it, .Semicolon);
|
|
|
|
const body_node = if (semicolon == null) blk: {
|
|
break :blk try expectNode(arena, it, tree, parseBlockExprStatement, .{
|
|
.ExpectedBlockOrExpression = .{ .token = it.index },
|
|
});
|
|
} else null;
|
|
|
|
const node = try arena.create(Node.Suspend);
|
|
node.* = .{
|
|
.suspend_token = suspend_token,
|
|
.body = body_node,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
const defer_token = eatToken(it, .Keyword_defer) orelse eatToken(it, .Keyword_errdefer);
|
|
if (defer_token) |token| {
|
|
const expr_node = try expectNode(arena, it, tree, parseBlockExprStatement, .{
|
|
.ExpectedBlockOrExpression = .{ .token = it.index },
|
|
});
|
|
const node = try arena.create(Node.Defer);
|
|
node.* = .{
|
|
.defer_token = token,
|
|
.expr = expr_node,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (try parseIfStatement(arena, it, tree)) |node| return node;
|
|
if (try parseLabeledStatement(arena, it, tree)) |node| return node;
|
|
if (try parseSwitchExpr(arena, it, tree)) |node| return node;
|
|
if (try parseAssignExpr(arena, it, tree)) |node| {
|
|
_ = try expectToken(it, tree, .Semicolon);
|
|
return node;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// IfStatement
|
|
/// <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
|
|
/// / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
|
|
fn parseIfStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const if_node = (try parseIfPrefix(arena, it, tree)) orelse return null;
|
|
const if_prefix = if_node.cast(Node.If).?;
|
|
|
|
const block_expr = (try parseBlockExpr(arena, it, tree));
|
|
const assign_expr = if (block_expr == null) blk: {
|
|
break :blk (try parseAssignExpr(arena, it, tree)) orelse null;
|
|
} else null;
|
|
|
|
if (block_expr == null and assign_expr == null) {
|
|
try tree.errors.push(.{
|
|
.ExpectedBlockOrAssignment = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
|
|
const semicolon = if (assign_expr != null) eatToken(it, .Semicolon) else null;
|
|
|
|
const else_node = if (semicolon == null) blk: {
|
|
const else_token = eatToken(it, .Keyword_else) orelse break :blk null;
|
|
const payload = try parsePayload(arena, it, tree);
|
|
const else_body = try expectNode(arena, it, tree, parseStatement, .{
|
|
.InvalidToken = .{ .token = it.index },
|
|
});
|
|
|
|
const node = try arena.create(Node.Else);
|
|
node.* = .{
|
|
.else_token = else_token,
|
|
.payload = payload,
|
|
.body = else_body,
|
|
};
|
|
|
|
break :blk node;
|
|
} else null;
|
|
|
|
if (block_expr) |body| {
|
|
if_prefix.body = body;
|
|
if_prefix.@"else" = else_node;
|
|
return if_node;
|
|
}
|
|
|
|
if (assign_expr) |body| {
|
|
if_prefix.body = body;
|
|
if (semicolon != null) return if_node;
|
|
if (else_node != null) {
|
|
if_prefix.@"else" = else_node;
|
|
return if_node;
|
|
}
|
|
try tree.errors.push(.{
|
|
.ExpectedSemiOrElse = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
|
|
unreachable;
|
|
}
|
|
|
|
/// LabeledStatement <- BlockLabel? (Block / LoopStatement)
|
|
fn parseLabeledStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
var colon: TokenIndex = undefined;
|
|
const label_token = parseBlockLabel(arena, it, tree, &colon);
|
|
|
|
if (try parseBlock(arena, it, tree)) |node| {
|
|
node.cast(Node.Block).?.label = label_token;
|
|
return node;
|
|
}
|
|
|
|
if (try parseLoopStatement(arena, it, tree)) |node| {
|
|
if (node.cast(Node.For)) |for_node| {
|
|
for_node.label = label_token;
|
|
} else if (node.cast(Node.While)) |while_node| {
|
|
while_node.label = label_token;
|
|
} else unreachable;
|
|
return node;
|
|
}
|
|
|
|
if (label_token != null) {
|
|
try tree.errors.push(.{
|
|
.ExpectedLabelable = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)
|
|
fn parseLoopStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const inline_token = eatToken(it, .Keyword_inline);
|
|
|
|
if (try parseForStatement(arena, it, tree)) |node| {
|
|
node.cast(Node.For).?.inline_token = inline_token;
|
|
return node;
|
|
}
|
|
|
|
if (try parseWhileStatement(arena, it, tree)) |node| {
|
|
node.cast(Node.While).?.inline_token = inline_token;
|
|
return node;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// ForStatement
|
|
/// <- ForPrefix BlockExpr ( KEYWORD_else Statement )?
|
|
/// / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )
|
|
fn parseForStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const node = (try parseForPrefix(arena, it, tree)) orelse return null;
|
|
const for_prefix = node.cast(Node.For).?;
|
|
|
|
if (try parseBlockExpr(arena, it, tree)) |block_expr_node| {
|
|
for_prefix.body = block_expr_node;
|
|
|
|
if (eatToken(it, .Keyword_else)) |else_token| {
|
|
const statement_node = try expectNode(arena, it, tree, parseStatement, .{
|
|
.InvalidToken = .{ .token = it.index },
|
|
});
|
|
|
|
const else_node = try arena.create(Node.Else);
|
|
else_node.* = .{
|
|
.else_token = else_token,
|
|
.payload = null,
|
|
.body = statement_node,
|
|
};
|
|
for_prefix.@"else" = else_node;
|
|
|
|
return node;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
if (try parseAssignExpr(arena, it, tree)) |assign_expr| {
|
|
for_prefix.body = assign_expr;
|
|
|
|
if (eatToken(it, .Semicolon) != null) return node;
|
|
|
|
if (eatToken(it, .Keyword_else)) |else_token| {
|
|
const statement_node = try expectNode(arena, it, tree, parseStatement, .{
|
|
.ExpectedStatement = .{ .token = it.index },
|
|
});
|
|
|
|
const else_node = try arena.create(Node.Else);
|
|
else_node.* = .{
|
|
.else_token = else_token,
|
|
.payload = null,
|
|
.body = statement_node,
|
|
};
|
|
for_prefix.@"else" = else_node;
|
|
return node;
|
|
}
|
|
|
|
try tree.errors.push(.{
|
|
.ExpectedSemiOrElse = .{ .token = it.index },
|
|
});
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// WhileStatement
|
|
/// <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
|
|
/// / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
|
|
fn parseWhileStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const node = (try parseWhilePrefix(arena, it, tree)) orelse return null;
|
|
const while_prefix = node.cast(Node.While).?;
|
|
|
|
if (try parseBlockExpr(arena, it, tree)) |block_expr_node| {
|
|
while_prefix.body = block_expr_node;
|
|
|
|
if (eatToken(it, .Keyword_else)) |else_token| {
|
|
const payload = try parsePayload(arena, it, tree);
|
|
|
|
const statement_node = try expectNode(arena, it, tree, parseStatement, .{
|
|
.InvalidToken = .{ .token = it.index },
|
|
});
|
|
|
|
const else_node = try arena.create(Node.Else);
|
|
else_node.* = .{
|
|
.else_token = else_token,
|
|
.payload = payload,
|
|
.body = statement_node,
|
|
};
|
|
while_prefix.@"else" = else_node;
|
|
|
|
return node;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
if (try parseAssignExpr(arena, it, tree)) |assign_expr_node| {
|
|
while_prefix.body = assign_expr_node;
|
|
|
|
if (eatToken(it, .Semicolon) != null) return node;
|
|
|
|
if (eatToken(it, .Keyword_else)) |else_token| {
|
|
const payload = try parsePayload(arena, it, tree);
|
|
|
|
const statement_node = try expectNode(arena, it, tree, parseStatement, .{
|
|
.ExpectedStatement = .{ .token = it.index },
|
|
});
|
|
|
|
const else_node = try arena.create(Node.Else);
|
|
else_node.* = .{
|
|
.else_token = else_token,
|
|
.payload = payload,
|
|
.body = statement_node,
|
|
};
|
|
while_prefix.@"else" = else_node;
|
|
return node;
|
|
}
|
|
|
|
try tree.errors.push(.{
|
|
.ExpectedSemiOrElse = .{ .token = it.index },
|
|
});
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// BlockExprStatement
|
|
/// <- BlockExpr
|
|
/// / AssignExpr SEMICOLON
|
|
fn parseBlockExprStatement(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
if (try parseBlockExpr(arena, it, tree)) |node| return node;
|
|
if (try parseAssignExpr(arena, it, tree)) |node| {
|
|
_ = try expectToken(it, tree, .Semicolon);
|
|
return node;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// BlockExpr <- BlockLabel? Block
|
|
fn parseBlockExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
|
|
var colon: TokenIndex = undefined;
|
|
const label_token = parseBlockLabel(arena, it, tree, &colon);
|
|
const block_node = (try parseBlock(arena, it, tree)) orelse {
|
|
if (label_token) |label| {
|
|
putBackToken(it, label + 1); // ":"
|
|
putBackToken(it, label); // IDENTIFIER
|
|
}
|
|
return null;
|
|
};
|
|
block_node.cast(Node.Block).?.label = label_token;
|
|
return block_node;
|
|
}
|
|
|
|
/// AssignExpr <- Expr (AssignOp Expr)?
|
|
fn parseAssignExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseBinOpExpr(arena, it, tree, parseAssignOp, parseExpr, .Once);
|
|
}
|
|
|
|
/// Expr <- KEYWORD_try* BoolOrExpr
|
|
fn parseExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
|
|
return parsePrefixOpExpr(arena, it, tree, parseTry, parseBoolOrExpr);
|
|
}
|
|
|
|
/// BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)*
|
|
fn parseBoolOrExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseBinOpExpr(
|
|
arena,
|
|
it,
|
|
tree,
|
|
SimpleBinOpParseFn(.Keyword_or, Node.InfixOp.Op.BoolOr),
|
|
parseBoolAndExpr,
|
|
.Infinitely,
|
|
);
|
|
}
|
|
|
|
/// BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)*
|
|
fn parseBoolAndExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseBinOpExpr(
|
|
arena,
|
|
it,
|
|
tree,
|
|
SimpleBinOpParseFn(.Keyword_and, .BoolAnd),
|
|
parseCompareExpr,
|
|
.Infinitely,
|
|
);
|
|
}
|
|
|
|
/// CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?
|
|
fn parseCompareExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseBinOpExpr(arena, it, tree, parseCompareOp, parseBitwiseExpr, .Once);
|
|
}
|
|
|
|
/// BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)*
|
|
fn parseBitwiseExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseBinOpExpr(arena, it, tree, parseBitwiseOp, parseBitShiftExpr, .Infinitely);
|
|
}
|
|
|
|
/// BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)*
|
|
fn parseBitShiftExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseBinOpExpr(arena, it, tree, parseBitShiftOp, parseAdditionExpr, .Infinitely);
|
|
}
|
|
|
|
/// AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)*
|
|
fn parseAdditionExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseBinOpExpr(arena, it, tree, parseAdditionOp, parseMultiplyExpr, .Infinitely);
|
|
}
|
|
|
|
/// MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)*
|
|
fn parseMultiplyExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseBinOpExpr(arena, it, tree, parseMultiplyOp, parsePrefixExpr, .Infinitely);
|
|
}
|
|
|
|
/// PrefixExpr <- PrefixOp* PrimaryExpr
|
|
fn parsePrefixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parsePrefixOpExpr(arena, it, tree, parsePrefixOp, parsePrimaryExpr);
|
|
}
|
|
|
|
/// PrimaryExpr
|
|
/// <- AsmExpr
|
|
/// / IfExpr
|
|
/// / KEYWORD_break BreakLabel? Expr?
|
|
/// / KEYWORD_comptime Expr
|
|
/// / KEYWORD_noasync Expr
|
|
/// / KEYWORD_continue BreakLabel?
|
|
/// / KEYWORD_resume Expr
|
|
/// / KEYWORD_return Expr?
|
|
/// / BlockLabel? LoopExpr
|
|
/// / Block
|
|
/// / CurlySuffixExpr
|
|
fn parsePrimaryExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
if (try parseAsmExpr(arena, it, tree)) |node| return node;
|
|
if (try parseIfExpr(arena, it, tree)) |node| return node;
|
|
|
|
if (eatToken(it, .Keyword_break)) |token| {
|
|
const label = try parseBreakLabel(arena, it, tree);
|
|
const expr_node = try parseExpr(arena, it, tree);
|
|
const node = try arena.create(Node.ControlFlowExpression);
|
|
node.* = .{
|
|
.ltoken = token,
|
|
.kind = Node.ControlFlowExpression.Kind{ .Break = label },
|
|
.rhs = expr_node,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (eatToken(it, .Keyword_comptime)) |token| {
|
|
const expr_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
const node = try arena.create(Node.Comptime);
|
|
node.* = .{
|
|
.doc_comments = null,
|
|
.comptime_token = token,
|
|
.expr = expr_node,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (eatToken(it, .Keyword_noasync)) |token| {
|
|
const expr_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
const node = try arena.create(Node.Noasync);
|
|
node.* = .{
|
|
.noasync_token = token,
|
|
.expr = expr_node,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (eatToken(it, .Keyword_continue)) |token| {
|
|
const label = try parseBreakLabel(arena, it, tree);
|
|
const node = try arena.create(Node.ControlFlowExpression);
|
|
node.* = .{
|
|
.ltoken = token,
|
|
.kind = Node.ControlFlowExpression.Kind{ .Continue = label },
|
|
.rhs = null,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (eatToken(it, .Keyword_resume)) |token| {
|
|
const expr_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
const node = try arena.create(Node.PrefixOp);
|
|
node.* = .{
|
|
.op_token = token,
|
|
.op = .Resume,
|
|
.rhs = expr_node,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (eatToken(it, .Keyword_return)) |token| {
|
|
const expr_node = try parseExpr(arena, it, tree);
|
|
const node = try arena.create(Node.ControlFlowExpression);
|
|
node.* = .{
|
|
.ltoken = token,
|
|
.kind = Node.ControlFlowExpression.Kind.Return,
|
|
.rhs = expr_node,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
var colon: TokenIndex = undefined;
|
|
const label = parseBlockLabel(arena, it, tree, &colon);
|
|
if (try parseLoopExpr(arena, it, tree)) |node| {
|
|
if (node.cast(Node.For)) |for_node| {
|
|
for_node.label = label;
|
|
} else if (node.cast(Node.While)) |while_node| {
|
|
while_node.label = label;
|
|
} else unreachable;
|
|
return node;
|
|
}
|
|
if (label) |token| {
|
|
putBackToken(it, token + 1); // ":"
|
|
putBackToken(it, token); // IDENTIFIER
|
|
}
|
|
|
|
if (try parseBlock(arena, it, tree)) |node| return node;
|
|
if (try parseCurlySuffixExpr(arena, it, tree)) |node| return node;
|
|
|
|
return null;
|
|
}
|
|
|
|
/// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
|
|
fn parseIfExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseIf(arena, it, tree, parseExpr);
|
|
}
|
|
|
|
/// Block <- LBRACE Statement* RBRACE
|
|
fn parseBlock(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const lbrace = eatToken(it, .LBrace) orelse return null;
|
|
|
|
var statements = Node.Block.StatementList.init(arena);
|
|
while (true) {
|
|
const statement = (try parseStatement(arena, it, tree)) orelse break;
|
|
try statements.push(statement);
|
|
}
|
|
|
|
const rbrace = try expectToken(it, tree, .RBrace);
|
|
|
|
const block_node = try arena.create(Node.Block);
|
|
block_node.* = .{
|
|
.label = null,
|
|
.lbrace = lbrace,
|
|
.statements = statements,
|
|
.rbrace = rbrace,
|
|
};
|
|
|
|
return &block_node.base;
|
|
}
|
|
|
|
/// LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
|
|
fn parseLoopExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const inline_token = eatToken(it, .Keyword_inline);
|
|
|
|
if (try parseForExpr(arena, it, tree)) |node| {
|
|
node.cast(Node.For).?.inline_token = inline_token;
|
|
return node;
|
|
}
|
|
|
|
if (try parseWhileExpr(arena, it, tree)) |node| {
|
|
node.cast(Node.While).?.inline_token = inline_token;
|
|
return node;
|
|
}
|
|
|
|
if (inline_token == null) return null;
|
|
|
|
// If we've seen "inline", there should have been a "for" or "while"
|
|
try tree.errors.push(.{
|
|
.ExpectedInlinable = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
|
|
/// ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?
|
|
fn parseForExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const node = (try parseForPrefix(arena, it, tree)) orelse return null;
|
|
const for_prefix = node.cast(Node.For).?;
|
|
|
|
const body_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
for_prefix.body = body_node;
|
|
|
|
if (eatToken(it, .Keyword_else)) |else_token| {
|
|
const body = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
|
|
const else_node = try arena.create(Node.Else);
|
|
else_node.* = .{
|
|
.else_token = else_token,
|
|
.payload = null,
|
|
.body = body,
|
|
};
|
|
|
|
for_prefix.@"else" = else_node;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/// WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
|
|
fn parseWhileExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const node = (try parseWhilePrefix(arena, it, tree)) orelse return null;
|
|
const while_prefix = node.cast(Node.While).?;
|
|
|
|
const body_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
while_prefix.body = body_node;
|
|
|
|
if (eatToken(it, .Keyword_else)) |else_token| {
|
|
const payload = try parsePayload(arena, it, tree);
|
|
const body = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
|
|
const else_node = try arena.create(Node.Else);
|
|
else_node.* = .{
|
|
.else_token = else_token,
|
|
.payload = payload,
|
|
.body = body,
|
|
};
|
|
|
|
while_prefix.@"else" = else_node;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/// CurlySuffixExpr <- TypeExpr InitList?
|
|
fn parseCurlySuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const type_expr = (try parseTypeExpr(arena, it, tree)) orelse return null;
|
|
const suffix_op = (try parseInitList(arena, it, tree)) orelse return type_expr;
|
|
suffix_op.lhs.node = type_expr;
|
|
return &suffix_op.base;
|
|
}
|
|
|
|
/// InitList
|
|
/// <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
|
|
/// / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
|
|
/// / LBRACE RBRACE
|
|
fn parseInitList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node.SuffixOp {
|
|
const lbrace = eatToken(it, .LBrace) orelse return null;
|
|
var init_list = Node.SuffixOp.Op.InitList.init(arena);
|
|
|
|
const op: Node.SuffixOp.Op = blk: {
|
|
if (try parseFieldInit(arena, it, tree)) |field_init| {
|
|
try init_list.push(field_init);
|
|
while (eatToken(it, .Comma)) |_| {
|
|
const next = (try parseFieldInit(arena, it, tree)) orelse break;
|
|
try init_list.push(next);
|
|
}
|
|
break :blk .{ .StructInitializer = init_list };
|
|
}
|
|
|
|
if (try parseExpr(arena, it, tree)) |expr| {
|
|
try init_list.push(expr);
|
|
while (eatToken(it, .Comma)) |_| {
|
|
const next = (try parseExpr(arena, it, tree)) orelse break;
|
|
try init_list.push(next);
|
|
}
|
|
break :blk .{ .ArrayInitializer = init_list };
|
|
}
|
|
|
|
break :blk .{ .StructInitializer = init_list };
|
|
};
|
|
|
|
const node = try arena.create(Node.SuffixOp);
|
|
node.* = .{
|
|
.lhs = .{ .node = undefined }, // set by caller
|
|
.op = op,
|
|
.rtoken = try expectToken(it, tree, .RBrace),
|
|
};
|
|
return node;
|
|
}
|
|
|
|
/// TypeExpr <- PrefixTypeOp* ErrorUnionExpr
|
|
fn parseTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
|
|
return parsePrefixOpExpr(arena, it, tree, parsePrefixTypeOp, parseErrorUnionExpr);
|
|
}
|
|
|
|
/// ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
|
|
fn parseErrorUnionExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const suffix_expr = (try parseSuffixExpr(arena, it, tree)) orelse return null;
|
|
|
|
if (try SimpleBinOpParseFn(.Bang, Node.InfixOp.Op.ErrorUnion)(arena, it, tree)) |node| {
|
|
const error_union = node.cast(Node.InfixOp).?;
|
|
const type_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
|
|
.ExpectedTypeExpr = .{ .token = it.index },
|
|
});
|
|
error_union.lhs = suffix_expr;
|
|
error_union.rhs = type_expr;
|
|
return node;
|
|
}
|
|
|
|
return suffix_expr;
|
|
}
|
|
|
|
/// SuffixExpr
|
|
/// <- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
|
|
/// / PrimaryTypeExpr (SuffixOp / FnCallArguments)*
|
|
fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const maybe_async = eatToken(it, .Keyword_async);
|
|
if (maybe_async) |async_token| {
|
|
const token_fn = eatToken(it, .Keyword_fn);
|
|
if (token_fn != null) {
|
|
// HACK: If we see the keyword `fn`, then we assume that
|
|
// we are parsing an async fn proto, and not a call.
|
|
// We therefore put back all tokens consumed by the async
|
|
// prefix...
|
|
putBackToken(it, token_fn.?);
|
|
putBackToken(it, async_token);
|
|
return parsePrimaryTypeExpr(arena, it, tree);
|
|
}
|
|
// TODO: Implement hack for parsing `async fn ...` in ast_parse_suffix_expr
|
|
var res = try expectNode(arena, it, tree, parsePrimaryTypeExpr, .{
|
|
.ExpectedPrimaryTypeExpr = .{ .token = it.index },
|
|
});
|
|
|
|
while (try parseSuffixOp(arena, it, tree)) |node| {
|
|
switch (node.id) {
|
|
.SuffixOp => node.cast(Node.SuffixOp).?.lhs = .{ .node = res },
|
|
.InfixOp => node.cast(Node.InfixOp).?.lhs = res,
|
|
else => unreachable,
|
|
}
|
|
res = node;
|
|
}
|
|
|
|
const params = (try parseFnCallArguments(arena, it, tree)) orelse {
|
|
try tree.errors.push(.{
|
|
.ExpectedParamList = .{ .token = it.index },
|
|
});
|
|
return null;
|
|
};
|
|
const node = try arena.create(Node.SuffixOp);
|
|
node.* = .{
|
|
.lhs = .{ .node = res },
|
|
.op = .{
|
|
.Call = .{
|
|
.params = params.list,
|
|
.async_token = async_token,
|
|
},
|
|
},
|
|
.rtoken = params.rparen,
|
|
};
|
|
return &node.base;
|
|
}
|
|
if (try parsePrimaryTypeExpr(arena, it, tree)) |expr| {
|
|
var res = expr;
|
|
|
|
while (true) {
|
|
if (try parseSuffixOp(arena, it, tree)) |node| {
|
|
switch (node.id) {
|
|
.SuffixOp => node.cast(Node.SuffixOp).?.lhs = .{ .node = res },
|
|
.InfixOp => node.cast(Node.InfixOp).?.lhs = res,
|
|
else => unreachable,
|
|
}
|
|
res = node;
|
|
continue;
|
|
}
|
|
if (try parseFnCallArguments(arena, it, tree)) |params| {
|
|
const call = try arena.create(Node.SuffixOp);
|
|
call.* = .{
|
|
.lhs = .{ .node = res },
|
|
.op = .{
|
|
.Call = .{
|
|
.params = params.list,
|
|
.async_token = null,
|
|
},
|
|
},
|
|
.rtoken = params.rparen,
|
|
};
|
|
res = &call.base;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// PrimaryTypeExpr
|
|
/// <- BUILTINIDENTIFIER FnCallArguments
|
|
/// / CHAR_LITERAL
|
|
/// / ContainerDecl
|
|
/// / DOT IDENTIFIER
|
|
/// / ErrorSetDecl
|
|
/// / FLOAT
|
|
/// / FnProto
|
|
/// / GroupedExpr
|
|
/// / LabeledTypeExpr
|
|
/// / IDENTIFIER
|
|
/// / IfTypeExpr
|
|
/// / INTEGER
|
|
/// / KEYWORD_comptime TypeExpr
|
|
/// / KEYWORD_noasync TypeExpr
|
|
/// / KEYWORD_error DOT IDENTIFIER
|
|
/// / KEYWORD_false
|
|
/// / KEYWORD_null
|
|
/// / KEYWORD_anyframe
|
|
/// / KEYWORD_true
|
|
/// / KEYWORD_undefined
|
|
/// / KEYWORD_unreachable
|
|
/// / STRINGLITERAL
|
|
/// / SwitchExpr
|
|
fn parsePrimaryTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
if (try parseBuiltinCall(arena, it, tree)) |node| return node;
|
|
if (eatToken(it, .CharLiteral)) |token| {
|
|
const node = try arena.create(Node.CharLiteral);
|
|
node.* = .{
|
|
.token = token,
|
|
};
|
|
return &node.base;
|
|
}
|
|
if (try parseContainerDecl(arena, it, tree)) |node| return node;
|
|
if (try parseAnonLiteral(arena, it, tree)) |node| return node;
|
|
if (try parseErrorSetDecl(arena, it, tree)) |node| return node;
|
|
if (try parseFloatLiteral(arena, it, tree)) |node| return node;
|
|
if (try parseFnProto(arena, it, tree)) |node| return node;
|
|
if (try parseGroupedExpr(arena, it, tree)) |node| return node;
|
|
if (try parseLabeledTypeExpr(arena, it, tree)) |node| return node;
|
|
if (try parseIdentifier(arena, it, tree)) |node| return node;
|
|
if (try parseIfTypeExpr(arena, it, tree)) |node| return node;
|
|
if (try parseIntegerLiteral(arena, it, tree)) |node| return node;
|
|
if (eatToken(it, .Keyword_comptime)) |token| {
|
|
const expr = (try parseTypeExpr(arena, it, tree)) orelse return null;
|
|
const node = try arena.create(Node.Comptime);
|
|
node.* = .{
|
|
.doc_comments = null,
|
|
.comptime_token = token,
|
|
.expr = expr,
|
|
};
|
|
return &node.base;
|
|
}
|
|
if (eatToken(it, .Keyword_noasync)) |token| {
|
|
const expr = (try parseTypeExpr(arena, it, tree)) orelse return null;
|
|
const node = try arena.create(Node.Noasync);
|
|
node.* = .{
|
|
.noasync_token = token,
|
|
.expr = expr,
|
|
};
|
|
return &node.base;
|
|
}
|
|
if (eatToken(it, .Keyword_error)) |token| {
|
|
const period = try expectToken(it, tree, .Period);
|
|
const identifier = try expectNode(arena, it, tree, parseIdentifier, .{
|
|
.ExpectedIdentifier = .{ .token = it.index },
|
|
});
|
|
const global_error_set = try createLiteral(arena, Node.ErrorType, token);
|
|
const node = try arena.create(Node.InfixOp);
|
|
node.* = .{
|
|
.op_token = period,
|
|
.lhs = global_error_set,
|
|
.op = .Period,
|
|
.rhs = identifier,
|
|
};
|
|
return &node.base;
|
|
}
|
|
if (eatToken(it, .Keyword_false)) |token| return createLiteral(arena, Node.BoolLiteral, token);
|
|
if (eatToken(it, .Keyword_null)) |token| return createLiteral(arena, Node.NullLiteral, token);
|
|
if (eatToken(it, .Keyword_anyframe)) |token| {
|
|
const node = try arena.create(Node.AnyFrameType);
|
|
node.* = .{
|
|
.anyframe_token = token,
|
|
.result = null,
|
|
};
|
|
return &node.base;
|
|
}
|
|
if (eatToken(it, .Keyword_true)) |token| return createLiteral(arena, Node.BoolLiteral, token);
|
|
if (eatToken(it, .Keyword_undefined)) |token| return createLiteral(arena, Node.UndefinedLiteral, token);
|
|
if (eatToken(it, .Keyword_unreachable)) |token| return createLiteral(arena, Node.Unreachable, token);
|
|
if (try parseStringLiteral(arena, it, tree)) |node| return node;
|
|
if (try parseSwitchExpr(arena, it, tree)) |node| return node;
|
|
|
|
return null;
|
|
}
|
|
|
|
/// ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto
|
|
fn parseContainerDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const layout_token = eatToken(it, .Keyword_extern) orelse
|
|
eatToken(it, .Keyword_packed);
|
|
|
|
const node = (try parseContainerDeclAuto(arena, it, tree)) orelse {
|
|
if (layout_token) |token|
|
|
putBackToken(it, token);
|
|
return null;
|
|
};
|
|
node.cast(Node.ContainerDecl).?.*.layout_token = layout_token;
|
|
return node;
|
|
}
|
|
|
|
/// ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
|
|
fn parseErrorSetDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const error_token = eatToken(it, .Keyword_error) orelse return null;
|
|
if (eatToken(it, .LBrace) == null) {
|
|
// Might parse as `KEYWORD_error DOT IDENTIFIER` later in PrimaryTypeExpr, so don't error
|
|
putBackToken(it, error_token);
|
|
return null;
|
|
}
|
|
const decls = try parseErrorTagList(arena, it, tree);
|
|
const rbrace = try expectToken(it, tree, .RBrace);
|
|
|
|
const node = try arena.create(Node.ErrorSetDecl);
|
|
node.* = .{
|
|
.error_token = error_token,
|
|
.decls = decls,
|
|
.rbrace_token = rbrace,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// GroupedExpr <- LPAREN Expr RPAREN
|
|
fn parseGroupedExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const lparen = eatToken(it, .LParen) orelse return null;
|
|
const expr = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
const rparen = try expectToken(it, tree, .RParen);
|
|
|
|
const node = try arena.create(Node.GroupedExpression);
|
|
node.* = .{
|
|
.lparen = lparen,
|
|
.expr = expr,
|
|
.rparen = rparen,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
|
|
fn parseIfTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
return parseIf(arena, it, tree, parseTypeExpr);
|
|
}
|
|
|
|
/// LabeledTypeExpr
|
|
/// <- BlockLabel Block
|
|
/// / BlockLabel? LoopTypeExpr
|
|
fn parseLabeledTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
var colon: TokenIndex = undefined;
|
|
const label = parseBlockLabel(arena, it, tree, &colon);
|
|
|
|
if (label) |token| {
|
|
if (try parseBlock(arena, it, tree)) |node| {
|
|
node.cast(Node.Block).?.label = token;
|
|
return node;
|
|
}
|
|
}
|
|
|
|
if (try parseLoopTypeExpr(arena, it, tree)) |node| {
|
|
switch (node.id) {
|
|
.For => node.cast(Node.For).?.label = label,
|
|
.While => node.cast(Node.While).?.label = label,
|
|
else => unreachable,
|
|
}
|
|
return node;
|
|
}
|
|
|
|
if (label) |token| {
|
|
putBackToken(it, colon);
|
|
putBackToken(it, token);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
|
|
fn parseLoopTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const inline_token = eatToken(it, .Keyword_inline);
|
|
|
|
if (try parseForTypeExpr(arena, it, tree)) |node| {
|
|
node.cast(Node.For).?.inline_token = inline_token;
|
|
return node;
|
|
}
|
|
|
|
if (try parseWhileTypeExpr(arena, it, tree)) |node| {
|
|
node.cast(Node.While).?.inline_token = inline_token;
|
|
return node;
|
|
}
|
|
|
|
if (inline_token == null) return null;
|
|
|
|
// If we've seen "inline", there should have been a "for" or "while"
|
|
try tree.errors.push(.{
|
|
.ExpectedInlinable = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
|
|
/// ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?
|
|
fn parseForTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const node = (try parseForPrefix(arena, it, tree)) orelse return null;
|
|
const for_prefix = node.cast(Node.For).?;
|
|
|
|
const type_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
|
|
.ExpectedTypeExpr = .{ .token = it.index },
|
|
});
|
|
for_prefix.body = type_expr;
|
|
|
|
if (eatToken(it, .Keyword_else)) |else_token| {
|
|
const else_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
|
|
.ExpectedTypeExpr = .{ .token = it.index },
|
|
});
|
|
|
|
const else_node = try arena.create(Node.Else);
|
|
else_node.* = .{
|
|
.else_token = else_token,
|
|
.payload = null,
|
|
.body = else_expr,
|
|
};
|
|
|
|
for_prefix.@"else" = else_node;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/// WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
|
|
fn parseWhileTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const node = (try parseWhilePrefix(arena, it, tree)) orelse return null;
|
|
const while_prefix = node.cast(Node.While).?;
|
|
|
|
const type_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
|
|
.ExpectedTypeExpr = .{ .token = it.index },
|
|
});
|
|
while_prefix.body = type_expr;
|
|
|
|
if (eatToken(it, .Keyword_else)) |else_token| {
|
|
const payload = try parsePayload(arena, it, tree);
|
|
|
|
const else_expr = try expectNode(arena, it, tree, parseTypeExpr, .{
|
|
.ExpectedTypeExpr = .{ .token = it.index },
|
|
});
|
|
|
|
const else_node = try arena.create(Node.Else);
|
|
else_node.* = .{
|
|
.else_token = else_token,
|
|
.payload = null,
|
|
.body = else_expr,
|
|
};
|
|
|
|
while_prefix.@"else" = else_node;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/// SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
|
|
fn parseSwitchExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const switch_token = eatToken(it, .Keyword_switch) orelse return null;
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const expr_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
_ = try expectToken(it, tree, .LBrace);
|
|
const cases = try parseSwitchProngList(arena, it, tree);
|
|
const rbrace = try expectToken(it, tree, .RBrace);
|
|
|
|
const node = try arena.create(Node.Switch);
|
|
node.* = .{
|
|
.switch_token = switch_token,
|
|
.expr = expr_node,
|
|
.cases = cases,
|
|
.rbrace = rbrace,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN
|
|
fn parseAsmExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const asm_token = eatToken(it, .Keyword_asm) orelse return null;
|
|
const volatile_token = eatToken(it, .Keyword_volatile);
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const template = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
|
|
const node = try arena.create(Node.Asm);
|
|
node.* = .{
|
|
.asm_token = asm_token,
|
|
.volatile_token = volatile_token,
|
|
.template = template,
|
|
.outputs = Node.Asm.OutputList.init(arena),
|
|
.inputs = Node.Asm.InputList.init(arena),
|
|
.clobbers = Node.Asm.ClobberList.init(arena),
|
|
.rparen = undefined,
|
|
};
|
|
|
|
try parseAsmOutput(arena, it, tree, node);
|
|
node.rparen = try expectToken(it, tree, .RParen);
|
|
return &node.base;
|
|
}
|
|
|
|
/// DOT IDENTIFIER
|
|
fn parseAnonLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const dot = eatToken(it, .Period) orelse return null;
|
|
|
|
// anon enum literal
|
|
if (eatToken(it, .Identifier)) |name| {
|
|
const node = try arena.create(Node.EnumLiteral);
|
|
node.* = .{
|
|
.dot = dot,
|
|
.name = name,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
// anon container literal
|
|
if (try parseInitList(arena, it, tree)) |node| {
|
|
node.lhs = .{ .dot = dot };
|
|
return &node.base;
|
|
}
|
|
|
|
putBackToken(it, dot);
|
|
return null;
|
|
}
|
|
|
|
/// AsmOutput <- COLON AsmOutputList AsmInput?
|
|
fn parseAsmOutput(arena: *Allocator, it: *TokenIterator, tree: *Tree, asm_node: *Node.Asm) !void {
|
|
if (eatToken(it, .Colon) == null) return;
|
|
asm_node.outputs = try parseAsmOutputList(arena, it, tree);
|
|
try parseAsmInput(arena, it, tree, asm_node);
|
|
}
|
|
|
|
/// AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
|
|
fn parseAsmOutputItem(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node.AsmOutput {
|
|
const lbracket = eatToken(it, .LBracket) orelse return null;
|
|
const name = try expectNode(arena, it, tree, parseIdentifier, .{
|
|
.ExpectedIdentifier = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RBracket);
|
|
|
|
const constraint = try expectNode(arena, it, tree, parseStringLiteral, .{
|
|
.ExpectedStringLiteral = .{ .token = it.index },
|
|
});
|
|
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const kind: Node.AsmOutput.Kind = blk: {
|
|
if (eatToken(it, .Arrow) != null) {
|
|
const return_ident = try expectNode(arena, it, tree, parseTypeExpr, .{
|
|
.ExpectedTypeExpr = .{ .token = it.index },
|
|
});
|
|
break :blk .{ .Return = return_ident };
|
|
}
|
|
const variable = try expectNode(arena, it, tree, parseIdentifier, .{
|
|
.ExpectedIdentifier = .{ .token = it.index },
|
|
});
|
|
break :blk .{ .Variable = variable.cast(Node.Identifier).? };
|
|
};
|
|
const rparen = try expectToken(it, tree, .RParen);
|
|
|
|
const node = try arena.create(Node.AsmOutput);
|
|
node.* = .{
|
|
.lbracket = lbracket,
|
|
.symbolic_name = name,
|
|
.constraint = constraint,
|
|
.kind = kind,
|
|
.rparen = rparen,
|
|
};
|
|
return node;
|
|
}
|
|
|
|
/// AsmInput <- COLON AsmInputList AsmClobbers?
|
|
fn parseAsmInput(arena: *Allocator, it: *TokenIterator, tree: *Tree, asm_node: *Node.Asm) !void {
|
|
if (eatToken(it, .Colon) == null) return;
|
|
asm_node.inputs = try parseAsmInputList(arena, it, tree);
|
|
try parseAsmClobbers(arena, it, tree, asm_node);
|
|
}
|
|
|
|
/// AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN
|
|
fn parseAsmInputItem(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node.AsmInput {
|
|
const lbracket = eatToken(it, .LBracket) orelse return null;
|
|
const name = try expectNode(arena, it, tree, parseIdentifier, .{
|
|
.ExpectedIdentifier = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RBracket);
|
|
|
|
const constraint = try expectNode(arena, it, tree, parseStringLiteral, .{
|
|
.ExpectedStringLiteral = .{ .token = it.index },
|
|
});
|
|
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const expr = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
const rparen = try expectToken(it, tree, .RParen);
|
|
|
|
const node = try arena.create(Node.AsmInput);
|
|
node.* = .{
|
|
.lbracket = lbracket,
|
|
.symbolic_name = name,
|
|
.constraint = constraint,
|
|
.expr = expr,
|
|
.rparen = rparen,
|
|
};
|
|
return node;
|
|
}
|
|
|
|
/// AsmClobbers <- COLON StringList
|
|
/// StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?
|
|
fn parseAsmClobbers(arena: *Allocator, it: *TokenIterator, tree: *Tree, asm_node: *Node.Asm) !void {
|
|
if (eatToken(it, .Colon) == null) return;
|
|
asm_node.clobbers = try ListParseFn(
|
|
Node.Asm.ClobberList,
|
|
parseStringLiteral,
|
|
)(arena, it, tree);
|
|
}
|
|
|
|
/// BreakLabel <- COLON IDENTIFIER
|
|
fn parseBreakLabel(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
_ = eatToken(it, .Colon) orelse return null;
|
|
return try expectNode(arena, it, tree, parseIdentifier, .{
|
|
.ExpectedIdentifier = .{ .token = it.index },
|
|
});
|
|
}
|
|
|
|
/// BlockLabel <- IDENTIFIER COLON
|
|
fn parseBlockLabel(arena: *Allocator, it: *TokenIterator, tree: *Tree, colon_token: *TokenIndex) ?TokenIndex {
|
|
const identifier = eatToken(it, .Identifier) orelse return null;
|
|
if (eatToken(it, .Colon)) |colon| {
|
|
colon_token.* = colon;
|
|
return identifier;
|
|
}
|
|
putBackToken(it, identifier);
|
|
return null;
|
|
}
|
|
|
|
/// FieldInit <- DOT IDENTIFIER EQUAL Expr
|
|
fn parseFieldInit(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const period_token = eatToken(it, .Period) orelse return null;
|
|
const name_token = eatToken(it, .Identifier) orelse {
|
|
// Because of anon literals `.{` is also valid.
|
|
putBackToken(it, period_token);
|
|
return null;
|
|
};
|
|
const eq_token = eatToken(it, .Equal) orelse {
|
|
// `.Name` may also be an enum literal, which is a later rule.
|
|
putBackToken(it, name_token);
|
|
putBackToken(it, period_token);
|
|
return null;
|
|
};
|
|
const expr_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
|
|
const node = try arena.create(Node.FieldInitializer);
|
|
node.* = .{
|
|
.period_token = period_token,
|
|
.name_token = name_token,
|
|
.expr = expr_node,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN
|
|
fn parseWhileContinueExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
_ = eatToken(it, .Colon) orelse return null;
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const node = try expectNode(arena, it, tree, parseAssignExpr, .{
|
|
.ExpectedExprOrAssignment = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
return node;
|
|
}
|
|
|
|
/// LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN
|
|
fn parseLinkSection(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
_ = eatToken(it, .Keyword_linksection) orelse return null;
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const expr_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
return expr_node;
|
|
}
|
|
|
|
/// CallConv <- KEYWORD_callconv LPAREN Expr RPAREN
|
|
fn parseCallconv(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
_ = eatToken(it, .Keyword_callconv) orelse return null;
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const expr_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
return expr_node;
|
|
}
|
|
|
|
/// FnCC
|
|
/// <- KEYWORD_nakedcc
|
|
/// / KEYWORD_stdcallcc
|
|
/// / KEYWORD_extern
|
|
/// / KEYWORD_async
|
|
fn parseFnCC(arena: *Allocator, it: *TokenIterator, tree: *Tree) ?FnCC {
|
|
if (eatToken(it, .Keyword_nakedcc)) |token| return FnCC{ .CC = token };
|
|
if (eatToken(it, .Keyword_stdcallcc)) |token| return FnCC{ .CC = token };
|
|
if (eatToken(it, .Keyword_extern)) |token| return FnCC{ .Extern = token };
|
|
if (eatToken(it, .Keyword_async)) |token| return FnCC{ .CC = token };
|
|
return null;
|
|
}
|
|
|
|
const FnCC = union(enum) {
|
|
CC: TokenIndex,
|
|
Extern: TokenIndex,
|
|
};
|
|
|
|
/// ParamDecl <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
|
|
fn parseParamDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const doc_comments = try parseDocComment(arena, it, tree);
|
|
const noalias_token = eatToken(it, .Keyword_noalias);
|
|
const comptime_token = if (noalias_token == null) eatToken(it, .Keyword_comptime) else null;
|
|
const name_token = blk: {
|
|
const identifier = eatToken(it, .Identifier) orelse break :blk null;
|
|
if (eatToken(it, .Colon) != null) break :blk identifier;
|
|
putBackToken(it, identifier); // ParamType may also be an identifier
|
|
break :blk null;
|
|
};
|
|
const param_type = (try parseParamType(arena, it, tree)) orelse {
|
|
// Only return cleanly if no keyword, identifier, or doc comment was found
|
|
if (noalias_token == null and
|
|
comptime_token == null and
|
|
name_token == null and
|
|
doc_comments == null) return null;
|
|
try tree.errors.push(.{
|
|
.ExpectedParamType = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
};
|
|
|
|
const param_decl = try arena.create(Node.ParamDecl);
|
|
param_decl.* = .{
|
|
.doc_comments = doc_comments,
|
|
.comptime_token = comptime_token,
|
|
.noalias_token = noalias_token,
|
|
.name_token = name_token,
|
|
// TODO: These should be squished into a ParamType enum
|
|
.type_node = undefined,
|
|
.var_args_token = null,
|
|
};
|
|
switch (param_type) {
|
|
.VarType => |node| param_decl.type_node = node,
|
|
.TypeExpr => |node| param_decl.type_node = node,
|
|
.VarArgs => |token| param_decl.var_args_token = token,
|
|
}
|
|
return ¶m_decl.base;
|
|
}
|
|
|
|
/// ParamType
|
|
/// <- KEYWORD_var
|
|
/// / DOT3
|
|
/// / TypeExpr
|
|
fn parseParamType(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?ParamType {
|
|
if (try parseVarType(arena, it, tree)) |node| return ParamType{ .VarType = node };
|
|
if (eatToken(it, .Ellipsis3)) |token| return ParamType{ .VarArgs = token };
|
|
if (try parseTypeExpr(arena, it, tree)) |node| return ParamType{ .TypeExpr = node };
|
|
return null;
|
|
}
|
|
|
|
// TODO: Move to ast.Node.ParamDecl.ParamType
|
|
const ParamType = union(enum) {
|
|
VarType: *Node,
|
|
VarArgs: TokenIndex,
|
|
TypeExpr: *Node,
|
|
};
|
|
|
|
/// IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload?
|
|
fn parseIfPrefix(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const if_token = eatToken(it, .Keyword_if) orelse return null;
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const condition = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
const payload = try parsePtrPayload(arena, it, tree);
|
|
|
|
const node = try arena.create(Node.If);
|
|
node.* = .{
|
|
.if_token = if_token,
|
|
.condition = condition,
|
|
.payload = payload,
|
|
.body = undefined, // set by caller
|
|
.@"else" = null,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
|
|
fn parseWhilePrefix(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const while_token = eatToken(it, .Keyword_while) orelse return null;
|
|
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const condition = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
|
|
const payload = try parsePtrPayload(arena, it, tree);
|
|
const continue_expr = try parseWhileContinueExpr(arena, it, tree);
|
|
|
|
const node = try arena.create(Node.While);
|
|
node.* = .{
|
|
.label = null,
|
|
.inline_token = null,
|
|
.while_token = while_token,
|
|
.condition = condition,
|
|
.payload = payload,
|
|
.continue_expr = continue_expr,
|
|
.body = undefined, // set by caller
|
|
.@"else" = null,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// ForPrefix <- KEYWORD_for LPAREN Expr RPAREN PtrIndexPayload
|
|
fn parseForPrefix(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const for_token = eatToken(it, .Keyword_for) orelse return null;
|
|
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const array_expr = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
|
|
const payload = try expectNode(arena, it, tree, parsePtrIndexPayload, .{
|
|
.ExpectedPayload = .{ .token = it.index },
|
|
});
|
|
|
|
const node = try arena.create(Node.For);
|
|
node.* = .{
|
|
.label = null,
|
|
.inline_token = null,
|
|
.for_token = for_token,
|
|
.array_expr = array_expr,
|
|
.payload = payload,
|
|
.body = undefined, // set by caller
|
|
.@"else" = null,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// Payload <- PIPE IDENTIFIER PIPE
|
|
fn parsePayload(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const lpipe = eatToken(it, .Pipe) orelse return null;
|
|
const identifier = try expectNode(arena, it, tree, parseIdentifier, .{
|
|
.ExpectedIdentifier = .{ .token = it.index },
|
|
});
|
|
const rpipe = try expectToken(it, tree, .Pipe);
|
|
|
|
const node = try arena.create(Node.Payload);
|
|
node.* = .{
|
|
.lpipe = lpipe,
|
|
.error_symbol = identifier,
|
|
.rpipe = rpipe,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE
|
|
fn parsePtrPayload(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const lpipe = eatToken(it, .Pipe) orelse return null;
|
|
const asterisk = eatToken(it, .Asterisk);
|
|
const identifier = try expectNode(arena, it, tree, parseIdentifier, .{
|
|
.ExpectedIdentifier = .{ .token = it.index },
|
|
});
|
|
const rpipe = try expectToken(it, tree, .Pipe);
|
|
|
|
const node = try arena.create(Node.PointerPayload);
|
|
node.* = .{
|
|
.lpipe = lpipe,
|
|
.ptr_token = asterisk,
|
|
.value_symbol = identifier,
|
|
.rpipe = rpipe,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE
|
|
fn parsePtrIndexPayload(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const lpipe = eatToken(it, .Pipe) orelse return null;
|
|
const asterisk = eatToken(it, .Asterisk);
|
|
const identifier = try expectNode(arena, it, tree, parseIdentifier, .{
|
|
.ExpectedIdentifier = .{ .token = it.index },
|
|
});
|
|
|
|
const index = if (eatToken(it, .Comma) == null)
|
|
null
|
|
else
|
|
try expectNode(arena, it, tree, parseIdentifier, .{
|
|
.ExpectedIdentifier = .{ .token = it.index },
|
|
});
|
|
|
|
const rpipe = try expectToken(it, tree, .Pipe);
|
|
|
|
const node = try arena.create(Node.PointerIndexPayload);
|
|
node.* = .{
|
|
.lpipe = lpipe,
|
|
.ptr_token = asterisk,
|
|
.value_symbol = identifier,
|
|
.index_symbol = index,
|
|
.rpipe = rpipe,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr
|
|
fn parseSwitchProng(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const node = (try parseSwitchCase(arena, it, tree)) orelse return null;
|
|
const arrow = try expectToken(it, tree, .EqualAngleBracketRight);
|
|
const payload = try parsePtrPayload(arena, it, tree);
|
|
const expr = try expectNode(arena, it, tree, parseAssignExpr, .{
|
|
.ExpectedExprOrAssignment = .{ .token = it.index },
|
|
});
|
|
|
|
const switch_case = node.cast(Node.SwitchCase).?;
|
|
switch_case.arrow_token = arrow;
|
|
switch_case.payload = payload;
|
|
switch_case.expr = expr;
|
|
|
|
return node;
|
|
}
|
|
|
|
/// SwitchCase
|
|
/// <- SwitchItem (COMMA SwitchItem)* COMMA?
|
|
/// / KEYWORD_else
|
|
fn parseSwitchCase(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
var list = Node.SwitchCase.ItemList.init(arena);
|
|
|
|
if (try parseSwitchItem(arena, it, tree)) |first_item| {
|
|
try list.push(first_item);
|
|
while (eatToken(it, .Comma) != null) {
|
|
const next_item = (try parseSwitchItem(arena, it, tree)) orelse break;
|
|
try list.push(next_item);
|
|
}
|
|
} else if (eatToken(it, .Keyword_else)) |else_token| {
|
|
const else_node = try arena.create(Node.SwitchElse);
|
|
else_node.* = .{
|
|
.token = else_token,
|
|
};
|
|
try list.push(&else_node.base);
|
|
} else return null;
|
|
|
|
const node = try arena.create(Node.SwitchCase);
|
|
node.* = .{
|
|
.items = list,
|
|
.arrow_token = undefined, // set by caller
|
|
.payload = null,
|
|
.expr = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// SwitchItem <- Expr (DOT3 Expr)?
|
|
fn parseSwitchItem(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const expr = (try parseExpr(arena, it, tree)) orelse return null;
|
|
if (eatToken(it, .Ellipsis3)) |token| {
|
|
const range_end = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
|
|
const node = try arena.create(Node.InfixOp);
|
|
node.* = .{
|
|
.op_token = token,
|
|
.lhs = expr,
|
|
.op = .Range,
|
|
.rhs = range_end,
|
|
};
|
|
return &node.base;
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
/// AssignOp
|
|
/// <- ASTERISKEQUAL
|
|
/// / SLASHEQUAL
|
|
/// / PERCENTEQUAL
|
|
/// / PLUSEQUAL
|
|
/// / MINUSEQUAL
|
|
/// / LARROW2EQUAL
|
|
/// / RARROW2EQUAL
|
|
/// / AMPERSANDEQUAL
|
|
/// / CARETEQUAL
|
|
/// / PIPEEQUAL
|
|
/// / ASTERISKPERCENTEQUAL
|
|
/// / PLUSPERCENTEQUAL
|
|
/// / MINUSPERCENTEQUAL
|
|
/// / EQUAL
|
|
fn parseAssignOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = nextToken(it);
|
|
const op: Node.InfixOp.Op = switch (token.ptr.id) {
|
|
.AsteriskEqual => .AssignMul,
|
|
.SlashEqual => .AssignDiv,
|
|
.PercentEqual => .AssignMod,
|
|
.PlusEqual => .AssignAdd,
|
|
.MinusEqual => .AssignSub,
|
|
.AngleBracketAngleBracketLeftEqual => .AssignBitShiftLeft,
|
|
.AngleBracketAngleBracketRightEqual => .AssignBitShiftRight,
|
|
.AmpersandEqual => .AssignBitAnd,
|
|
.CaretEqual => .AssignBitXor,
|
|
.PipeEqual => .AssignBitOr,
|
|
.AsteriskPercentEqual => .AssignMulWrap,
|
|
.PlusPercentEqual => .AssignAddWrap,
|
|
.MinusPercentEqual => .AssignSubWrap,
|
|
.Equal => .Assign,
|
|
else => {
|
|
putBackToken(it, token.index);
|
|
return null;
|
|
},
|
|
};
|
|
|
|
const node = try arena.create(Node.InfixOp);
|
|
node.* = .{
|
|
.op_token = token.index,
|
|
.lhs = undefined, // set by caller
|
|
.op = op,
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// CompareOp
|
|
/// <- EQUALEQUAL
|
|
/// / EXCLAMATIONMARKEQUAL
|
|
/// / LARROW
|
|
/// / RARROW
|
|
/// / LARROWEQUAL
|
|
/// / RARROWEQUAL
|
|
fn parseCompareOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = nextToken(it);
|
|
const op: Node.InfixOp.Op = switch (token.ptr.id) {
|
|
.EqualEqual => .EqualEqual,
|
|
.BangEqual => .BangEqual,
|
|
.AngleBracketLeft => .LessThan,
|
|
.AngleBracketRight => .GreaterThan,
|
|
.AngleBracketLeftEqual => .LessOrEqual,
|
|
.AngleBracketRightEqual => .GreaterOrEqual,
|
|
else => {
|
|
putBackToken(it, token.index);
|
|
return null;
|
|
},
|
|
};
|
|
|
|
return try createInfixOp(arena, token.index, op);
|
|
}
|
|
|
|
/// BitwiseOp
|
|
/// <- AMPERSAND
|
|
/// / CARET
|
|
/// / PIPE
|
|
/// / KEYWORD_orelse
|
|
/// / KEYWORD_catch Payload?
|
|
fn parseBitwiseOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = nextToken(it);
|
|
const op: Node.InfixOp.Op = switch (token.ptr.id) {
|
|
.Ampersand => .BitAnd,
|
|
.Caret => .BitXor,
|
|
.Pipe => .BitOr,
|
|
.Keyword_orelse => .UnwrapOptional,
|
|
.Keyword_catch => .{ .Catch = try parsePayload(arena, it, tree) },
|
|
else => {
|
|
putBackToken(it, token.index);
|
|
return null;
|
|
},
|
|
};
|
|
|
|
return try createInfixOp(arena, token.index, op);
|
|
}
|
|
|
|
/// BitShiftOp
|
|
/// <- LARROW2
|
|
/// / RARROW2
|
|
fn parseBitShiftOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = nextToken(it);
|
|
const op: Node.InfixOp.Op = switch (token.ptr.id) {
|
|
.AngleBracketAngleBracketLeft => .BitShiftLeft,
|
|
.AngleBracketAngleBracketRight => .BitShiftRight,
|
|
else => {
|
|
putBackToken(it, token.index);
|
|
return null;
|
|
},
|
|
};
|
|
|
|
return try createInfixOp(arena, token.index, op);
|
|
}
|
|
|
|
/// AdditionOp
|
|
/// <- PLUS
|
|
/// / MINUS
|
|
/// / PLUS2
|
|
/// / PLUSPERCENT
|
|
/// / MINUSPERCENT
|
|
fn parseAdditionOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = nextToken(it);
|
|
const op: Node.InfixOp.Op = switch (token.ptr.id) {
|
|
.Plus => .Add,
|
|
.Minus => .Sub,
|
|
.PlusPlus => .ArrayCat,
|
|
.PlusPercent => .AddWrap,
|
|
.MinusPercent => .SubWrap,
|
|
else => {
|
|
putBackToken(it, token.index);
|
|
return null;
|
|
},
|
|
};
|
|
|
|
return try createInfixOp(arena, token.index, op);
|
|
}
|
|
|
|
/// MultiplyOp
|
|
/// <- PIPE2
|
|
/// / ASTERISK
|
|
/// / SLASH
|
|
/// / PERCENT
|
|
/// / ASTERISK2
|
|
/// / ASTERISKPERCENT
|
|
fn parseMultiplyOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = nextToken(it);
|
|
const op: Node.InfixOp.Op = switch (token.ptr.id) {
|
|
.PipePipe => .MergeErrorSets,
|
|
.Asterisk => .Mul,
|
|
.Slash => .Div,
|
|
.Percent => .Mod,
|
|
.AsteriskAsterisk => .ArrayMult,
|
|
.AsteriskPercent => .MulWrap,
|
|
else => {
|
|
putBackToken(it, token.index);
|
|
return null;
|
|
},
|
|
};
|
|
|
|
return try createInfixOp(arena, token.index, op);
|
|
}
|
|
|
|
/// PrefixOp
|
|
/// <- EXCLAMATIONMARK
|
|
/// / MINUS
|
|
/// / TILDE
|
|
/// / MINUSPERCENT
|
|
/// / AMPERSAND
|
|
/// / KEYWORD_try
|
|
/// / KEYWORD_await
|
|
fn parsePrefixOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = nextToken(it);
|
|
const op: Node.PrefixOp.Op = switch (token.ptr.id) {
|
|
.Bang => .BoolNot,
|
|
.Minus => .Negation,
|
|
.Tilde => .BitNot,
|
|
.MinusPercent => .NegationWrap,
|
|
.Ampersand => .AddressOf,
|
|
.Keyword_try => .Try,
|
|
.Keyword_await => .Await,
|
|
else => {
|
|
putBackToken(it, token.index);
|
|
return null;
|
|
},
|
|
};
|
|
|
|
const node = try arena.create(Node.PrefixOp);
|
|
node.* = .{
|
|
.op_token = token.index,
|
|
.op = op,
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
// TODO: ArrayTypeStart is either an array or a slice, but const/allowzero only work on
|
|
// pointers. Consider updating this rule:
|
|
// ...
|
|
// / ArrayTypeStart
|
|
// / SliceTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
|
|
// / PtrTypeStart ...
|
|
|
|
/// PrefixTypeOp
|
|
/// <- QUESTIONMARK
|
|
/// / KEYWORD_anyframe MINUSRARROW
|
|
/// / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
|
|
/// / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
|
|
fn parsePrefixTypeOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
if (eatToken(it, .QuestionMark)) |token| {
|
|
const node = try arena.create(Node.PrefixOp);
|
|
node.* = .{
|
|
.op_token = token,
|
|
.op = .OptionalType,
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
// TODO: Returning a AnyFrameType instead of PrefixOp makes casting and setting .rhs or
|
|
// .return_type more difficult for the caller (see parsePrefixOpExpr helper).
|
|
// Consider making the AnyFrameType a member of PrefixOp and add a
|
|
// PrefixOp.AnyFrameType variant?
|
|
if (eatToken(it, .Keyword_anyframe)) |token| {
|
|
const arrow = eatToken(it, .Arrow) orelse {
|
|
putBackToken(it, token);
|
|
return null;
|
|
};
|
|
const node = try arena.create(Node.AnyFrameType);
|
|
node.* = .{
|
|
.anyframe_token = token,
|
|
.result = Node.AnyFrameType.Result{
|
|
.arrow_token = arrow,
|
|
.return_type = undefined, // set by caller
|
|
},
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (try parsePtrTypeStart(arena, it, tree)) |node| {
|
|
// If the token encountered was **, there will be two nodes instead of one.
|
|
// The attributes should be applied to the rightmost operator.
|
|
const prefix_op = node.cast(Node.PrefixOp).?;
|
|
var ptr_info = if (tree.tokens.at(prefix_op.op_token).id == .AsteriskAsterisk)
|
|
&prefix_op.rhs.cast(Node.PrefixOp).?.op.PtrType
|
|
else
|
|
&prefix_op.op.PtrType;
|
|
|
|
while (true) {
|
|
if (eatToken(it, .Keyword_align)) |align_token| {
|
|
const lparen = try expectToken(it, tree, .LParen);
|
|
const expr_node = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
|
|
// Optional bit range
|
|
const bit_range = if (eatToken(it, .Colon)) |_| bit_range_value: {
|
|
const range_start = try expectNode(arena, it, tree, parseIntegerLiteral, .{
|
|
.ExpectedIntegerLiteral = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .Colon);
|
|
const range_end = try expectNode(arena, it, tree, parseIntegerLiteral, .{
|
|
.ExpectedIntegerLiteral = .{ .token = it.index },
|
|
});
|
|
|
|
break :bit_range_value Node.PrefixOp.PtrInfo.Align.BitRange{
|
|
.start = range_start,
|
|
.end = range_end,
|
|
};
|
|
} else null;
|
|
_ = try expectToken(it, tree, .RParen);
|
|
|
|
ptr_info.align_info = Node.PrefixOp.PtrInfo.Align{
|
|
.node = expr_node,
|
|
.bit_range = bit_range,
|
|
};
|
|
|
|
continue;
|
|
}
|
|
if (eatToken(it, .Keyword_const)) |const_token| {
|
|
ptr_info.const_token = const_token;
|
|
continue;
|
|
}
|
|
if (eatToken(it, .Keyword_volatile)) |volatile_token| {
|
|
ptr_info.volatile_token = volatile_token;
|
|
continue;
|
|
}
|
|
if (eatToken(it, .Keyword_allowzero)) |allowzero_token| {
|
|
ptr_info.allowzero_token = allowzero_token;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
if (try parseArrayTypeStart(arena, it, tree)) |node| {
|
|
switch (node.cast(Node.PrefixOp).?.op) {
|
|
.ArrayType => {},
|
|
.SliceType => |*slice_type| {
|
|
// Collect pointer qualifiers in any order, but disallow duplicates
|
|
while (true) {
|
|
if (try parseByteAlign(arena, it, tree)) |align_expr| {
|
|
if (slice_type.align_info != null) {
|
|
try tree.errors.push(.{
|
|
.ExtraAlignQualifier = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
slice_type.align_info = Node.PrefixOp.PtrInfo.Align{
|
|
.node = align_expr,
|
|
.bit_range = null,
|
|
};
|
|
continue;
|
|
}
|
|
if (eatToken(it, .Keyword_const)) |const_token| {
|
|
if (slice_type.const_token != null) {
|
|
try tree.errors.push(.{
|
|
.ExtraConstQualifier = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
slice_type.const_token = const_token;
|
|
continue;
|
|
}
|
|
if (eatToken(it, .Keyword_volatile)) |volatile_token| {
|
|
if (slice_type.volatile_token != null) {
|
|
try tree.errors.push(.{
|
|
.ExtraVolatileQualifier = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
slice_type.volatile_token = volatile_token;
|
|
continue;
|
|
}
|
|
if (eatToken(it, .Keyword_allowzero)) |allowzero_token| {
|
|
if (slice_type.allowzero_token != null) {
|
|
try tree.errors.push(.{
|
|
.ExtraAllowZeroQualifier = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
slice_type.allowzero_token = allowzero_token;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
return node;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// SuffixOp
|
|
/// <- LBRACKET Expr (DOT2 (Expr (COLON Expr)?)?)? RBRACKET
|
|
/// / DOT IDENTIFIER
|
|
/// / DOTASTERISK
|
|
/// / DOTQUESTIONMARK
|
|
fn parseSuffixOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const OpAndToken = struct {
|
|
op: Node.SuffixOp.Op,
|
|
token: TokenIndex,
|
|
};
|
|
const op_and_token: OpAndToken = blk: {
|
|
if (eatToken(it, .LBracket)) |_| {
|
|
const index_expr = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
|
|
if (eatToken(it, .Ellipsis2) != null) {
|
|
const end_expr = try parseExpr(arena, it, tree);
|
|
const sentinel: ?*ast.Node = if (eatToken(it, .Colon) != null)
|
|
try parseExpr(arena, it, tree)
|
|
else
|
|
null;
|
|
break :blk .{
|
|
.op = .{
|
|
.Slice = .{
|
|
.start = index_expr,
|
|
.end = end_expr,
|
|
.sentinel = sentinel,
|
|
},
|
|
},
|
|
.token = try expectToken(it, tree, .RBracket),
|
|
};
|
|
}
|
|
|
|
break :blk .{
|
|
.op = .{ .ArrayAccess = index_expr },
|
|
.token = try expectToken(it, tree, .RBracket),
|
|
};
|
|
}
|
|
|
|
if (eatToken(it, .PeriodAsterisk)) |period_asterisk| {
|
|
break :blk .{ .op = .Deref, .token = period_asterisk };
|
|
}
|
|
|
|
if (eatToken(it, .Period)) |period| {
|
|
if (try parseIdentifier(arena, it, tree)) |identifier| {
|
|
// TODO: It's a bit weird to return an InfixOp from the SuffixOp parser.
|
|
// Should there be an ast.Node.SuffixOp.FieldAccess variant? Or should
|
|
// this grammar rule be altered?
|
|
const node = try arena.create(Node.InfixOp);
|
|
node.* = .{
|
|
.op_token = period,
|
|
.lhs = undefined, // set by caller
|
|
.op = .Period,
|
|
.rhs = identifier,
|
|
};
|
|
return &node.base;
|
|
}
|
|
if (eatToken(it, .QuestionMark)) |question_mark| {
|
|
break :blk .{ .op = .UnwrapOptional, .token = question_mark };
|
|
}
|
|
try tree.errors.push(.{
|
|
.ExpectedSuffixOp = .{ .token = it.index },
|
|
});
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const node = try arena.create(Node.SuffixOp);
|
|
node.* = .{
|
|
.lhs = undefined, // set by caller
|
|
.op = op_and_token.op,
|
|
.rtoken = op_and_token.token,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// FnCallArguments <- LPAREN ExprList RPAREN
|
|
/// ExprList <- (Expr COMMA)* Expr?
|
|
fn parseFnCallArguments(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?AnnotatedParamList {
|
|
if (eatToken(it, .LParen) == null) return null;
|
|
const list = try ListParseFn(Node.FnProto.ParamList, parseExpr)(arena, it, tree);
|
|
const rparen = try expectToken(it, tree, .RParen);
|
|
return AnnotatedParamList{ .list = list, .rparen = rparen };
|
|
}
|
|
|
|
const AnnotatedParamList = struct {
|
|
list: Node.FnProto.ParamList, // NOTE: may also be any other type SegmentedList(*Node, 2)
|
|
rparen: TokenIndex,
|
|
};
|
|
|
|
/// ArrayTypeStart <- LBRACKET Expr? RBRACKET
|
|
fn parseArrayTypeStart(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const lbracket = eatToken(it, .LBracket) orelse return null;
|
|
const expr = try parseExpr(arena, it, tree);
|
|
const sentinel = if (eatToken(it, .Colon)) |_|
|
|
try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
})
|
|
else
|
|
null;
|
|
const rbracket = try expectToken(it, tree, .RBracket);
|
|
|
|
const op: Node.PrefixOp.Op = if (expr) |len_expr|
|
|
.{
|
|
.ArrayType = .{
|
|
.len_expr = len_expr,
|
|
.sentinel = sentinel,
|
|
},
|
|
}
|
|
else
|
|
.{
|
|
.SliceType = Node.PrefixOp.PtrInfo{
|
|
.allowzero_token = null,
|
|
.align_info = null,
|
|
.const_token = null,
|
|
.volatile_token = null,
|
|
.sentinel = sentinel,
|
|
},
|
|
};
|
|
|
|
const node = try arena.create(Node.PrefixOp);
|
|
node.* = .{
|
|
.op_token = lbracket,
|
|
.op = op,
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// PtrTypeStart
|
|
/// <- ASTERISK
|
|
/// / ASTERISK2
|
|
/// / PTRUNKNOWN
|
|
/// / PTRC
|
|
fn parsePtrTypeStart(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
if (eatToken(it, .Asterisk)) |asterisk| {
|
|
const sentinel = if (eatToken(it, .Colon)) |_|
|
|
try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
})
|
|
else
|
|
null;
|
|
const node = try arena.create(Node.PrefixOp);
|
|
node.* = .{
|
|
.op_token = asterisk,
|
|
.op = .{ .PtrType = .{ .sentinel = sentinel } },
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
if (eatToken(it, .AsteriskAsterisk)) |double_asterisk| {
|
|
const node = try arena.create(Node.PrefixOp);
|
|
node.* = .{
|
|
.op_token = double_asterisk,
|
|
.op = .{ .PtrType = .{} },
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
|
|
// Special case for **, which is its own token
|
|
const child = try arena.create(Node.PrefixOp);
|
|
child.* = .{
|
|
.op_token = double_asterisk,
|
|
.op = .{ .PtrType = .{} },
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
node.rhs = &child.base;
|
|
|
|
return &node.base;
|
|
}
|
|
if (eatToken(it, .LBracket)) |lbracket| {
|
|
const asterisk = eatToken(it, .Asterisk) orelse {
|
|
putBackToken(it, lbracket);
|
|
return null;
|
|
};
|
|
if (eatToken(it, .Identifier)) |ident| {
|
|
if (!std.mem.eql(u8, tree.tokenSlice(ident), "c")) {
|
|
putBackToken(it, ident);
|
|
} else {
|
|
_ = try expectToken(it, tree, .RBracket);
|
|
const node = try arena.create(Node.PrefixOp);
|
|
node.* = .{
|
|
.op_token = lbracket,
|
|
.op = .{ .PtrType = .{} },
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
}
|
|
const sentinel = if (eatToken(it, .Colon)) |_|
|
|
try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
})
|
|
else
|
|
null;
|
|
_ = try expectToken(it, tree, .RBracket);
|
|
const node = try arena.create(Node.PrefixOp);
|
|
node.* = .{
|
|
.op_token = lbracket,
|
|
.op = .{ .PtrType = .{ .sentinel = sentinel } },
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE
|
|
fn parseContainerDeclAuto(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const node = (try parseContainerDeclType(arena, it, tree)) orelse return null;
|
|
const lbrace = try expectToken(it, tree, .LBrace);
|
|
const members = try parseContainerMembers(arena, it, tree);
|
|
const rbrace = try expectToken(it, tree, .RBrace);
|
|
|
|
const decl_type = node.cast(Node.ContainerDecl).?;
|
|
decl_type.fields_and_decls = members;
|
|
decl_type.lbrace_token = lbrace;
|
|
decl_type.rbrace_token = rbrace;
|
|
|
|
return node;
|
|
}
|
|
|
|
/// ContainerDeclType
|
|
/// <- KEYWORD_struct
|
|
/// / KEYWORD_enum (LPAREN Expr RPAREN)?
|
|
/// / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?
|
|
fn parseContainerDeclType(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const kind_token = nextToken(it);
|
|
|
|
const init_arg_expr = switch (kind_token.ptr.id) {
|
|
.Keyword_struct => Node.ContainerDecl.InitArg{ .None = {} },
|
|
.Keyword_enum => blk: {
|
|
if (eatToken(it, .LParen) != null) {
|
|
const expr = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
break :blk Node.ContainerDecl.InitArg{ .Type = expr };
|
|
}
|
|
break :blk Node.ContainerDecl.InitArg{ .None = {} };
|
|
},
|
|
.Keyword_union => blk: {
|
|
if (eatToken(it, .LParen) != null) {
|
|
if (eatToken(it, .Keyword_enum) != null) {
|
|
if (eatToken(it, .LParen) != null) {
|
|
const expr = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
_ = try expectToken(it, tree, .RParen);
|
|
break :blk Node.ContainerDecl.InitArg{ .Enum = expr };
|
|
}
|
|
_ = try expectToken(it, tree, .RParen);
|
|
break :blk Node.ContainerDecl.InitArg{ .Enum = null };
|
|
}
|
|
const expr = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
break :blk Node.ContainerDecl.InitArg{ .Type = expr };
|
|
}
|
|
break :blk Node.ContainerDecl.InitArg{ .None = {} };
|
|
},
|
|
else => {
|
|
putBackToken(it, kind_token.index);
|
|
return null;
|
|
},
|
|
};
|
|
|
|
const node = try arena.create(Node.ContainerDecl);
|
|
node.* = .{
|
|
.layout_token = null,
|
|
.kind_token = kind_token.index,
|
|
.init_arg_expr = init_arg_expr,
|
|
.fields_and_decls = undefined, // set by caller
|
|
.lbrace_token = undefined, // set by caller
|
|
.rbrace_token = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
|
|
fn parseByteAlign(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
_ = eatToken(it, .Keyword_align) orelse return null;
|
|
_ = try expectToken(it, tree, .LParen);
|
|
const expr = try expectNode(arena, it, tree, parseExpr, .{
|
|
.ExpectedExpr = .{ .token = it.index },
|
|
});
|
|
_ = try expectToken(it, tree, .RParen);
|
|
return expr;
|
|
}
|
|
|
|
/// IdentifierList <- (IDENTIFIER COMMA)* IDENTIFIER?
|
|
/// Only ErrorSetDecl parses an IdentifierList
|
|
fn parseErrorTagList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !Node.ErrorSetDecl.DeclList {
|
|
return try ListParseFn(Node.ErrorSetDecl.DeclList, parseErrorTag)(arena, it, tree);
|
|
}
|
|
|
|
/// SwitchProngList <- (SwitchProng COMMA)* SwitchProng?
|
|
fn parseSwitchProngList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !Node.Switch.CaseList {
|
|
return try ListParseFn(Node.Switch.CaseList, parseSwitchProng)(arena, it, tree);
|
|
}
|
|
|
|
/// AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
|
|
fn parseAsmOutputList(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!Node.Asm.OutputList {
|
|
return try ListParseFn(Node.Asm.OutputList, parseAsmOutputItem)(arena, it, tree);
|
|
}
|
|
|
|
/// AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
|
|
fn parseAsmInputList(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!Node.Asm.InputList {
|
|
return try ListParseFn(Node.Asm.InputList, parseAsmInputItem)(arena, it, tree);
|
|
}
|
|
|
|
/// ParamDeclList <- (ParamDecl COMMA)* ParamDecl?
|
|
fn parseParamDeclList(arena: *Allocator, it: *TokenIterator, tree: *Tree) !Node.FnProto.ParamList {
|
|
return try ListParseFn(Node.FnProto.ParamList, parseParamDecl)(arena, it, tree);
|
|
}
|
|
|
|
fn ParseFn(comptime T: type) type {
|
|
return fn (*Allocator, *TokenIterator, *Tree) Error!T;
|
|
}
|
|
|
|
const NodeParseFn = fn (*Allocator, *TokenIterator, *Tree) Error!?*Node;
|
|
|
|
fn ListParseFn(comptime L: type, comptime nodeParseFn: var) ParseFn(L) {
|
|
return struct {
|
|
pub fn parse(arena: *Allocator, it: *TokenIterator, tree: *Tree) !L {
|
|
var list = L.init(arena);
|
|
while (try nodeParseFn(arena, it, tree)) |node| {
|
|
try list.push(node);
|
|
if (eatToken(it, .Comma) == null) break;
|
|
}
|
|
return list;
|
|
}
|
|
}.parse;
|
|
}
|
|
|
|
fn SimpleBinOpParseFn(comptime token: Token.Id, comptime op: Node.InfixOp.Op) NodeParseFn {
|
|
return struct {
|
|
pub fn parse(arena: *Allocator, it: *TokenIterator, tree: *Tree) Error!?*Node {
|
|
const op_token = eatToken(it, token) orelse return null;
|
|
const node = try arena.create(Node.InfixOp);
|
|
node.* = .{
|
|
.op_token = op_token,
|
|
.lhs = undefined, // set by caller
|
|
.op = op,
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
}.parse;
|
|
}
|
|
|
|
// Helper parsers not included in the grammar
|
|
|
|
fn parseBuiltinCall(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = eatToken(it, .Builtin) orelse return null;
|
|
const params = (try parseFnCallArguments(arena, it, tree)) orelse {
|
|
try tree.errors.push(.{
|
|
.ExpectedParamList = .{ .token = it.index },
|
|
});
|
|
return error.ParseError;
|
|
};
|
|
const node = try arena.create(Node.BuiltinCall);
|
|
node.* = .{
|
|
.builtin_token = token,
|
|
.params = params.list,
|
|
.rparen_token = params.rparen,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
fn parseErrorTag(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const doc_comments = try parseDocComment(arena, it, tree); // no need to rewind on failure
|
|
const token = eatToken(it, .Identifier) orelse return null;
|
|
|
|
const node = try arena.create(Node.ErrorTag);
|
|
node.* = .{
|
|
.doc_comments = doc_comments,
|
|
.name_token = token,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
fn parseIdentifier(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = eatToken(it, .Identifier) orelse return null;
|
|
const node = try arena.create(Node.Identifier);
|
|
node.* = .{
|
|
.token = token,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
fn parseVarType(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = eatToken(it, .Keyword_var) orelse return null;
|
|
const node = try arena.create(Node.VarType);
|
|
node.* = .{
|
|
.token = token,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
fn createLiteral(arena: *Allocator, comptime T: type, token: TokenIndex) !*Node {
|
|
const result = try arena.create(T);
|
|
result.* = T{
|
|
.base = Node{ .id = Node.typeToId(T) },
|
|
.token = token,
|
|
};
|
|
return &result.base;
|
|
}
|
|
|
|
fn parseStringLiteralSingle(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
if (eatToken(it, .StringLiteral)) |token| {
|
|
const node = try arena.create(Node.StringLiteral);
|
|
node.* = .{
|
|
.token = token,
|
|
};
|
|
return &node.base;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// string literal or multiline string literal
|
|
fn parseStringLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
if (try parseStringLiteralSingle(arena, it, tree)) |node| return node;
|
|
|
|
if (eatToken(it, .MultilineStringLiteralLine)) |first_line| {
|
|
const node = try arena.create(Node.MultilineStringLiteral);
|
|
node.* = .{
|
|
.lines = Node.MultilineStringLiteral.LineList.init(arena),
|
|
};
|
|
try node.lines.push(first_line);
|
|
while (eatToken(it, .MultilineStringLiteralLine)) |line|
|
|
try node.lines.push(line);
|
|
|
|
return &node.base;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
fn parseIntegerLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = eatToken(it, .IntegerLiteral) orelse return null;
|
|
const node = try arena.create(Node.IntegerLiteral);
|
|
node.* = .{
|
|
.token = token,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
fn parseFloatLiteral(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = eatToken(it, .FloatLiteral) orelse return null;
|
|
const node = try arena.create(Node.FloatLiteral);
|
|
node.* = .{
|
|
.token = token,
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
fn parseTry(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = eatToken(it, .Keyword_try) orelse return null;
|
|
const node = try arena.create(Node.PrefixOp);
|
|
node.* = .{
|
|
.op_token = token,
|
|
.op = .Try,
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
fn parseUse(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
|
const token = eatToken(it, .Keyword_usingnamespace) orelse return null;
|
|
const node = try arena.create(Node.Use);
|
|
node.* = .{
|
|
.doc_comments = null,
|
|
.visib_token = null,
|
|
.use_token = token,
|
|
.expr = undefined, // set by caller
|
|
.semicolon_token = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
/// IfPrefix Body (KEYWORD_else Payload? Body)?
|
|
fn parseIf(arena: *Allocator, it: *TokenIterator, tree: *Tree, bodyParseFn: NodeParseFn) !?*Node {
|
|
const node = (try parseIfPrefix(arena, it, tree)) orelse return null;
|
|
const if_prefix = node.cast(Node.If).?;
|
|
|
|
if_prefix.body = try expectNode(arena, it, tree, bodyParseFn, .{
|
|
.InvalidToken = .{ .token = it.index },
|
|
});
|
|
|
|
const else_token = eatToken(it, .Keyword_else) orelse return node;
|
|
const payload = try parsePayload(arena, it, tree);
|
|
const else_expr = try expectNode(arena, it, tree, bodyParseFn, .{
|
|
.InvalidToken = .{ .token = it.index },
|
|
});
|
|
const else_node = try arena.create(Node.Else);
|
|
else_node.* = .{
|
|
.else_token = else_token,
|
|
.payload = payload,
|
|
.body = else_expr,
|
|
};
|
|
if_prefix.@"else" = else_node;
|
|
|
|
return node;
|
|
}
|
|
|
|
/// Eat a multiline doc comment
|
|
fn parseDocComment(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node.DocComment {
|
|
var lines = Node.DocComment.LineList.init(arena);
|
|
while (eatToken(it, .DocComment)) |line| {
|
|
try lines.push(line);
|
|
}
|
|
|
|
if (lines.len == 0) return null;
|
|
|
|
const node = try arena.create(Node.DocComment);
|
|
node.* = .{
|
|
.lines = lines,
|
|
};
|
|
return node;
|
|
}
|
|
|
|
/// Eat a single-line doc comment on the same line as another node
|
|
fn parseAppendedDocComment(arena: *Allocator, it: *TokenIterator, tree: *Tree, after_token: TokenIndex) !?*Node.DocComment {
|
|
const comment_token = eatToken(it, .DocComment) orelse return null;
|
|
if (tree.tokensOnSameLine(after_token, comment_token)) {
|
|
const node = try arena.create(Node.DocComment);
|
|
node.* = .{
|
|
.lines = Node.DocComment.LineList.init(arena),
|
|
};
|
|
try node.lines.push(comment_token);
|
|
return node;
|
|
}
|
|
putBackToken(it, comment_token);
|
|
return null;
|
|
}
|
|
|
|
/// Op* Child
|
|
fn parsePrefixOpExpr(
|
|
arena: *Allocator,
|
|
it: *TokenIterator,
|
|
tree: *Tree,
|
|
opParseFn: NodeParseFn,
|
|
childParseFn: NodeParseFn,
|
|
) Error!?*Node {
|
|
if (try opParseFn(arena, it, tree)) |first_op| {
|
|
var rightmost_op = first_op;
|
|
while (true) {
|
|
switch (rightmost_op.id) {
|
|
.PrefixOp => {
|
|
var prefix_op = rightmost_op.cast(Node.PrefixOp).?;
|
|
// If the token encountered was **, there will be two nodes
|
|
if (tree.tokens.at(prefix_op.op_token).id == .AsteriskAsterisk) {
|
|
rightmost_op = prefix_op.rhs;
|
|
prefix_op = rightmost_op.cast(Node.PrefixOp).?;
|
|
}
|
|
if (try opParseFn(arena, it, tree)) |rhs| {
|
|
prefix_op.rhs = rhs;
|
|
rightmost_op = rhs;
|
|
} else break;
|
|
},
|
|
.AnyFrameType => {
|
|
const prom = rightmost_op.cast(Node.AnyFrameType).?;
|
|
if (try opParseFn(arena, it, tree)) |rhs| {
|
|
prom.result.?.return_type = rhs;
|
|
rightmost_op = rhs;
|
|
} else break;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
// If any prefix op existed, a child node on the RHS is required
|
|
switch (rightmost_op.id) {
|
|
.PrefixOp => {
|
|
const prefix_op = rightmost_op.cast(Node.PrefixOp).?;
|
|
prefix_op.rhs = try expectNode(arena, it, tree, childParseFn, .{
|
|
.InvalidToken = .{ .token = it.index },
|
|
});
|
|
},
|
|
.AnyFrameType => {
|
|
const prom = rightmost_op.cast(Node.AnyFrameType).?;
|
|
prom.result.?.return_type = try expectNode(arena, it, tree, childParseFn, .{
|
|
.InvalidToken = .{ .token = it.index },
|
|
});
|
|
},
|
|
else => unreachable,
|
|
}
|
|
|
|
return first_op;
|
|
}
|
|
|
|
// Otherwise, the child node is optional
|
|
return try childParseFn(arena, it, tree);
|
|
}
|
|
|
|
/// Child (Op Child)*
|
|
/// Child (Op Child)?
|
|
fn parseBinOpExpr(
|
|
arena: *Allocator,
|
|
it: *TokenIterator,
|
|
tree: *Tree,
|
|
opParseFn: NodeParseFn,
|
|
childParseFn: NodeParseFn,
|
|
chain: enum {
|
|
Once,
|
|
Infinitely,
|
|
},
|
|
) Error!?*Node {
|
|
var res = (try childParseFn(arena, it, tree)) orelse return null;
|
|
|
|
while (try opParseFn(arena, it, tree)) |node| {
|
|
const right = try expectNode(arena, it, tree, childParseFn, .{
|
|
.InvalidToken = .{ .token = it.index },
|
|
});
|
|
const left = res;
|
|
res = node;
|
|
|
|
const op = node.cast(Node.InfixOp).?;
|
|
op.*.lhs = left;
|
|
op.*.rhs = right;
|
|
|
|
switch (chain) {
|
|
.Once => break,
|
|
.Infinitely => continue,
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
fn createInfixOp(arena: *Allocator, index: TokenIndex, op: Node.InfixOp.Op) !*Node {
|
|
const node = try arena.create(Node.InfixOp);
|
|
node.* = .{
|
|
.op_token = index,
|
|
.lhs = undefined, // set by caller
|
|
.op = op,
|
|
.rhs = undefined, // set by caller
|
|
};
|
|
return &node.base;
|
|
}
|
|
|
|
fn eatToken(it: *TokenIterator, id: Token.Id) ?TokenIndex {
|
|
return if (eatAnnotatedToken(it, id)) |token| token.index else null;
|
|
}
|
|
|
|
fn eatAnnotatedToken(it: *TokenIterator, id: Token.Id) ?AnnotatedToken {
|
|
return if (it.peek().?.id == id) nextToken(it) else null;
|
|
}
|
|
|
|
fn expectToken(it: *TokenIterator, tree: *Tree, id: Token.Id) Error!TokenIndex {
|
|
const token = nextToken(it);
|
|
if (token.ptr.id != id) {
|
|
try tree.errors.push(.{
|
|
.ExpectedToken = .{ .token = token.index, .expected_id = id },
|
|
});
|
|
return error.ParseError;
|
|
}
|
|
return token.index;
|
|
}
|
|
|
|
fn nextToken(it: *TokenIterator) AnnotatedToken {
|
|
const result = AnnotatedToken{
|
|
.index = it.index,
|
|
.ptr = it.next().?,
|
|
};
|
|
assert(result.ptr.id != .LineComment);
|
|
|
|
while (true) {
|
|
const next_tok = it.peek() orelse return result;
|
|
if (next_tok.id != .LineComment) return result;
|
|
_ = it.next();
|
|
}
|
|
}
|
|
|
|
fn putBackToken(it: *TokenIterator, putting_back: TokenIndex) void {
|
|
while (true) {
|
|
const prev_tok = it.prev() orelse return;
|
|
if (prev_tok.id == .LineComment) continue;
|
|
assert(it.list.at(putting_back) == prev_tok);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const AnnotatedToken = struct {
|
|
index: TokenIndex,
|
|
ptr: *Token,
|
|
};
|
|
|
|
fn expectNode(
|
|
arena: *Allocator,
|
|
it: *TokenIterator,
|
|
tree: *Tree,
|
|
parseFn: NodeParseFn,
|
|
err: AstError, // if parsing fails
|
|
) Error!*Node {
|
|
return (try parseFn(arena, it, tree)) orelse {
|
|
try tree.errors.push(err);
|
|
return error.ParseError;
|
|
};
|
|
}
|
|
|
|
test "std.zig.parser" {
|
|
_ = @import("parser_test.zig");
|
|
}
|