mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
The other pointer types are `.opt_node_and_node` but `ptr_type` and `ptr_type_bit_range` contain `.extra_and_node` in their `data` field
4144 lines
145 KiB
Zig
4144 lines
145 KiB
Zig
//! Abstract Syntax Tree for Zig source code.
|
|
//! For Zig syntax, the root node is at nodes[0] and contains the list of
|
|
//! sub-nodes.
|
|
//! For Zon syntax, the root node is at nodes[0] and contains lhs as the node
|
|
//! index of the main expression.
|
|
|
|
const std = @import("../std.zig");
|
|
const assert = std.debug.assert;
|
|
const testing = std.testing;
|
|
const mem = std.mem;
|
|
const Token = std.zig.Token;
|
|
const Ast = @This();
|
|
const Allocator = std.mem.Allocator;
|
|
const Parse = @import("Parse.zig");
|
|
const Writer = std.Io.Writer;
|
|
|
|
/// Reference to externally-owned data.
|
|
source: [:0]const u8,
|
|
|
|
tokens: TokenList.Slice,
|
|
nodes: NodeList.Slice,
|
|
extra_data: []u32,
|
|
mode: Mode = .zig,
|
|
|
|
errors: []const Error,
|
|
|
|
pub const ByteOffset = u32;
|
|
|
|
pub const TokenList = std.MultiArrayList(struct {
|
|
tag: Token.Tag,
|
|
start: ByteOffset,
|
|
});
|
|
pub const NodeList = std.MultiArrayList(Node);
|
|
|
|
/// Index into `tokens`.
|
|
pub const TokenIndex = u32;
|
|
|
|
/// Index into `tokens`, or null.
|
|
pub const OptionalTokenIndex = enum(u32) {
|
|
none = std.math.maxInt(u32),
|
|
_,
|
|
|
|
pub fn unwrap(oti: OptionalTokenIndex) ?TokenIndex {
|
|
return if (oti == .none) null else @intFromEnum(oti);
|
|
}
|
|
|
|
pub fn fromToken(ti: TokenIndex) OptionalTokenIndex {
|
|
return @enumFromInt(ti);
|
|
}
|
|
|
|
pub fn fromOptional(oti: ?TokenIndex) OptionalTokenIndex {
|
|
return if (oti) |ti| @enumFromInt(ti) else .none;
|
|
}
|
|
};
|
|
|
|
/// A relative token index.
|
|
pub const TokenOffset = enum(i32) {
|
|
zero = 0,
|
|
_,
|
|
|
|
pub fn init(base: TokenIndex, destination: TokenIndex) TokenOffset {
|
|
const base_i64: i64 = base;
|
|
const destination_i64: i64 = destination;
|
|
return @enumFromInt(destination_i64 - base_i64);
|
|
}
|
|
|
|
pub fn toOptional(to: TokenOffset) OptionalTokenOffset {
|
|
const result: OptionalTokenOffset = @enumFromInt(@intFromEnum(to));
|
|
assert(result != .none);
|
|
return result;
|
|
}
|
|
|
|
pub fn toAbsolute(offset: TokenOffset, base: TokenIndex) TokenIndex {
|
|
return @intCast(@as(i64, base) + @intFromEnum(offset));
|
|
}
|
|
};
|
|
|
|
/// A relative token index, or null.
|
|
pub const OptionalTokenOffset = enum(i32) {
|
|
none = std.math.maxInt(i32),
|
|
_,
|
|
|
|
pub fn unwrap(oto: OptionalTokenOffset) ?TokenOffset {
|
|
return if (oto == .none) null else @enumFromInt(@intFromEnum(oto));
|
|
}
|
|
};
|
|
|
|
pub fn tokenTag(tree: *const Ast, token_index: TokenIndex) Token.Tag {
|
|
return tree.tokens.items(.tag)[token_index];
|
|
}
|
|
|
|
pub fn tokenStart(tree: *const Ast, token_index: TokenIndex) ByteOffset {
|
|
return tree.tokens.items(.start)[token_index];
|
|
}
|
|
|
|
pub fn nodeTag(tree: *const Ast, node: Node.Index) Node.Tag {
|
|
return tree.nodes.items(.tag)[@intFromEnum(node)];
|
|
}
|
|
|
|
pub fn nodeMainToken(tree: *const Ast, node: Node.Index) TokenIndex {
|
|
return tree.nodes.items(.main_token)[@intFromEnum(node)];
|
|
}
|
|
|
|
pub fn nodeData(tree: *const Ast, node: Node.Index) Node.Data {
|
|
return tree.nodes.items(.data)[@intFromEnum(node)];
|
|
}
|
|
|
|
pub fn isTokenPrecededByTags(
|
|
tree: *const Ast,
|
|
ti: TokenIndex,
|
|
expected_token_tags: []const Token.Tag,
|
|
) bool {
|
|
return std.mem.endsWith(
|
|
Token.Tag,
|
|
tree.tokens.items(.tag)[0..ti],
|
|
expected_token_tags,
|
|
);
|
|
}
|
|
|
|
pub const Location = struct {
|
|
line: usize,
|
|
column: usize,
|
|
line_start: usize,
|
|
line_end: usize,
|
|
};
|
|
|
|
pub const Span = struct {
|
|
start: u32,
|
|
end: u32,
|
|
main: u32,
|
|
};
|
|
|
|
pub fn deinit(tree: *Ast, gpa: Allocator) void {
|
|
tree.tokens.deinit(gpa);
|
|
tree.nodes.deinit(gpa);
|
|
gpa.free(tree.extra_data);
|
|
gpa.free(tree.errors);
|
|
tree.* = undefined;
|
|
}
|
|
|
|
pub const Mode = enum { zig, zon };
|
|
|
|
/// Result should be freed with tree.deinit() when there are
|
|
/// no more references to any of the tokens or nodes.
|
|
pub fn parse(gpa: Allocator, source: [:0]const u8, mode: Mode) Allocator.Error!Ast {
|
|
var tokens = Ast.TokenList{};
|
|
defer tokens.deinit(gpa);
|
|
|
|
// Empirically, the zig std lib has an 8:1 ratio of source bytes to token count.
|
|
const estimated_token_count = source.len / 8;
|
|
try tokens.ensureTotalCapacity(gpa, estimated_token_count);
|
|
|
|
var tokenizer = std.zig.Tokenizer.init(source);
|
|
while (true) {
|
|
const token = tokenizer.next();
|
|
try tokens.append(gpa, .{
|
|
.tag = token.tag,
|
|
.start = @intCast(token.loc.start),
|
|
});
|
|
if (token.tag == .eof) break;
|
|
}
|
|
|
|
var parser: Parse = .{
|
|
.source = source,
|
|
.gpa = gpa,
|
|
.tokens = tokens.slice(),
|
|
.errors = .{},
|
|
.nodes = .{},
|
|
.extra_data = .{},
|
|
.scratch = .{},
|
|
.tok_i = 0,
|
|
};
|
|
defer parser.errors.deinit(gpa);
|
|
defer parser.nodes.deinit(gpa);
|
|
defer parser.extra_data.deinit(gpa);
|
|
defer parser.scratch.deinit(gpa);
|
|
|
|
// Empirically, Zig source code has a 2:1 ratio of tokens to AST nodes.
|
|
// Make sure at least 1 so we can use appendAssumeCapacity on the root node below.
|
|
const estimated_node_count = (tokens.len + 2) / 2;
|
|
try parser.nodes.ensureTotalCapacity(gpa, estimated_node_count);
|
|
|
|
switch (mode) {
|
|
.zig => try parser.parseRoot(),
|
|
.zon => try parser.parseZon(),
|
|
}
|
|
|
|
const extra_data = try parser.extra_data.toOwnedSlice(gpa);
|
|
errdefer gpa.free(extra_data);
|
|
const errors = try parser.errors.toOwnedSlice(gpa);
|
|
errdefer gpa.free(errors);
|
|
|
|
// TODO experiment with compacting the MultiArrayList slices here
|
|
return Ast{
|
|
.source = source,
|
|
.mode = mode,
|
|
.tokens = tokens.toOwnedSlice(),
|
|
.nodes = parser.nodes.toOwnedSlice(),
|
|
.extra_data = extra_data,
|
|
.errors = errors,
|
|
};
|
|
}
|
|
|
|
/// `gpa` is used for allocating the resulting formatted source code.
|
|
/// Caller owns the returned slice of bytes, allocated with `gpa`.
|
|
pub fn renderAlloc(tree: Ast, gpa: Allocator) error{OutOfMemory}![]u8 {
|
|
var aw: std.io.Writer.Allocating = .init(gpa);
|
|
defer aw.deinit();
|
|
render(tree, gpa, &aw.writer, .{}) catch |err| switch (err) {
|
|
error.WriteFailed, error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
return aw.toOwnedSlice();
|
|
}
|
|
|
|
pub const Render = @import("Ast/Render.zig");
|
|
|
|
pub fn render(tree: Ast, gpa: Allocator, w: *Writer, fixups: Render.Fixups) Render.Error!void {
|
|
return Render.renderTree(gpa, w, tree, fixups);
|
|
}
|
|
|
|
/// Returns an extra offset for column and byte offset of errors that
|
|
/// should point after the token in the error message.
|
|
pub fn errorOffset(tree: Ast, parse_error: Error) u32 {
|
|
return if (parse_error.token_is_prev) @intCast(tree.tokenSlice(parse_error.token).len) else 0;
|
|
}
|
|
|
|
pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location {
|
|
var loc = Location{
|
|
.line = 0,
|
|
.column = 0,
|
|
.line_start = start_offset,
|
|
.line_end = self.source.len,
|
|
};
|
|
const token_start = self.tokenStart(token_index);
|
|
|
|
// Scan to by line until we go past the token start
|
|
while (std.mem.indexOfScalarPos(u8, self.source, loc.line_start, '\n')) |i| {
|
|
if (i >= token_start) {
|
|
break; // Went past
|
|
}
|
|
loc.line += 1;
|
|
loc.line_start = i + 1;
|
|
}
|
|
|
|
const offset = loc.line_start;
|
|
for (self.source[offset..], 0..) |c, i| {
|
|
if (i + offset == token_start) {
|
|
loc.line_end = i + offset;
|
|
while (loc.line_end < self.source.len and self.source[loc.line_end] != '\n') {
|
|
loc.line_end += 1;
|
|
}
|
|
return loc;
|
|
}
|
|
if (c == '\n') {
|
|
loc.line += 1;
|
|
loc.column = 0;
|
|
loc.line_start = i + 1;
|
|
} else {
|
|
loc.column += 1;
|
|
}
|
|
}
|
|
return loc;
|
|
}
|
|
|
|
pub fn tokenSlice(tree: Ast, token_index: TokenIndex) []const u8 {
|
|
const token_tag = tree.tokenTag(token_index);
|
|
|
|
// Many tokens can be determined entirely by their tag.
|
|
if (token_tag.lexeme()) |lexeme| {
|
|
return lexeme;
|
|
}
|
|
|
|
// For some tokens, re-tokenization is needed to find the end.
|
|
var tokenizer: std.zig.Tokenizer = .{
|
|
.buffer = tree.source,
|
|
.index = tree.tokenStart(token_index),
|
|
};
|
|
const token = tokenizer.next();
|
|
assert(token.tag == token_tag);
|
|
return tree.source[token.loc.start..token.loc.end];
|
|
}
|
|
|
|
pub fn extraDataSlice(tree: Ast, range: Node.SubRange, comptime T: type) []const T {
|
|
return @ptrCast(tree.extra_data[@intFromEnum(range.start)..@intFromEnum(range.end)]);
|
|
}
|
|
|
|
pub fn extraDataSliceWithLen(tree: Ast, start: ExtraIndex, len: u32, comptime T: type) []const T {
|
|
return @ptrCast(tree.extra_data[@intFromEnum(start)..][0..len]);
|
|
}
|
|
|
|
pub fn extraData(tree: Ast, index: ExtraIndex, comptime T: type) T {
|
|
const fields = std.meta.fields(T);
|
|
var result: T = undefined;
|
|
inline for (fields, 0..) |field, i| {
|
|
@field(result, field.name) = switch (field.type) {
|
|
Node.Index,
|
|
Node.OptionalIndex,
|
|
OptionalTokenIndex,
|
|
ExtraIndex,
|
|
=> @enumFromInt(tree.extra_data[@intFromEnum(index) + i]),
|
|
TokenIndex => tree.extra_data[@intFromEnum(index) + i],
|
|
else => @compileError("unexpected field type: " ++ @typeName(field.type)),
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn loadOptionalNodesIntoBuffer(comptime size: usize, buffer: *[size]Node.Index, items: [size]Node.OptionalIndex) []Node.Index {
|
|
for (buffer, items, 0..) |*node, opt_node, i| {
|
|
node.* = opt_node.unwrap() orelse return buffer[0..i];
|
|
}
|
|
return buffer[0..];
|
|
}
|
|
|
|
pub fn rootDecls(tree: Ast) []const Node.Index {
|
|
switch (tree.mode) {
|
|
.zig => return tree.extraDataSlice(tree.nodeData(.root).extra_range, Node.Index),
|
|
// Ensure that the returned slice points into the existing memory of the Ast
|
|
.zon => return (&tree.nodes.items(.data)[@intFromEnum(Node.Index.root)].node)[0..1],
|
|
}
|
|
}
|
|
|
|
pub fn renderError(tree: Ast, parse_error: Error, w: *Writer) Writer.Error!void {
|
|
switch (parse_error.tag) {
|
|
.asterisk_after_ptr_deref => {
|
|
// Note that the token will point at the `.*` but ideally the source
|
|
// location would point to the `*` after the `.*`.
|
|
return w.writeAll("'.*' cannot be followed by '*'; are you missing a space?");
|
|
},
|
|
.chained_comparison_operators => {
|
|
return w.writeAll("comparison operators cannot be chained");
|
|
},
|
|
.decl_between_fields => {
|
|
return w.writeAll("declarations are not allowed between container fields");
|
|
},
|
|
.expected_block => {
|
|
return w.print("expected block, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_block_or_assignment => {
|
|
return w.print("expected block or assignment, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_block_or_expr => {
|
|
return w.print("expected block or expression, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_block_or_field => {
|
|
return w.print("expected block or field, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_container_members => {
|
|
return w.print("expected test, comptime, var decl, or container field, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token).symbol(),
|
|
});
|
|
},
|
|
.expected_expr => {
|
|
return w.print("expected expression, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_expr_or_assignment => {
|
|
return w.print("expected expression or assignment, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_expr_or_var_decl => {
|
|
return w.print("expected expression or var decl, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_fn => {
|
|
return w.print("expected function, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_inlinable => {
|
|
return w.print("expected 'while' or 'for', found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_labelable => {
|
|
return w.print("expected 'while', 'for', 'inline', or '{{', found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_param_list => {
|
|
return w.print("expected parameter list, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_prefix_expr => {
|
|
return w.print("expected prefix expression, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_primary_type_expr => {
|
|
return w.print("expected primary type expression, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_pub_item => {
|
|
return w.writeAll("expected function or variable declaration after pub");
|
|
},
|
|
.expected_return_type => {
|
|
return w.print("expected return type expression, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_semi_or_else => {
|
|
return w.writeAll("expected ';' or 'else' after statement");
|
|
},
|
|
.expected_semi_or_lbrace => {
|
|
return w.writeAll("expected ';' or block after function prototype");
|
|
},
|
|
.expected_statement => {
|
|
return w.print("expected statement, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token).symbol(),
|
|
});
|
|
},
|
|
.expected_suffix_op => {
|
|
return w.print("expected pointer dereference, optional unwrap, or field access, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_type_expr => {
|
|
return w.print("expected type expression, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_var_decl => {
|
|
return w.print("expected variable declaration, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_var_decl_or_fn => {
|
|
return w.print("expected variable declaration or function, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_loop_payload => {
|
|
return w.print("expected loop payload, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.expected_container => {
|
|
return w.print("expected a struct, enum or union, found '{s}'", .{
|
|
tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev)).symbol(),
|
|
});
|
|
},
|
|
.extern_fn_body => {
|
|
return w.writeAll("extern functions have no body");
|
|
},
|
|
.extra_addrspace_qualifier => {
|
|
return w.writeAll("extra addrspace qualifier");
|
|
},
|
|
.extra_align_qualifier => {
|
|
return w.writeAll("extra align qualifier");
|
|
},
|
|
.extra_allowzero_qualifier => {
|
|
return w.writeAll("extra allowzero qualifier");
|
|
},
|
|
.extra_const_qualifier => {
|
|
return w.writeAll("extra const qualifier");
|
|
},
|
|
.extra_volatile_qualifier => {
|
|
return w.writeAll("extra volatile qualifier");
|
|
},
|
|
.ptr_mod_on_array_child_type => {
|
|
return w.print("pointer modifier '{s}' not allowed on array child type", .{
|
|
tree.tokenTag(parse_error.token).symbol(),
|
|
});
|
|
},
|
|
.invalid_bit_range => {
|
|
return w.writeAll("bit range not allowed on slices and arrays");
|
|
},
|
|
.same_line_doc_comment => {
|
|
return w.writeAll("same line documentation comment");
|
|
},
|
|
.unattached_doc_comment => {
|
|
return w.writeAll("unattached documentation comment");
|
|
},
|
|
.test_doc_comment => {
|
|
return w.writeAll("documentation comments cannot be attached to tests");
|
|
},
|
|
.comptime_doc_comment => {
|
|
return w.writeAll("documentation comments cannot be attached to comptime blocks");
|
|
},
|
|
.varargs_nonfinal => {
|
|
return w.writeAll("function prototype has parameter after varargs");
|
|
},
|
|
.expected_continue_expr => {
|
|
return w.writeAll("expected ':' before while continue expression");
|
|
},
|
|
|
|
.expected_semi_after_decl => {
|
|
return w.writeAll("expected ';' after declaration");
|
|
},
|
|
.expected_semi_after_stmt => {
|
|
return w.writeAll("expected ';' after statement");
|
|
},
|
|
.expected_comma_after_field => {
|
|
return w.writeAll("expected ',' after field");
|
|
},
|
|
.expected_comma_after_arg => {
|
|
return w.writeAll("expected ',' after argument");
|
|
},
|
|
.expected_comma_after_param => {
|
|
return w.writeAll("expected ',' after parameter");
|
|
},
|
|
.expected_comma_after_initializer => {
|
|
return w.writeAll("expected ',' after initializer");
|
|
},
|
|
.expected_comma_after_switch_prong => {
|
|
return w.writeAll("expected ',' after switch prong");
|
|
},
|
|
.expected_comma_after_for_operand => {
|
|
return w.writeAll("expected ',' after for operand");
|
|
},
|
|
.expected_comma_after_capture => {
|
|
return w.writeAll("expected ',' after for capture");
|
|
},
|
|
.expected_initializer => {
|
|
return w.writeAll("expected field initializer");
|
|
},
|
|
.mismatched_binary_op_whitespace => {
|
|
return w.print("binary operator '{s}' has whitespace on one side, but not the other", .{tree.tokenTag(parse_error.token).lexeme().?});
|
|
},
|
|
.invalid_ampersand_ampersand => {
|
|
return w.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND");
|
|
},
|
|
.c_style_container => {
|
|
return w.print("'{s} {s}' is invalid", .{
|
|
parse_error.extra.expected_tag.symbol(), tree.tokenSlice(parse_error.token),
|
|
});
|
|
},
|
|
.zig_style_container => {
|
|
return w.print("to declare a container do 'const {s} = {s}'", .{
|
|
tree.tokenSlice(parse_error.token), parse_error.extra.expected_tag.symbol(),
|
|
});
|
|
},
|
|
.previous_field => {
|
|
return w.writeAll("field before declarations here");
|
|
},
|
|
.next_field => {
|
|
return w.writeAll("field after declarations here");
|
|
},
|
|
.expected_var_const => {
|
|
return w.writeAll("expected 'var' or 'const' before variable declaration");
|
|
},
|
|
.wrong_equal_var_decl => {
|
|
return w.writeAll("variable initialized with '==' instead of '='");
|
|
},
|
|
.var_const_decl => {
|
|
return w.writeAll("use 'var' or 'const' to declare variable");
|
|
},
|
|
.extra_for_capture => {
|
|
return w.writeAll("extra capture in for loop");
|
|
},
|
|
.for_input_not_captured => {
|
|
return w.writeAll("for input is not captured");
|
|
},
|
|
|
|
.invalid_byte => {
|
|
const tok_slice = tree.source[tree.tokens.items(.start)[parse_error.token]..];
|
|
return w.print("{s} contains invalid byte: '{f}'", .{
|
|
switch (tok_slice[0]) {
|
|
'\'' => "character literal",
|
|
'"', '\\' => "string literal",
|
|
'/' => "comment",
|
|
else => unreachable,
|
|
},
|
|
std.zig.fmtChar(tok_slice[parse_error.extra.offset]),
|
|
});
|
|
},
|
|
|
|
.expected_token => {
|
|
const found_tag = tree.tokenTag(parse_error.token + @intFromBool(parse_error.token_is_prev));
|
|
const expected_symbol = parse_error.extra.expected_tag.symbol();
|
|
switch (found_tag) {
|
|
.invalid => return w.print("expected '{s}', found invalid bytes", .{
|
|
expected_symbol,
|
|
}),
|
|
else => return w.print("expected '{s}', found '{s}'", .{
|
|
expected_symbol, found_tag.symbol(),
|
|
}),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn firstToken(tree: Ast, node: Node.Index) TokenIndex {
|
|
var end_offset: u32 = 0;
|
|
var n = node;
|
|
while (true) switch (tree.nodeTag(n)) {
|
|
.root => return 0,
|
|
|
|
.test_decl,
|
|
.@"errdefer",
|
|
.@"defer",
|
|
.bool_not,
|
|
.negation,
|
|
.bit_not,
|
|
.negation_wrap,
|
|
.address_of,
|
|
.@"try",
|
|
.optional_type,
|
|
.@"switch",
|
|
.switch_comma,
|
|
.if_simple,
|
|
.@"if",
|
|
.@"suspend",
|
|
.@"resume",
|
|
.@"continue",
|
|
.@"break",
|
|
.@"return",
|
|
.anyframe_type,
|
|
.identifier,
|
|
.anyframe_literal,
|
|
.char_literal,
|
|
.number_literal,
|
|
.unreachable_literal,
|
|
.string_literal,
|
|
.multiline_string_literal,
|
|
.grouped_expression,
|
|
.builtin_call_two,
|
|
.builtin_call_two_comma,
|
|
.builtin_call,
|
|
.builtin_call_comma,
|
|
.error_set_decl,
|
|
.@"comptime",
|
|
.@"nosuspend",
|
|
.asm_simple,
|
|
.@"asm",
|
|
.asm_legacy,
|
|
.array_type,
|
|
.array_type_sentinel,
|
|
.error_value,
|
|
=> return tree.nodeMainToken(n) - end_offset,
|
|
|
|
.array_init_dot,
|
|
.array_init_dot_comma,
|
|
.array_init_dot_two,
|
|
.array_init_dot_two_comma,
|
|
.struct_init_dot,
|
|
.struct_init_dot_comma,
|
|
.struct_init_dot_two,
|
|
.struct_init_dot_two_comma,
|
|
.enum_literal,
|
|
=> return tree.nodeMainToken(n) - 1 - end_offset,
|
|
|
|
.@"catch",
|
|
.equal_equal,
|
|
.bang_equal,
|
|
.less_than,
|
|
.greater_than,
|
|
.less_or_equal,
|
|
.greater_or_equal,
|
|
.assign_mul,
|
|
.assign_div,
|
|
.assign_mod,
|
|
.assign_add,
|
|
.assign_sub,
|
|
.assign_shl,
|
|
.assign_shl_sat,
|
|
.assign_shr,
|
|
.assign_bit_and,
|
|
.assign_bit_xor,
|
|
.assign_bit_or,
|
|
.assign_mul_wrap,
|
|
.assign_add_wrap,
|
|
.assign_sub_wrap,
|
|
.assign_mul_sat,
|
|
.assign_add_sat,
|
|
.assign_sub_sat,
|
|
.assign,
|
|
.merge_error_sets,
|
|
.mul,
|
|
.div,
|
|
.mod,
|
|
.array_mult,
|
|
.mul_wrap,
|
|
.mul_sat,
|
|
.add,
|
|
.sub,
|
|
.array_cat,
|
|
.add_wrap,
|
|
.sub_wrap,
|
|
.add_sat,
|
|
.sub_sat,
|
|
.shl,
|
|
.shl_sat,
|
|
.shr,
|
|
.bit_and,
|
|
.bit_xor,
|
|
.bit_or,
|
|
.@"orelse",
|
|
.bool_and,
|
|
.bool_or,
|
|
.slice_open,
|
|
.array_access,
|
|
.array_init_one,
|
|
.array_init_one_comma,
|
|
.switch_range,
|
|
.error_union,
|
|
=> n = tree.nodeData(n).node_and_node[0],
|
|
|
|
.for_range,
|
|
.call_one,
|
|
.call_one_comma,
|
|
.struct_init_one,
|
|
.struct_init_one_comma,
|
|
=> n = tree.nodeData(n).node_and_opt_node[0],
|
|
|
|
.field_access,
|
|
.unwrap_optional,
|
|
=> n = tree.nodeData(n).node_and_token[0],
|
|
|
|
.slice,
|
|
.slice_sentinel,
|
|
.array_init,
|
|
.array_init_comma,
|
|
.struct_init,
|
|
.struct_init_comma,
|
|
.call,
|
|
.call_comma,
|
|
=> n = tree.nodeData(n).node_and_extra[0],
|
|
|
|
.deref => n = tree.nodeData(n).node,
|
|
|
|
.assign_destructure => n = tree.assignDestructure(n).ast.variables[0],
|
|
|
|
.fn_decl,
|
|
.fn_proto_simple,
|
|
.fn_proto_multi,
|
|
.fn_proto_one,
|
|
.fn_proto,
|
|
=> {
|
|
var i = tree.nodeMainToken(n); // fn token
|
|
while (i > 0) {
|
|
i -= 1;
|
|
switch (tree.tokenTag(i)) {
|
|
.keyword_extern,
|
|
.keyword_export,
|
|
.keyword_pub,
|
|
.keyword_inline,
|
|
.keyword_noinline,
|
|
.string_literal,
|
|
=> continue,
|
|
|
|
else => return i + 1 - end_offset,
|
|
}
|
|
}
|
|
return i - end_offset;
|
|
},
|
|
|
|
.container_field_init,
|
|
.container_field_align,
|
|
.container_field,
|
|
=> {
|
|
const name_token = tree.nodeMainToken(n);
|
|
const has_comptime_token = tree.isTokenPrecededByTags(name_token, &.{.keyword_comptime});
|
|
end_offset += @intFromBool(has_comptime_token);
|
|
return name_token - end_offset;
|
|
},
|
|
|
|
.global_var_decl,
|
|
.local_var_decl,
|
|
.simple_var_decl,
|
|
.aligned_var_decl,
|
|
=> {
|
|
var i = tree.nodeMainToken(n); // mut token
|
|
while (i > 0) {
|
|
i -= 1;
|
|
switch (tree.tokenTag(i)) {
|
|
.keyword_extern,
|
|
.keyword_export,
|
|
.keyword_comptime,
|
|
.keyword_pub,
|
|
.keyword_threadlocal,
|
|
.string_literal,
|
|
=> continue,
|
|
|
|
else => return i + 1 - end_offset,
|
|
}
|
|
}
|
|
return i - end_offset;
|
|
},
|
|
|
|
.block,
|
|
.block_semicolon,
|
|
.block_two,
|
|
.block_two_semicolon,
|
|
=> {
|
|
// Look for a label.
|
|
const lbrace = tree.nodeMainToken(n);
|
|
if (tree.isTokenPrecededByTags(lbrace, &.{ .identifier, .colon })) {
|
|
end_offset += 2;
|
|
}
|
|
return lbrace - end_offset;
|
|
},
|
|
|
|
.container_decl,
|
|
.container_decl_trailing,
|
|
.container_decl_two,
|
|
.container_decl_two_trailing,
|
|
.container_decl_arg,
|
|
.container_decl_arg_trailing,
|
|
.tagged_union,
|
|
.tagged_union_trailing,
|
|
.tagged_union_two,
|
|
.tagged_union_two_trailing,
|
|
.tagged_union_enum_tag,
|
|
.tagged_union_enum_tag_trailing,
|
|
=> {
|
|
const main_token = tree.nodeMainToken(n);
|
|
switch (tree.tokenTag(main_token -| 1)) {
|
|
.keyword_packed, .keyword_extern => end_offset += 1,
|
|
else => {},
|
|
}
|
|
return main_token - end_offset;
|
|
},
|
|
|
|
.ptr_type_aligned,
|
|
.ptr_type_sentinel,
|
|
.ptr_type,
|
|
.ptr_type_bit_range,
|
|
=> return tree.nodeMainToken(n) - end_offset,
|
|
|
|
.switch_case_one,
|
|
.switch_case_inline_one,
|
|
.switch_case,
|
|
.switch_case_inline,
|
|
=> {
|
|
const full_switch = tree.fullSwitchCase(n).?;
|
|
if (full_switch.inline_token) |inline_token| {
|
|
return inline_token;
|
|
} else if (full_switch.ast.values.len == 0) {
|
|
return full_switch.ast.arrow_token - 1 - end_offset; // else token
|
|
} else {
|
|
n = full_switch.ast.values[0];
|
|
}
|
|
},
|
|
|
|
.asm_output, .asm_input => {
|
|
assert(tree.tokenTag(tree.nodeMainToken(n) - 1) == .l_bracket);
|
|
return tree.nodeMainToken(n) - 1 - end_offset;
|
|
},
|
|
|
|
.while_simple,
|
|
.while_cont,
|
|
.@"while",
|
|
.for_simple,
|
|
.@"for",
|
|
=> {
|
|
// Look for a label and inline.
|
|
const main_token = tree.nodeMainToken(n);
|
|
var result = main_token;
|
|
if (tree.isTokenPrecededByTags(result, &.{.keyword_inline})) {
|
|
result = result - 1;
|
|
}
|
|
if (tree.isTokenPrecededByTags(result, &.{ .identifier, .colon })) {
|
|
result = result - 2;
|
|
}
|
|
return result - end_offset;
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex {
|
|
var n = node;
|
|
var end_offset: u32 = 0;
|
|
while (true) switch (tree.nodeTag(n)) {
|
|
.root => return @intCast(tree.tokens.len - 1),
|
|
|
|
.bool_not,
|
|
.negation,
|
|
.bit_not,
|
|
.negation_wrap,
|
|
.address_of,
|
|
.@"try",
|
|
.optional_type,
|
|
.@"suspend",
|
|
.@"resume",
|
|
.@"nosuspend",
|
|
.@"comptime",
|
|
=> n = tree.nodeData(n).node,
|
|
|
|
.@"catch",
|
|
.equal_equal,
|
|
.bang_equal,
|
|
.less_than,
|
|
.greater_than,
|
|
.less_or_equal,
|
|
.greater_or_equal,
|
|
.assign_mul,
|
|
.assign_div,
|
|
.assign_mod,
|
|
.assign_add,
|
|
.assign_sub,
|
|
.assign_shl,
|
|
.assign_shl_sat,
|
|
.assign_shr,
|
|
.assign_bit_and,
|
|
.assign_bit_xor,
|
|
.assign_bit_or,
|
|
.assign_mul_wrap,
|
|
.assign_add_wrap,
|
|
.assign_sub_wrap,
|
|
.assign_mul_sat,
|
|
.assign_add_sat,
|
|
.assign_sub_sat,
|
|
.assign,
|
|
.merge_error_sets,
|
|
.mul,
|
|
.div,
|
|
.mod,
|
|
.array_mult,
|
|
.mul_wrap,
|
|
.mul_sat,
|
|
.add,
|
|
.sub,
|
|
.array_cat,
|
|
.add_wrap,
|
|
.sub_wrap,
|
|
.add_sat,
|
|
.sub_sat,
|
|
.shl,
|
|
.shl_sat,
|
|
.shr,
|
|
.bit_and,
|
|
.bit_xor,
|
|
.bit_or,
|
|
.@"orelse",
|
|
.bool_and,
|
|
.bool_or,
|
|
.error_union,
|
|
.if_simple,
|
|
.while_simple,
|
|
.for_simple,
|
|
.fn_decl,
|
|
.array_type,
|
|
.switch_range,
|
|
=> n = tree.nodeData(n).node_and_node[1],
|
|
|
|
.test_decl, .@"errdefer" => n = tree.nodeData(n).opt_token_and_node[1],
|
|
.@"defer" => n = tree.nodeData(n).node,
|
|
.anyframe_type => n = tree.nodeData(n).token_and_node[1],
|
|
|
|
.switch_case_one,
|
|
.switch_case_inline_one,
|
|
.ptr_type_aligned,
|
|
.ptr_type_sentinel,
|
|
=> n = tree.nodeData(n).opt_node_and_node[1],
|
|
|
|
.assign_destructure,
|
|
.ptr_type,
|
|
.ptr_type_bit_range,
|
|
.switch_case,
|
|
.switch_case_inline,
|
|
=> n = tree.nodeData(n).extra_and_node[1],
|
|
|
|
.fn_proto_simple => n = tree.nodeData(n).opt_node_and_opt_node[1].unwrap().?,
|
|
.fn_proto_multi,
|
|
.fn_proto_one,
|
|
.fn_proto,
|
|
=> n = tree.nodeData(n).extra_and_opt_node[1].unwrap().?,
|
|
|
|
.for_range => {
|
|
n = tree.nodeData(n).node_and_opt_node[1].unwrap() orelse {
|
|
return tree.nodeMainToken(n) + end_offset;
|
|
};
|
|
},
|
|
|
|
.field_access,
|
|
.unwrap_optional,
|
|
.asm_simple,
|
|
=> return tree.nodeData(n).node_and_token[1] + end_offset,
|
|
.grouped_expression, .asm_input => return tree.nodeData(n).node_and_token[1] + end_offset,
|
|
.multiline_string_literal, .error_set_decl => return tree.nodeData(n).token_and_token[1] + end_offset,
|
|
.asm_output => return tree.nodeData(n).opt_node_and_token[1] + end_offset,
|
|
.error_value => return tree.nodeMainToken(n) + 2 + end_offset,
|
|
|
|
.anyframe_literal,
|
|
.char_literal,
|
|
.number_literal,
|
|
.unreachable_literal,
|
|
.identifier,
|
|
.deref,
|
|
.enum_literal,
|
|
.string_literal,
|
|
=> return tree.nodeMainToken(n) + end_offset,
|
|
|
|
.@"return" => {
|
|
n = tree.nodeData(n).opt_node.unwrap() orelse {
|
|
return tree.nodeMainToken(n) + end_offset;
|
|
};
|
|
},
|
|
|
|
.call => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const params = tree.extraData(extra_index, Node.SubRange);
|
|
assert(params.start != params.end);
|
|
end_offset += 1; // for the rparen
|
|
n = @enumFromInt(tree.extra_data[@intFromEnum(params.end) - 1]); // last parameter
|
|
},
|
|
.tagged_union_enum_tag => {
|
|
const arg, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const members = tree.extraData(extra_index, Node.SubRange);
|
|
if (members.start == members.end) {
|
|
end_offset += 4; // for the rparen + rparen + lbrace + rbrace
|
|
n = arg;
|
|
} else {
|
|
end_offset += 1; // for the rbrace
|
|
n = @enumFromInt(tree.extra_data[@intFromEnum(members.end) - 1]); // last parameter
|
|
}
|
|
},
|
|
.call_comma,
|
|
.tagged_union_enum_tag_trailing,
|
|
=> {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const params = tree.extraData(extra_index, Node.SubRange);
|
|
assert(params.start != params.end);
|
|
end_offset += 2; // for the comma/semicolon + rparen/rbrace
|
|
n = @enumFromInt(tree.extra_data[@intFromEnum(params.end) - 1]); // last parameter
|
|
},
|
|
.@"switch" => {
|
|
const condition, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const cases = tree.extraData(extra_index, Node.SubRange);
|
|
if (cases.start == cases.end) {
|
|
end_offset += 3; // rparen, lbrace, rbrace
|
|
n = condition;
|
|
} else {
|
|
end_offset += 1; // for the rbrace
|
|
n = @enumFromInt(tree.extra_data[@intFromEnum(cases.end) - 1]); // last case
|
|
}
|
|
},
|
|
.container_decl_arg => {
|
|
const arg, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const members = tree.extraData(extra_index, Node.SubRange);
|
|
if (members.end == members.start) {
|
|
end_offset += 3; // for the rparen + lbrace + rbrace
|
|
n = arg;
|
|
} else {
|
|
end_offset += 1; // for the rbrace
|
|
n = @enumFromInt(tree.extra_data[@intFromEnum(members.end) - 1]); // last parameter
|
|
}
|
|
},
|
|
.asm_legacy => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.AsmLegacy);
|
|
return extra.rparen + end_offset;
|
|
},
|
|
.@"asm" => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.Asm);
|
|
return extra.rparen + end_offset;
|
|
},
|
|
.array_init,
|
|
.struct_init,
|
|
=> {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const elements = tree.extraData(extra_index, Node.SubRange);
|
|
assert(elements.start != elements.end);
|
|
end_offset += 1; // for the rbrace
|
|
n = @enumFromInt(tree.extra_data[@intFromEnum(elements.end) - 1]); // last element
|
|
},
|
|
.array_init_comma,
|
|
.struct_init_comma,
|
|
.container_decl_arg_trailing,
|
|
.switch_comma,
|
|
=> {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const members = tree.extraData(extra_index, Node.SubRange);
|
|
assert(members.start != members.end);
|
|
end_offset += 2; // for the comma + rbrace
|
|
n = @enumFromInt(tree.extra_data[@intFromEnum(members.end) - 1]); // last parameter
|
|
},
|
|
.array_init_dot,
|
|
.struct_init_dot,
|
|
.block,
|
|
.container_decl,
|
|
.tagged_union,
|
|
.builtin_call,
|
|
=> {
|
|
const range = tree.nodeData(n).extra_range;
|
|
assert(range.start != range.end);
|
|
end_offset += 1; // for the rbrace
|
|
n = @enumFromInt(tree.extra_data[@intFromEnum(range.end) - 1]); // last statement
|
|
},
|
|
.array_init_dot_comma,
|
|
.struct_init_dot_comma,
|
|
.block_semicolon,
|
|
.container_decl_trailing,
|
|
.tagged_union_trailing,
|
|
.builtin_call_comma,
|
|
=> {
|
|
const range = tree.nodeData(n).extra_range;
|
|
assert(range.start != range.end);
|
|
end_offset += 2; // for the comma/semicolon + rbrace/rparen
|
|
n = @enumFromInt(tree.extra_data[@intFromEnum(range.end) - 1]); // last member
|
|
},
|
|
.call_one,
|
|
=> {
|
|
_, const first_param = tree.nodeData(n).node_and_opt_node;
|
|
end_offset += 1; // for the rparen
|
|
n = first_param.unwrap() orelse {
|
|
return tree.nodeMainToken(n) + end_offset;
|
|
};
|
|
},
|
|
|
|
.array_init_dot_two,
|
|
.block_two,
|
|
.builtin_call_two,
|
|
.struct_init_dot_two,
|
|
.container_decl_two,
|
|
.tagged_union_two,
|
|
=> {
|
|
const opt_lhs, const opt_rhs = tree.nodeData(n).opt_node_and_opt_node;
|
|
if (opt_rhs.unwrap()) |rhs| {
|
|
end_offset += 1; // for the rparen/rbrace
|
|
n = rhs;
|
|
} else if (opt_lhs.unwrap()) |lhs| {
|
|
end_offset += 1; // for the rparen/rbrace
|
|
n = lhs;
|
|
} else {
|
|
switch (tree.nodeTag(n)) {
|
|
.array_init_dot_two,
|
|
.block_two,
|
|
.struct_init_dot_two,
|
|
=> end_offset += 1, // rbrace
|
|
.builtin_call_two => end_offset += 2, // lparen/lbrace + rparen/rbrace
|
|
.container_decl_two => {
|
|
var i: u32 = 2; // lbrace + rbrace
|
|
while (tree.tokenTag(tree.nodeMainToken(n) + i) == .container_doc_comment) i += 1;
|
|
end_offset += i;
|
|
},
|
|
.tagged_union_two => {
|
|
var i: u32 = 5; // (enum) {}
|
|
while (tree.tokenTag(tree.nodeMainToken(n) + i) == .container_doc_comment) i += 1;
|
|
end_offset += i;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
return tree.nodeMainToken(n) + end_offset;
|
|
}
|
|
},
|
|
.array_init_dot_two_comma,
|
|
.builtin_call_two_comma,
|
|
.block_two_semicolon,
|
|
.struct_init_dot_two_comma,
|
|
.container_decl_two_trailing,
|
|
.tagged_union_two_trailing,
|
|
=> {
|
|
const opt_lhs, const opt_rhs = tree.nodeData(n).opt_node_and_opt_node;
|
|
end_offset += 2; // for the comma/semicolon + rbrace/rparen
|
|
if (opt_rhs.unwrap()) |rhs| {
|
|
n = rhs;
|
|
} else if (opt_lhs.unwrap()) |lhs| {
|
|
n = lhs;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
},
|
|
.simple_var_decl => {
|
|
const type_node, const init_node = tree.nodeData(n).opt_node_and_opt_node;
|
|
if (init_node.unwrap()) |rhs| {
|
|
n = rhs;
|
|
} else if (type_node.unwrap()) |lhs| {
|
|
n = lhs;
|
|
} else {
|
|
end_offset += 1; // from mut token to name
|
|
return tree.nodeMainToken(n) + end_offset;
|
|
}
|
|
},
|
|
.aligned_var_decl => {
|
|
const align_node, const init_node = tree.nodeData(n).node_and_opt_node;
|
|
if (init_node.unwrap()) |rhs| {
|
|
n = rhs;
|
|
} else {
|
|
end_offset += 1; // for the rparen
|
|
n = align_node;
|
|
}
|
|
},
|
|
.global_var_decl => {
|
|
const extra_index, const init_node = tree.nodeData(n).extra_and_opt_node;
|
|
if (init_node.unwrap()) |rhs| {
|
|
n = rhs;
|
|
} else {
|
|
const extra = tree.extraData(extra_index, Node.GlobalVarDecl);
|
|
if (extra.section_node.unwrap()) |section_node| {
|
|
end_offset += 1; // for the rparen
|
|
n = section_node;
|
|
} else if (extra.align_node.unwrap()) |align_node| {
|
|
end_offset += 1; // for the rparen
|
|
n = align_node;
|
|
} else if (extra.type_node.unwrap()) |type_node| {
|
|
n = type_node;
|
|
} else {
|
|
end_offset += 1; // from mut token to name
|
|
return tree.nodeMainToken(n) + end_offset;
|
|
}
|
|
}
|
|
},
|
|
.local_var_decl => {
|
|
const extra_index, const init_node = tree.nodeData(n).extra_and_opt_node;
|
|
if (init_node.unwrap()) |rhs| {
|
|
n = rhs;
|
|
} else {
|
|
const extra = tree.extraData(extra_index, Node.LocalVarDecl);
|
|
end_offset += 1; // for the rparen
|
|
n = extra.align_node;
|
|
}
|
|
},
|
|
.container_field_init => {
|
|
const type_expr, const value_expr = tree.nodeData(n).node_and_opt_node;
|
|
n = value_expr.unwrap() orelse type_expr;
|
|
},
|
|
|
|
.array_access,
|
|
.array_init_one,
|
|
.container_field_align,
|
|
=> {
|
|
_, const rhs = tree.nodeData(n).node_and_node;
|
|
end_offset += 1; // for the rbracket/rbrace/rparen
|
|
n = rhs;
|
|
},
|
|
.container_field => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.ContainerField);
|
|
n = extra.value_expr;
|
|
},
|
|
|
|
.struct_init_one => {
|
|
_, const first_field = tree.nodeData(n).node_and_opt_node;
|
|
end_offset += 1; // rbrace
|
|
n = first_field.unwrap() orelse {
|
|
return tree.nodeMainToken(n) + end_offset;
|
|
};
|
|
},
|
|
.slice_open => {
|
|
_, const start_node = tree.nodeData(n).node_and_node;
|
|
end_offset += 2; // ellipsis2 + rbracket, or comma + rparen
|
|
n = start_node;
|
|
},
|
|
.array_init_one_comma => {
|
|
_, const first_element = tree.nodeData(n).node_and_node;
|
|
end_offset += 2; // comma + rbrace
|
|
n = first_element;
|
|
},
|
|
.call_one_comma,
|
|
.struct_init_one_comma,
|
|
=> {
|
|
_, const first_field = tree.nodeData(n).node_and_opt_node;
|
|
end_offset += 2; // ellipsis2 + rbracket, or comma + rparen
|
|
n = first_field.unwrap().?;
|
|
},
|
|
.slice => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.Slice);
|
|
end_offset += 1; // rbracket
|
|
n = extra.end;
|
|
},
|
|
.slice_sentinel => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.SliceSentinel);
|
|
end_offset += 1; // rbracket
|
|
n = extra.sentinel;
|
|
},
|
|
|
|
.@"continue", .@"break" => {
|
|
const opt_label, const opt_rhs = tree.nodeData(n).opt_token_and_opt_node;
|
|
if (opt_rhs.unwrap()) |rhs| {
|
|
n = rhs;
|
|
} else if (opt_label.unwrap()) |lhs| {
|
|
return lhs + end_offset;
|
|
} else {
|
|
return tree.nodeMainToken(n) + end_offset;
|
|
}
|
|
},
|
|
.while_cont => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.WhileCont);
|
|
n = extra.then_expr;
|
|
},
|
|
.@"while" => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.While);
|
|
n = extra.else_expr;
|
|
},
|
|
.@"if" => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.If);
|
|
n = extra.else_expr;
|
|
},
|
|
.@"for" => {
|
|
const extra_index, const extra = tree.nodeData(n).@"for";
|
|
const index = @intFromEnum(extra_index) + extra.inputs + @intFromBool(extra.has_else);
|
|
n = @enumFromInt(tree.extra_data[index]);
|
|
},
|
|
.array_type_sentinel => {
|
|
_, const extra_index = tree.nodeData(n).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.ArrayTypeSentinel);
|
|
n = extra.elem_type;
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn tokensOnSameLine(tree: Ast, token1: TokenIndex, token2: TokenIndex) bool {
|
|
const source = tree.source[tree.tokenStart(token1)..tree.tokenStart(token2)];
|
|
return mem.indexOfScalar(u8, source, '\n') == null;
|
|
}
|
|
|
|
pub fn getNodeSource(tree: Ast, node: Node.Index) []const u8 {
|
|
const first_token = tree.firstToken(node);
|
|
const last_token = tree.lastToken(node);
|
|
const start = tree.tokenStart(first_token);
|
|
const end = tree.tokenStart(last_token) + tree.tokenSlice(last_token).len;
|
|
return tree.source[start..end];
|
|
}
|
|
|
|
pub fn globalVarDecl(tree: Ast, node: Node.Index) full.VarDecl {
|
|
assert(tree.nodeTag(node) == .global_var_decl);
|
|
const extra_index, const init_node = tree.nodeData(node).extra_and_opt_node;
|
|
const extra = tree.extraData(extra_index, Node.GlobalVarDecl);
|
|
return tree.fullVarDeclComponents(.{
|
|
.type_node = extra.type_node,
|
|
.align_node = extra.align_node,
|
|
.addrspace_node = extra.addrspace_node,
|
|
.section_node = extra.section_node,
|
|
.init_node = init_node,
|
|
.mut_token = tree.nodeMainToken(node),
|
|
});
|
|
}
|
|
|
|
pub fn localVarDecl(tree: Ast, node: Node.Index) full.VarDecl {
|
|
assert(tree.nodeTag(node) == .local_var_decl);
|
|
const extra_index, const init_node = tree.nodeData(node).extra_and_opt_node;
|
|
const extra = tree.extraData(extra_index, Node.LocalVarDecl);
|
|
return tree.fullVarDeclComponents(.{
|
|
.type_node = extra.type_node.toOptional(),
|
|
.align_node = extra.align_node.toOptional(),
|
|
.addrspace_node = .none,
|
|
.section_node = .none,
|
|
.init_node = init_node,
|
|
.mut_token = tree.nodeMainToken(node),
|
|
});
|
|
}
|
|
|
|
pub fn simpleVarDecl(tree: Ast, node: Node.Index) full.VarDecl {
|
|
assert(tree.nodeTag(node) == .simple_var_decl);
|
|
const type_node, const init_node = tree.nodeData(node).opt_node_and_opt_node;
|
|
return tree.fullVarDeclComponents(.{
|
|
.type_node = type_node,
|
|
.align_node = .none,
|
|
.addrspace_node = .none,
|
|
.section_node = .none,
|
|
.init_node = init_node,
|
|
.mut_token = tree.nodeMainToken(node),
|
|
});
|
|
}
|
|
|
|
pub fn alignedVarDecl(tree: Ast, node: Node.Index) full.VarDecl {
|
|
assert(tree.nodeTag(node) == .aligned_var_decl);
|
|
const align_node, const init_node = tree.nodeData(node).node_and_opt_node;
|
|
return tree.fullVarDeclComponents(.{
|
|
.type_node = .none,
|
|
.align_node = align_node.toOptional(),
|
|
.addrspace_node = .none,
|
|
.section_node = .none,
|
|
.init_node = init_node,
|
|
.mut_token = tree.nodeMainToken(node),
|
|
});
|
|
}
|
|
|
|
pub fn assignDestructure(tree: Ast, node: Node.Index) full.AssignDestructure {
|
|
const extra_index, const value_expr = tree.nodeData(node).extra_and_node;
|
|
const variable_count = tree.extra_data[@intFromEnum(extra_index)];
|
|
return tree.fullAssignDestructureComponents(.{
|
|
.variables = tree.extraDataSliceWithLen(@enumFromInt(@intFromEnum(extra_index) + 1), variable_count, Node.Index),
|
|
.equal_token = tree.nodeMainToken(node),
|
|
.value_expr = value_expr,
|
|
});
|
|
}
|
|
|
|
pub fn ifSimple(tree: Ast, node: Node.Index) full.If {
|
|
assert(tree.nodeTag(node) == .if_simple);
|
|
const cond_expr, const then_expr = tree.nodeData(node).node_and_node;
|
|
return tree.fullIfComponents(.{
|
|
.cond_expr = cond_expr,
|
|
.then_expr = then_expr,
|
|
.else_expr = .none,
|
|
.if_token = tree.nodeMainToken(node),
|
|
});
|
|
}
|
|
|
|
pub fn ifFull(tree: Ast, node: Node.Index) full.If {
|
|
assert(tree.nodeTag(node) == .@"if");
|
|
const cond_expr, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.If);
|
|
return tree.fullIfComponents(.{
|
|
.cond_expr = cond_expr,
|
|
.then_expr = extra.then_expr,
|
|
.else_expr = extra.else_expr.toOptional(),
|
|
.if_token = tree.nodeMainToken(node),
|
|
});
|
|
}
|
|
|
|
pub fn containerField(tree: Ast, node: Node.Index) full.ContainerField {
|
|
assert(tree.nodeTag(node) == .container_field);
|
|
const type_expr, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.ContainerField);
|
|
const main_token = tree.nodeMainToken(node);
|
|
return tree.fullContainerFieldComponents(.{
|
|
.main_token = main_token,
|
|
.type_expr = type_expr.toOptional(),
|
|
.align_expr = extra.align_expr.toOptional(),
|
|
.value_expr = extra.value_expr.toOptional(),
|
|
.tuple_like = tree.tokenTag(main_token) != .identifier or
|
|
tree.tokenTag(main_token + 1) != .colon,
|
|
});
|
|
}
|
|
|
|
pub fn containerFieldInit(tree: Ast, node: Node.Index) full.ContainerField {
|
|
assert(tree.nodeTag(node) == .container_field_init);
|
|
const type_expr, const value_expr = tree.nodeData(node).node_and_opt_node;
|
|
const main_token = tree.nodeMainToken(node);
|
|
return tree.fullContainerFieldComponents(.{
|
|
.main_token = main_token,
|
|
.type_expr = type_expr.toOptional(),
|
|
.align_expr = .none,
|
|
.value_expr = value_expr,
|
|
.tuple_like = tree.tokenTag(main_token) != .identifier or
|
|
tree.tokenTag(main_token + 1) != .colon,
|
|
});
|
|
}
|
|
|
|
pub fn containerFieldAlign(tree: Ast, node: Node.Index) full.ContainerField {
|
|
assert(tree.nodeTag(node) == .container_field_align);
|
|
const type_expr, const align_expr = tree.nodeData(node).node_and_node;
|
|
const main_token = tree.nodeMainToken(node);
|
|
return tree.fullContainerFieldComponents(.{
|
|
.main_token = main_token,
|
|
.type_expr = type_expr.toOptional(),
|
|
.align_expr = align_expr.toOptional(),
|
|
.value_expr = .none,
|
|
.tuple_like = tree.tokenTag(main_token) != .identifier or
|
|
tree.tokenTag(main_token + 1) != .colon,
|
|
});
|
|
}
|
|
|
|
pub fn fnProtoSimple(tree: Ast, buffer: *[1]Node.Index, node: Node.Index) full.FnProto {
|
|
assert(tree.nodeTag(node) == .fn_proto_simple);
|
|
const first_param, const return_type = tree.nodeData(node).opt_node_and_opt_node;
|
|
const params = loadOptionalNodesIntoBuffer(1, buffer, .{first_param});
|
|
return tree.fullFnProtoComponents(.{
|
|
.proto_node = node,
|
|
.fn_token = tree.nodeMainToken(node),
|
|
.return_type = return_type,
|
|
.params = params,
|
|
.align_expr = .none,
|
|
.addrspace_expr = .none,
|
|
.section_expr = .none,
|
|
.callconv_expr = .none,
|
|
});
|
|
}
|
|
|
|
pub fn fnProtoMulti(tree: Ast, node: Node.Index) full.FnProto {
|
|
assert(tree.nodeTag(node) == .fn_proto_multi);
|
|
const extra_index, const return_type = tree.nodeData(node).extra_and_opt_node;
|
|
const params = tree.extraDataSlice(tree.extraData(extra_index, Node.SubRange), Node.Index);
|
|
return tree.fullFnProtoComponents(.{
|
|
.proto_node = node,
|
|
.fn_token = tree.nodeMainToken(node),
|
|
.return_type = return_type,
|
|
.params = params,
|
|
.align_expr = .none,
|
|
.addrspace_expr = .none,
|
|
.section_expr = .none,
|
|
.callconv_expr = .none,
|
|
});
|
|
}
|
|
|
|
pub fn fnProtoOne(tree: Ast, buffer: *[1]Node.Index, node: Node.Index) full.FnProto {
|
|
assert(tree.nodeTag(node) == .fn_proto_one);
|
|
const extra_index, const return_type = tree.nodeData(node).extra_and_opt_node;
|
|
const extra = tree.extraData(extra_index, Node.FnProtoOne);
|
|
const params = loadOptionalNodesIntoBuffer(1, buffer, .{extra.param});
|
|
return tree.fullFnProtoComponents(.{
|
|
.proto_node = node,
|
|
.fn_token = tree.nodeMainToken(node),
|
|
.return_type = return_type,
|
|
.params = params,
|
|
.align_expr = extra.align_expr,
|
|
.addrspace_expr = extra.addrspace_expr,
|
|
.section_expr = extra.section_expr,
|
|
.callconv_expr = extra.callconv_expr,
|
|
});
|
|
}
|
|
|
|
pub fn fnProto(tree: Ast, node: Node.Index) full.FnProto {
|
|
assert(tree.nodeTag(node) == .fn_proto);
|
|
const extra_index, const return_type = tree.nodeData(node).extra_and_opt_node;
|
|
const extra = tree.extraData(extra_index, Node.FnProto);
|
|
const params = tree.extraDataSlice(.{ .start = extra.params_start, .end = extra.params_end }, Node.Index);
|
|
return tree.fullFnProtoComponents(.{
|
|
.proto_node = node,
|
|
.fn_token = tree.nodeMainToken(node),
|
|
.return_type = return_type,
|
|
.params = params,
|
|
.align_expr = extra.align_expr,
|
|
.addrspace_expr = extra.addrspace_expr,
|
|
.section_expr = extra.section_expr,
|
|
.callconv_expr = extra.callconv_expr,
|
|
});
|
|
}
|
|
|
|
pub fn structInitOne(tree: Ast, buffer: *[1]Node.Index, node: Node.Index) full.StructInit {
|
|
assert(tree.nodeTag(node) == .struct_init_one or
|
|
tree.nodeTag(node) == .struct_init_one_comma);
|
|
const type_expr, const first_field = tree.nodeData(node).node_and_opt_node;
|
|
const fields = loadOptionalNodesIntoBuffer(1, buffer, .{first_field});
|
|
return .{
|
|
.ast = .{
|
|
.lbrace = tree.nodeMainToken(node),
|
|
.fields = fields,
|
|
.type_expr = type_expr.toOptional(),
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn structInitDotTwo(tree: Ast, buffer: *[2]Node.Index, node: Node.Index) full.StructInit {
|
|
assert(tree.nodeTag(node) == .struct_init_dot_two or
|
|
tree.nodeTag(node) == .struct_init_dot_two_comma);
|
|
const fields = loadOptionalNodesIntoBuffer(2, buffer, tree.nodeData(node).opt_node_and_opt_node);
|
|
return .{
|
|
.ast = .{
|
|
.lbrace = tree.nodeMainToken(node),
|
|
.fields = fields,
|
|
.type_expr = .none,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn structInitDot(tree: Ast, node: Node.Index) full.StructInit {
|
|
assert(tree.nodeTag(node) == .struct_init_dot or
|
|
tree.nodeTag(node) == .struct_init_dot_comma);
|
|
const fields = tree.extraDataSlice(tree.nodeData(node).extra_range, Node.Index);
|
|
return .{
|
|
.ast = .{
|
|
.lbrace = tree.nodeMainToken(node),
|
|
.fields = fields,
|
|
.type_expr = .none,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn structInit(tree: Ast, node: Node.Index) full.StructInit {
|
|
assert(tree.nodeTag(node) == .struct_init or
|
|
tree.nodeTag(node) == .struct_init_comma);
|
|
const type_expr, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const fields = tree.extraDataSlice(tree.extraData(extra_index, Node.SubRange), Node.Index);
|
|
return .{
|
|
.ast = .{
|
|
.lbrace = tree.nodeMainToken(node),
|
|
.fields = fields,
|
|
.type_expr = type_expr.toOptional(),
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn arrayInitOne(tree: Ast, buffer: *[1]Node.Index, node: Node.Index) full.ArrayInit {
|
|
assert(tree.nodeTag(node) == .array_init_one or
|
|
tree.nodeTag(node) == .array_init_one_comma);
|
|
const type_expr, buffer[0] = tree.nodeData(node).node_and_node;
|
|
return .{
|
|
.ast = .{
|
|
.lbrace = tree.nodeMainToken(node),
|
|
.elements = buffer[0..1],
|
|
.type_expr = type_expr.toOptional(),
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn arrayInitDotTwo(tree: Ast, buffer: *[2]Node.Index, node: Node.Index) full.ArrayInit {
|
|
assert(tree.nodeTag(node) == .array_init_dot_two or
|
|
tree.nodeTag(node) == .array_init_dot_two_comma);
|
|
const elements = loadOptionalNodesIntoBuffer(2, buffer, tree.nodeData(node).opt_node_and_opt_node);
|
|
return .{
|
|
.ast = .{
|
|
.lbrace = tree.nodeMainToken(node),
|
|
.elements = elements,
|
|
.type_expr = .none,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn arrayInitDot(tree: Ast, node: Node.Index) full.ArrayInit {
|
|
assert(tree.nodeTag(node) == .array_init_dot or
|
|
tree.nodeTag(node) == .array_init_dot_comma);
|
|
const elements = tree.extraDataSlice(tree.nodeData(node).extra_range, Node.Index);
|
|
return .{
|
|
.ast = .{
|
|
.lbrace = tree.nodeMainToken(node),
|
|
.elements = elements,
|
|
.type_expr = .none,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn arrayInit(tree: Ast, node: Node.Index) full.ArrayInit {
|
|
assert(tree.nodeTag(node) == .array_init or
|
|
tree.nodeTag(node) == .array_init_comma);
|
|
const type_expr, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const elements = tree.extraDataSlice(tree.extraData(extra_index, Node.SubRange), Node.Index);
|
|
return .{
|
|
.ast = .{
|
|
.lbrace = tree.nodeMainToken(node),
|
|
.elements = elements,
|
|
.type_expr = type_expr.toOptional(),
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn arrayType(tree: Ast, node: Node.Index) full.ArrayType {
|
|
assert(tree.nodeTag(node) == .array_type);
|
|
const elem_count, const elem_type = tree.nodeData(node).node_and_node;
|
|
return .{
|
|
.ast = .{
|
|
.lbracket = tree.nodeMainToken(node),
|
|
.elem_count = elem_count,
|
|
.sentinel = .none,
|
|
.elem_type = elem_type,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn arrayTypeSentinel(tree: Ast, node: Node.Index) full.ArrayType {
|
|
assert(tree.nodeTag(node) == .array_type_sentinel);
|
|
const elem_count, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.ArrayTypeSentinel);
|
|
return .{
|
|
.ast = .{
|
|
.lbracket = tree.nodeMainToken(node),
|
|
.elem_count = elem_count,
|
|
.sentinel = extra.sentinel.toOptional(),
|
|
.elem_type = extra.elem_type,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn ptrTypeAligned(tree: Ast, node: Node.Index) full.PtrType {
|
|
assert(tree.nodeTag(node) == .ptr_type_aligned);
|
|
const align_node, const child_type = tree.nodeData(node).opt_node_and_node;
|
|
return tree.fullPtrTypeComponents(.{
|
|
.main_token = tree.nodeMainToken(node),
|
|
.align_node = align_node,
|
|
.addrspace_node = .none,
|
|
.sentinel = .none,
|
|
.bit_range_start = .none,
|
|
.bit_range_end = .none,
|
|
.child_type = child_type,
|
|
});
|
|
}
|
|
|
|
pub fn ptrTypeSentinel(tree: Ast, node: Node.Index) full.PtrType {
|
|
assert(tree.nodeTag(node) == .ptr_type_sentinel);
|
|
const sentinel, const child_type = tree.nodeData(node).opt_node_and_node;
|
|
return tree.fullPtrTypeComponents(.{
|
|
.main_token = tree.nodeMainToken(node),
|
|
.align_node = .none,
|
|
.addrspace_node = .none,
|
|
.sentinel = sentinel,
|
|
.bit_range_start = .none,
|
|
.bit_range_end = .none,
|
|
.child_type = child_type,
|
|
});
|
|
}
|
|
|
|
pub fn ptrType(tree: Ast, node: Node.Index) full.PtrType {
|
|
assert(tree.nodeTag(node) == .ptr_type);
|
|
const extra_index, const child_type = tree.nodeData(node).extra_and_node;
|
|
const extra = tree.extraData(extra_index, Node.PtrType);
|
|
return tree.fullPtrTypeComponents(.{
|
|
.main_token = tree.nodeMainToken(node),
|
|
.align_node = extra.align_node,
|
|
.addrspace_node = extra.addrspace_node,
|
|
.sentinel = extra.sentinel,
|
|
.bit_range_start = .none,
|
|
.bit_range_end = .none,
|
|
.child_type = child_type,
|
|
});
|
|
}
|
|
|
|
pub fn ptrTypeBitRange(tree: Ast, node: Node.Index) full.PtrType {
|
|
assert(tree.nodeTag(node) == .ptr_type_bit_range);
|
|
const extra_index, const child_type = tree.nodeData(node).extra_and_node;
|
|
const extra = tree.extraData(extra_index, Node.PtrTypeBitRange);
|
|
return tree.fullPtrTypeComponents(.{
|
|
.main_token = tree.nodeMainToken(node),
|
|
.align_node = extra.align_node.toOptional(),
|
|
.addrspace_node = extra.addrspace_node,
|
|
.sentinel = extra.sentinel,
|
|
.bit_range_start = extra.bit_range_start.toOptional(),
|
|
.bit_range_end = extra.bit_range_end.toOptional(),
|
|
.child_type = child_type,
|
|
});
|
|
}
|
|
|
|
pub fn sliceOpen(tree: Ast, node: Node.Index) full.Slice {
|
|
assert(tree.nodeTag(node) == .slice_open);
|
|
const sliced, const start = tree.nodeData(node).node_and_node;
|
|
return .{
|
|
.ast = .{
|
|
.sliced = sliced,
|
|
.lbracket = tree.nodeMainToken(node),
|
|
.start = start,
|
|
.end = .none,
|
|
.sentinel = .none,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn slice(tree: Ast, node: Node.Index) full.Slice {
|
|
assert(tree.nodeTag(node) == .slice);
|
|
const sliced, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.Slice);
|
|
return .{
|
|
.ast = .{
|
|
.sliced = sliced,
|
|
.lbracket = tree.nodeMainToken(node),
|
|
.start = extra.start,
|
|
.end = extra.end.toOptional(),
|
|
.sentinel = .none,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn sliceSentinel(tree: Ast, node: Node.Index) full.Slice {
|
|
assert(tree.nodeTag(node) == .slice_sentinel);
|
|
const sliced, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.SliceSentinel);
|
|
return .{
|
|
.ast = .{
|
|
.sliced = sliced,
|
|
.lbracket = tree.nodeMainToken(node),
|
|
.start = extra.start,
|
|
.end = extra.end,
|
|
.sentinel = extra.sentinel.toOptional(),
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn containerDeclTwo(tree: Ast, buffer: *[2]Node.Index, node: Node.Index) full.ContainerDecl {
|
|
assert(tree.nodeTag(node) == .container_decl_two or
|
|
tree.nodeTag(node) == .container_decl_two_trailing);
|
|
const members = loadOptionalNodesIntoBuffer(2, buffer, tree.nodeData(node).opt_node_and_opt_node);
|
|
return tree.fullContainerDeclComponents(.{
|
|
.main_token = tree.nodeMainToken(node),
|
|
.enum_token = null,
|
|
.members = members,
|
|
.arg = .none,
|
|
});
|
|
}
|
|
|
|
pub fn containerDecl(tree: Ast, node: Node.Index) full.ContainerDecl {
|
|
assert(tree.nodeTag(node) == .container_decl or
|
|
tree.nodeTag(node) == .container_decl_trailing);
|
|
const members = tree.extraDataSlice(tree.nodeData(node).extra_range, Node.Index);
|
|
return tree.fullContainerDeclComponents(.{
|
|
.main_token = tree.nodeMainToken(node),
|
|
.enum_token = null,
|
|
.members = members,
|
|
.arg = .none,
|
|
});
|
|
}
|
|
|
|
pub fn containerDeclArg(tree: Ast, node: Node.Index) full.ContainerDecl {
|
|
assert(tree.nodeTag(node) == .container_decl_arg or
|
|
tree.nodeTag(node) == .container_decl_arg_trailing);
|
|
const arg, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const members = tree.extraDataSlice(tree.extraData(extra_index, Node.SubRange), Node.Index);
|
|
return tree.fullContainerDeclComponents(.{
|
|
.main_token = tree.nodeMainToken(node),
|
|
.enum_token = null,
|
|
.members = members,
|
|
.arg = arg.toOptional(),
|
|
});
|
|
}
|
|
|
|
pub fn containerDeclRoot(tree: Ast) full.ContainerDecl {
|
|
return .{
|
|
.layout_token = null,
|
|
.ast = .{
|
|
.main_token = 0,
|
|
.enum_token = null,
|
|
.members = tree.rootDecls(),
|
|
.arg = .none,
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn taggedUnionTwo(tree: Ast, buffer: *[2]Node.Index, node: Node.Index) full.ContainerDecl {
|
|
assert(tree.nodeTag(node) == .tagged_union_two or
|
|
tree.nodeTag(node) == .tagged_union_two_trailing);
|
|
const members = loadOptionalNodesIntoBuffer(2, buffer, tree.nodeData(node).opt_node_and_opt_node);
|
|
const main_token = tree.nodeMainToken(node);
|
|
return tree.fullContainerDeclComponents(.{
|
|
.main_token = main_token,
|
|
.enum_token = main_token + 2, // union lparen enum
|
|
.members = members,
|
|
.arg = .none,
|
|
});
|
|
}
|
|
|
|
pub fn taggedUnion(tree: Ast, node: Node.Index) full.ContainerDecl {
|
|
assert(tree.nodeTag(node) == .tagged_union or
|
|
tree.nodeTag(node) == .tagged_union_trailing);
|
|
const members = tree.extraDataSlice(tree.nodeData(node).extra_range, Node.Index);
|
|
const main_token = tree.nodeMainToken(node);
|
|
return tree.fullContainerDeclComponents(.{
|
|
.main_token = main_token,
|
|
.enum_token = main_token + 2, // union lparen enum
|
|
.members = members,
|
|
.arg = .none,
|
|
});
|
|
}
|
|
|
|
pub fn taggedUnionEnumTag(tree: Ast, node: Node.Index) full.ContainerDecl {
|
|
assert(tree.nodeTag(node) == .tagged_union_enum_tag or
|
|
tree.nodeTag(node) == .tagged_union_enum_tag_trailing);
|
|
const arg, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const members = tree.extraDataSlice(tree.extraData(extra_index, Node.SubRange), Node.Index);
|
|
const main_token = tree.nodeMainToken(node);
|
|
return tree.fullContainerDeclComponents(.{
|
|
.main_token = main_token,
|
|
.enum_token = main_token + 2, // union lparen enum
|
|
.members = members,
|
|
.arg = arg.toOptional(),
|
|
});
|
|
}
|
|
|
|
pub fn switchFull(tree: Ast, node: Node.Index) full.Switch {
|
|
const main_token = tree.nodeMainToken(node);
|
|
const switch_token: TokenIndex, const label_token: ?TokenIndex = switch (tree.tokenTag(main_token)) {
|
|
.identifier => .{ main_token + 2, main_token },
|
|
.keyword_switch => .{ main_token, null },
|
|
else => unreachable,
|
|
};
|
|
const condition, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const cases = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Node.Index);
|
|
return .{
|
|
.ast = .{
|
|
.switch_token = switch_token,
|
|
.condition = condition,
|
|
.cases = cases,
|
|
},
|
|
.label_token = label_token,
|
|
};
|
|
}
|
|
|
|
pub fn switchCaseOne(tree: Ast, node: Node.Index) full.SwitchCase {
|
|
const first_value, const target_expr = tree.nodeData(node).opt_node_and_node;
|
|
return tree.fullSwitchCaseComponents(.{
|
|
.values = if (first_value == .none)
|
|
&.{}
|
|
else
|
|
// Ensure that the returned slice points into the existing memory of the Ast
|
|
(@as(*const Node.Index, @ptrCast(&tree.nodes.items(.data)[@intFromEnum(node)].opt_node_and_node[0])))[0..1],
|
|
.arrow_token = tree.nodeMainToken(node),
|
|
.target_expr = target_expr,
|
|
}, node);
|
|
}
|
|
|
|
pub fn switchCase(tree: Ast, node: Node.Index) full.SwitchCase {
|
|
const extra_index, const target_expr = tree.nodeData(node).extra_and_node;
|
|
const values = tree.extraDataSlice(tree.extraData(extra_index, Node.SubRange), Node.Index);
|
|
return tree.fullSwitchCaseComponents(.{
|
|
.values = values,
|
|
.arrow_token = tree.nodeMainToken(node),
|
|
.target_expr = target_expr,
|
|
}, node);
|
|
}
|
|
|
|
pub fn asmSimple(tree: Ast, node: Node.Index) full.Asm {
|
|
const template, const rparen = tree.nodeData(node).node_and_token;
|
|
return tree.fullAsmComponents(.{
|
|
.asm_token = tree.nodeMainToken(node),
|
|
.template = template,
|
|
.items = &.{},
|
|
.rparen = rparen,
|
|
.clobbers = .none,
|
|
});
|
|
}
|
|
|
|
pub fn asmLegacy(tree: Ast, node: Node.Index) full.AsmLegacy {
|
|
const template, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.AsmLegacy);
|
|
const items = tree.extraDataSlice(.{ .start = extra.items_start, .end = extra.items_end }, Node.Index);
|
|
return tree.legacyAsmComponents(.{
|
|
.asm_token = tree.nodeMainToken(node),
|
|
.template = template,
|
|
.items = items,
|
|
.rparen = extra.rparen,
|
|
});
|
|
}
|
|
|
|
pub fn asmFull(tree: Ast, node: Node.Index) full.Asm {
|
|
const template, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.Asm);
|
|
const items = tree.extraDataSlice(.{ .start = extra.items_start, .end = extra.items_end }, Node.Index);
|
|
return tree.fullAsmComponents(.{
|
|
.asm_token = tree.nodeMainToken(node),
|
|
.template = template,
|
|
.items = items,
|
|
.clobbers = extra.clobbers,
|
|
.rparen = extra.rparen,
|
|
});
|
|
}
|
|
|
|
pub fn whileSimple(tree: Ast, node: Node.Index) full.While {
|
|
const cond_expr, const then_expr = tree.nodeData(node).node_and_node;
|
|
return tree.fullWhileComponents(.{
|
|
.while_token = tree.nodeMainToken(node),
|
|
.cond_expr = cond_expr,
|
|
.cont_expr = .none,
|
|
.then_expr = then_expr,
|
|
.else_expr = .none,
|
|
});
|
|
}
|
|
|
|
pub fn whileCont(tree: Ast, node: Node.Index) full.While {
|
|
const cond_expr, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.WhileCont);
|
|
return tree.fullWhileComponents(.{
|
|
.while_token = tree.nodeMainToken(node),
|
|
.cond_expr = cond_expr,
|
|
.cont_expr = extra.cont_expr.toOptional(),
|
|
.then_expr = extra.then_expr,
|
|
.else_expr = .none,
|
|
});
|
|
}
|
|
|
|
pub fn whileFull(tree: Ast, node: Node.Index) full.While {
|
|
const cond_expr, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const extra = tree.extraData(extra_index, Node.While);
|
|
return tree.fullWhileComponents(.{
|
|
.while_token = tree.nodeMainToken(node),
|
|
.cond_expr = cond_expr,
|
|
.cont_expr = extra.cont_expr,
|
|
.then_expr = extra.then_expr,
|
|
.else_expr = extra.else_expr.toOptional(),
|
|
});
|
|
}
|
|
|
|
pub fn forSimple(tree: Ast, node: Node.Index) full.For {
|
|
const data = &tree.nodes.items(.data)[@intFromEnum(node)].node_and_node;
|
|
return tree.fullForComponents(.{
|
|
.for_token = tree.nodeMainToken(node),
|
|
.inputs = (&data[0])[0..1],
|
|
.then_expr = data[1],
|
|
.else_expr = .none,
|
|
});
|
|
}
|
|
|
|
pub fn forFull(tree: Ast, node: Node.Index) full.For {
|
|
const extra_index, const extra = tree.nodeData(node).@"for";
|
|
const inputs = tree.extraDataSliceWithLen(extra_index, extra.inputs, Node.Index);
|
|
const then_expr: Node.Index = @enumFromInt(tree.extra_data[@intFromEnum(extra_index) + extra.inputs]);
|
|
const else_expr: Node.OptionalIndex = if (extra.has_else) @enumFromInt(tree.extra_data[@intFromEnum(extra_index) + extra.inputs + 1]) else .none;
|
|
return tree.fullForComponents(.{
|
|
.for_token = tree.nodeMainToken(node),
|
|
.inputs = inputs,
|
|
.then_expr = then_expr,
|
|
.else_expr = else_expr,
|
|
});
|
|
}
|
|
|
|
pub fn callOne(tree: Ast, buffer: *[1]Node.Index, node: Node.Index) full.Call {
|
|
const fn_expr, const first_param = tree.nodeData(node).node_and_opt_node;
|
|
const params = loadOptionalNodesIntoBuffer(1, buffer, .{first_param});
|
|
return .{ .ast = .{
|
|
.lparen = tree.nodeMainToken(node),
|
|
.fn_expr = fn_expr,
|
|
.params = params,
|
|
} };
|
|
}
|
|
|
|
pub fn callFull(tree: Ast, node: Node.Index) full.Call {
|
|
const fn_expr, const extra_index = tree.nodeData(node).node_and_extra;
|
|
const params = tree.extraDataSlice(tree.extraData(extra_index, Node.SubRange), Node.Index);
|
|
return .{ .ast = .{
|
|
.lparen = tree.nodeMainToken(node),
|
|
.fn_expr = fn_expr,
|
|
.params = params,
|
|
} };
|
|
}
|
|
|
|
fn fullVarDeclComponents(tree: Ast, info: full.VarDecl.Components) full.VarDecl {
|
|
var result: full.VarDecl = .{
|
|
.ast = info,
|
|
.visib_token = null,
|
|
.extern_export_token = null,
|
|
.lib_name = null,
|
|
.threadlocal_token = null,
|
|
.comptime_token = null,
|
|
};
|
|
var i = info.mut_token;
|
|
while (i > 0) {
|
|
i -= 1;
|
|
switch (tree.tokenTag(i)) {
|
|
.keyword_extern, .keyword_export => result.extern_export_token = i,
|
|
.keyword_comptime => result.comptime_token = i,
|
|
.keyword_pub => result.visib_token = i,
|
|
.keyword_threadlocal => result.threadlocal_token = i,
|
|
.string_literal => result.lib_name = i,
|
|
else => break,
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn fullAssignDestructureComponents(tree: Ast, info: full.AssignDestructure.Components) full.AssignDestructure {
|
|
var result: full.AssignDestructure = .{
|
|
.comptime_token = null,
|
|
.ast = info,
|
|
};
|
|
const first_variable_token = tree.firstToken(info.variables[0]);
|
|
const maybe_comptime_token = switch (tree.nodeTag(info.variables[0])) {
|
|
.global_var_decl,
|
|
.local_var_decl,
|
|
.aligned_var_decl,
|
|
.simple_var_decl,
|
|
=> first_variable_token,
|
|
else => first_variable_token - 1,
|
|
};
|
|
if (tree.tokenTag(maybe_comptime_token) == .keyword_comptime) {
|
|
result.comptime_token = maybe_comptime_token;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn fullIfComponents(tree: Ast, info: full.If.Components) full.If {
|
|
var result: full.If = .{
|
|
.ast = info,
|
|
.payload_token = null,
|
|
.error_token = null,
|
|
.else_token = undefined,
|
|
};
|
|
// if (cond_expr) |x|
|
|
// ^ ^
|
|
const payload_pipe = tree.lastToken(info.cond_expr) + 2;
|
|
if (tree.tokenTag(payload_pipe) == .pipe) {
|
|
result.payload_token = payload_pipe + 1;
|
|
}
|
|
if (info.else_expr != .none) {
|
|
// then_expr else |x|
|
|
// ^ ^
|
|
result.else_token = tree.lastToken(info.then_expr) + 1;
|
|
if (tree.tokenTag(result.else_token + 1) == .pipe) {
|
|
result.error_token = result.else_token + 2;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn fullContainerFieldComponents(tree: Ast, info: full.ContainerField.Components) full.ContainerField {
|
|
var result: full.ContainerField = .{
|
|
.ast = info,
|
|
.comptime_token = null,
|
|
};
|
|
if (tree.isTokenPrecededByTags(info.main_token, &.{.keyword_comptime})) {
|
|
// comptime type = init,
|
|
// ^ ^
|
|
// comptime name: type = init,
|
|
// ^ ^
|
|
result.comptime_token = info.main_token - 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn fullFnProtoComponents(tree: Ast, info: full.FnProto.Components) full.FnProto {
|
|
var result: full.FnProto = .{
|
|
.ast = info,
|
|
.visib_token = null,
|
|
.extern_export_inline_token = null,
|
|
.lib_name = null,
|
|
.name_token = null,
|
|
.lparen = undefined,
|
|
};
|
|
var i = info.fn_token;
|
|
while (i > 0) {
|
|
i -= 1;
|
|
switch (tree.tokenTag(i)) {
|
|
.keyword_extern,
|
|
.keyword_export,
|
|
.keyword_inline,
|
|
.keyword_noinline,
|
|
=> result.extern_export_inline_token = i,
|
|
.keyword_pub => result.visib_token = i,
|
|
.string_literal => result.lib_name = i,
|
|
else => break,
|
|
}
|
|
}
|
|
const after_fn_token = info.fn_token + 1;
|
|
if (tree.tokenTag(after_fn_token) == .identifier) {
|
|
result.name_token = after_fn_token;
|
|
result.lparen = after_fn_token + 1;
|
|
} else {
|
|
result.lparen = after_fn_token;
|
|
}
|
|
assert(tree.tokenTag(result.lparen) == .l_paren);
|
|
|
|
return result;
|
|
}
|
|
|
|
fn fullPtrTypeComponents(tree: Ast, info: full.PtrType.Components) full.PtrType {
|
|
const size: std.builtin.Type.Pointer.Size = switch (tree.tokenTag(info.main_token)) {
|
|
.asterisk,
|
|
.asterisk_asterisk,
|
|
=> .one,
|
|
.l_bracket => switch (tree.tokenTag(info.main_token + 1)) {
|
|
.asterisk => if (tree.tokenTag(info.main_token + 2) == .identifier) .c else .many,
|
|
else => .slice,
|
|
},
|
|
else => unreachable,
|
|
};
|
|
var result: full.PtrType = .{
|
|
.size = size,
|
|
.allowzero_token = null,
|
|
.const_token = null,
|
|
.volatile_token = null,
|
|
.ast = info,
|
|
};
|
|
// We need to be careful that we don't iterate over any sub-expressions
|
|
// here while looking for modifiers as that could result in false
|
|
// positives. Therefore, start after a sentinel if there is one and
|
|
// skip over any align node and bit range nodes.
|
|
var i = if (info.sentinel.unwrap()) |sentinel| tree.lastToken(sentinel) + 1 else switch (size) {
|
|
.many, .c => info.main_token + 1,
|
|
else => info.main_token,
|
|
};
|
|
const end = tree.firstToken(info.child_type);
|
|
while (i < end) : (i += 1) {
|
|
switch (tree.tokenTag(i)) {
|
|
.keyword_allowzero => result.allowzero_token = i,
|
|
.keyword_const => result.const_token = i,
|
|
.keyword_volatile => result.volatile_token = i,
|
|
.keyword_align => {
|
|
const align_node = info.align_node.unwrap().?;
|
|
if (info.bit_range_end.unwrap()) |bit_range_end| {
|
|
assert(info.bit_range_start != .none);
|
|
i = tree.lastToken(bit_range_end) + 1;
|
|
} else {
|
|
i = tree.lastToken(align_node) + 1;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn fullContainerDeclComponents(tree: Ast, info: full.ContainerDecl.Components) full.ContainerDecl {
|
|
var result: full.ContainerDecl = .{
|
|
.ast = info,
|
|
.layout_token = null,
|
|
};
|
|
|
|
if (info.main_token == 0) return result; // .root
|
|
const previous_token = info.main_token - 1;
|
|
|
|
switch (tree.tokenTag(previous_token)) {
|
|
.keyword_extern, .keyword_packed => result.layout_token = previous_token,
|
|
else => {},
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn fullSwitchComponents(tree: Ast, info: full.Switch.Components) full.Switch {
|
|
const tok_i = info.switch_token -| 1;
|
|
var result: full.Switch = .{
|
|
.ast = info,
|
|
.label_token = null,
|
|
};
|
|
if (tree.tokenTag(tok_i) == .colon and
|
|
tree.tokenTag(tok_i -| 1) == .identifier)
|
|
{
|
|
result.label_token = tok_i - 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn fullSwitchCaseComponents(tree: Ast, info: full.SwitchCase.Components, node: Node.Index) full.SwitchCase {
|
|
var result: full.SwitchCase = .{
|
|
.ast = info,
|
|
.payload_token = null,
|
|
.inline_token = null,
|
|
};
|
|
if (tree.tokenTag(info.arrow_token + 1) == .pipe) {
|
|
result.payload_token = info.arrow_token + 2;
|
|
}
|
|
result.inline_token = switch (tree.nodeTag(node)) {
|
|
.switch_case_inline, .switch_case_inline_one => if (result.ast.values.len == 0)
|
|
info.arrow_token - 2
|
|
else
|
|
tree.firstToken(result.ast.values[0]) - 1,
|
|
else => null,
|
|
};
|
|
return result;
|
|
}
|
|
|
|
fn legacyAsmComponents(tree: Ast, info: full.AsmLegacy.Components) full.AsmLegacy {
|
|
var result: full.AsmLegacy = .{
|
|
.ast = info,
|
|
.volatile_token = null,
|
|
.inputs = &.{},
|
|
.outputs = &.{},
|
|
.first_clobber = null,
|
|
};
|
|
if (tree.tokenTag(info.asm_token + 1) == .keyword_volatile) {
|
|
result.volatile_token = info.asm_token + 1;
|
|
}
|
|
const outputs_end: usize = for (info.items, 0..) |item, i| {
|
|
switch (tree.nodeTag(item)) {
|
|
.asm_output => continue,
|
|
else => break i,
|
|
}
|
|
} else info.items.len;
|
|
|
|
result.outputs = info.items[0..outputs_end];
|
|
result.inputs = info.items[outputs_end..];
|
|
|
|
if (info.items.len == 0) {
|
|
// asm ("foo" ::: "a", "b");
|
|
const template_token = tree.lastToken(info.template);
|
|
if (tree.tokenTag(template_token + 1) == .colon and
|
|
tree.tokenTag(template_token + 2) == .colon and
|
|
tree.tokenTag(template_token + 3) == .colon and
|
|
tree.tokenTag(template_token + 4) == .string_literal)
|
|
{
|
|
result.first_clobber = template_token + 4;
|
|
}
|
|
} else if (result.inputs.len != 0) {
|
|
// asm ("foo" :: [_] "" (y) : "a", "b");
|
|
const last_input = result.inputs[result.inputs.len - 1];
|
|
const rparen = tree.lastToken(last_input);
|
|
var i = rparen + 1;
|
|
// Allow a (useless) comma right after the closing parenthesis.
|
|
if (tree.tokenTag(i) == .comma) i = i + 1;
|
|
if (tree.tokenTag(i) == .colon and
|
|
tree.tokenTag(i + 1) == .string_literal)
|
|
{
|
|
result.first_clobber = i + 1;
|
|
}
|
|
} else {
|
|
// asm ("foo" : [_] "" (x) :: "a", "b");
|
|
const last_output = result.outputs[result.outputs.len - 1];
|
|
const rparen = tree.lastToken(last_output);
|
|
var i = rparen + 1;
|
|
// Allow a (useless) comma right after the closing parenthesis.
|
|
if (tree.tokenTag(i) == .comma) i = i + 1;
|
|
if (tree.tokenTag(i) == .colon and
|
|
tree.tokenTag(i + 1) == .colon and
|
|
tree.tokenTag(i + 2) == .string_literal)
|
|
{
|
|
result.first_clobber = i + 2;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
fn fullAsmComponents(tree: Ast, info: full.Asm.Components) full.Asm {
|
|
var result: full.Asm = .{
|
|
.ast = info,
|
|
.volatile_token = null,
|
|
.inputs = &.{},
|
|
.outputs = &.{},
|
|
};
|
|
if (tree.tokenTag(info.asm_token + 1) == .keyword_volatile) {
|
|
result.volatile_token = info.asm_token + 1;
|
|
}
|
|
const outputs_end: usize = for (info.items, 0..) |item, i| {
|
|
switch (tree.nodeTag(item)) {
|
|
.asm_output => continue,
|
|
else => break i,
|
|
}
|
|
} else info.items.len;
|
|
|
|
result.outputs = info.items[0..outputs_end];
|
|
result.inputs = info.items[outputs_end..];
|
|
|
|
return result;
|
|
}
|
|
|
|
fn fullWhileComponents(tree: Ast, info: full.While.Components) full.While {
|
|
var result: full.While = .{
|
|
.ast = info,
|
|
.inline_token = null,
|
|
.label_token = null,
|
|
.payload_token = null,
|
|
.else_token = undefined,
|
|
.error_token = null,
|
|
};
|
|
var tok_i = info.while_token;
|
|
if (tree.isTokenPrecededByTags(tok_i, &.{.keyword_inline})) {
|
|
result.inline_token = tok_i - 1;
|
|
tok_i = tok_i - 1;
|
|
}
|
|
if (tree.isTokenPrecededByTags(tok_i, &.{ .identifier, .colon })) {
|
|
result.label_token = tok_i - 2;
|
|
}
|
|
const last_cond_token = tree.lastToken(info.cond_expr);
|
|
if (tree.tokenTag(last_cond_token + 2) == .pipe) {
|
|
result.payload_token = last_cond_token + 3;
|
|
}
|
|
if (info.else_expr != .none) {
|
|
// then_expr else |x|
|
|
// ^ ^
|
|
result.else_token = tree.lastToken(info.then_expr) + 1;
|
|
if (tree.tokenTag(result.else_token + 1) == .pipe) {
|
|
result.error_token = result.else_token + 2;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn fullForComponents(tree: Ast, info: full.For.Components) full.For {
|
|
var result: full.For = .{
|
|
.ast = info,
|
|
.inline_token = null,
|
|
.label_token = null,
|
|
.payload_token = undefined,
|
|
.else_token = undefined,
|
|
};
|
|
var tok_i = info.for_token;
|
|
if (tree.isTokenPrecededByTags(tok_i, &.{.keyword_inline})) {
|
|
result.inline_token = tok_i - 1;
|
|
tok_i = tok_i - 1;
|
|
}
|
|
if (tree.isTokenPrecededByTags(tok_i, &.{ .identifier, .colon })) {
|
|
result.label_token = tok_i - 2;
|
|
}
|
|
const last_cond_token = tree.lastToken(info.inputs[info.inputs.len - 1]);
|
|
result.payload_token = last_cond_token + @as(u32, 3) + @intFromBool(tree.tokenTag(last_cond_token + 1) == .comma);
|
|
if (info.else_expr != .none) {
|
|
result.else_token = tree.lastToken(info.then_expr) + 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
pub fn fullVarDecl(tree: Ast, node: Node.Index) ?full.VarDecl {
|
|
return switch (tree.nodeTag(node)) {
|
|
.global_var_decl => tree.globalVarDecl(node),
|
|
.local_var_decl => tree.localVarDecl(node),
|
|
.aligned_var_decl => tree.alignedVarDecl(node),
|
|
.simple_var_decl => tree.simpleVarDecl(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullIf(tree: Ast, node: Node.Index) ?full.If {
|
|
return switch (tree.nodeTag(node)) {
|
|
.if_simple => tree.ifSimple(node),
|
|
.@"if" => tree.ifFull(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullWhile(tree: Ast, node: Node.Index) ?full.While {
|
|
return switch (tree.nodeTag(node)) {
|
|
.while_simple => tree.whileSimple(node),
|
|
.while_cont => tree.whileCont(node),
|
|
.@"while" => tree.whileFull(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullFor(tree: Ast, node: Node.Index) ?full.For {
|
|
return switch (tree.nodeTag(node)) {
|
|
.for_simple => tree.forSimple(node),
|
|
.@"for" => tree.forFull(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullContainerField(tree: Ast, node: Node.Index) ?full.ContainerField {
|
|
return switch (tree.nodeTag(node)) {
|
|
.container_field_init => tree.containerFieldInit(node),
|
|
.container_field_align => tree.containerFieldAlign(node),
|
|
.container_field => tree.containerField(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullFnProto(tree: Ast, buffer: *[1]Ast.Node.Index, node: Node.Index) ?full.FnProto {
|
|
return switch (tree.nodeTag(node)) {
|
|
.fn_proto => tree.fnProto(node),
|
|
.fn_proto_multi => tree.fnProtoMulti(node),
|
|
.fn_proto_one => tree.fnProtoOne(buffer, node),
|
|
.fn_proto_simple => tree.fnProtoSimple(buffer, node),
|
|
.fn_decl => tree.fullFnProto(buffer, tree.nodeData(node).node_and_node[0]),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullStructInit(tree: Ast, buffer: *[2]Ast.Node.Index, node: Node.Index) ?full.StructInit {
|
|
return switch (tree.nodeTag(node)) {
|
|
.struct_init_one, .struct_init_one_comma => tree.structInitOne(buffer[0..1], node),
|
|
.struct_init_dot_two, .struct_init_dot_two_comma => tree.structInitDotTwo(buffer, node),
|
|
.struct_init_dot, .struct_init_dot_comma => tree.structInitDot(node),
|
|
.struct_init, .struct_init_comma => tree.structInit(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullArrayInit(tree: Ast, buffer: *[2]Node.Index, node: Node.Index) ?full.ArrayInit {
|
|
return switch (tree.nodeTag(node)) {
|
|
.array_init_one, .array_init_one_comma => tree.arrayInitOne(buffer[0..1], node),
|
|
.array_init_dot_two, .array_init_dot_two_comma => tree.arrayInitDotTwo(buffer, node),
|
|
.array_init_dot, .array_init_dot_comma => tree.arrayInitDot(node),
|
|
.array_init, .array_init_comma => tree.arrayInit(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullArrayType(tree: Ast, node: Node.Index) ?full.ArrayType {
|
|
return switch (tree.nodeTag(node)) {
|
|
.array_type => tree.arrayType(node),
|
|
.array_type_sentinel => tree.arrayTypeSentinel(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullPtrType(tree: Ast, node: Node.Index) ?full.PtrType {
|
|
return switch (tree.nodeTag(node)) {
|
|
.ptr_type_aligned => tree.ptrTypeAligned(node),
|
|
.ptr_type_sentinel => tree.ptrTypeSentinel(node),
|
|
.ptr_type => tree.ptrType(node),
|
|
.ptr_type_bit_range => tree.ptrTypeBitRange(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullSlice(tree: Ast, node: Node.Index) ?full.Slice {
|
|
return switch (tree.nodeTag(node)) {
|
|
.slice_open => tree.sliceOpen(node),
|
|
.slice => tree.slice(node),
|
|
.slice_sentinel => tree.sliceSentinel(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullContainerDecl(tree: Ast, buffer: *[2]Ast.Node.Index, node: Node.Index) ?full.ContainerDecl {
|
|
return switch (tree.nodeTag(node)) {
|
|
.root => tree.containerDeclRoot(),
|
|
.container_decl, .container_decl_trailing => tree.containerDecl(node),
|
|
.container_decl_arg, .container_decl_arg_trailing => tree.containerDeclArg(node),
|
|
.container_decl_two, .container_decl_two_trailing => tree.containerDeclTwo(buffer, node),
|
|
.tagged_union, .tagged_union_trailing => tree.taggedUnion(node),
|
|
.tagged_union_enum_tag, .tagged_union_enum_tag_trailing => tree.taggedUnionEnumTag(node),
|
|
.tagged_union_two, .tagged_union_two_trailing => tree.taggedUnionTwo(buffer, node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullSwitch(tree: Ast, node: Node.Index) ?full.Switch {
|
|
return switch (tree.nodeTag(node)) {
|
|
.@"switch", .switch_comma => tree.switchFull(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullSwitchCase(tree: Ast, node: Node.Index) ?full.SwitchCase {
|
|
return switch (tree.nodeTag(node)) {
|
|
.switch_case_one, .switch_case_inline_one => tree.switchCaseOne(node),
|
|
.switch_case, .switch_case_inline => tree.switchCase(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullAsm(tree: Ast, node: Node.Index) ?full.Asm {
|
|
return switch (tree.nodeTag(node)) {
|
|
.asm_simple => tree.asmSimple(node),
|
|
.@"asm" => tree.asmFull(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
/// To be deleted after 0.15.0 is tagged
|
|
pub fn legacyAsm(tree: Ast, node: Node.Index) ?full.AsmLegacy {
|
|
return switch (tree.nodeTag(node)) {
|
|
.asm_legacy => tree.asmLegacy(node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn fullCall(tree: Ast, buffer: *[1]Ast.Node.Index, node: Node.Index) ?full.Call {
|
|
return switch (tree.nodeTag(node)) {
|
|
.call, .call_comma => tree.callFull(node),
|
|
.call_one, .call_one_comma => tree.callOne(buffer, node),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn builtinCallParams(tree: Ast, buffer: *[2]Ast.Node.Index, node: Ast.Node.Index) ?[]const Node.Index {
|
|
return switch (tree.nodeTag(node)) {
|
|
.builtin_call_two, .builtin_call_two_comma => loadOptionalNodesIntoBuffer(2, buffer, tree.nodeData(node).opt_node_and_opt_node),
|
|
.builtin_call, .builtin_call_comma => tree.extraDataSlice(tree.nodeData(node).extra_range, Node.Index),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
pub fn blockStatements(tree: Ast, buffer: *[2]Ast.Node.Index, node: Ast.Node.Index) ?[]const Node.Index {
|
|
return switch (tree.nodeTag(node)) {
|
|
.block_two, .block_two_semicolon => loadOptionalNodesIntoBuffer(2, buffer, tree.nodeData(node).opt_node_and_opt_node),
|
|
.block, .block_semicolon => tree.extraDataSlice(tree.nodeData(node).extra_range, Node.Index),
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
/// Fully assembled AST node information.
|
|
pub const full = struct {
|
|
pub const VarDecl = struct {
|
|
visib_token: ?TokenIndex,
|
|
extern_export_token: ?TokenIndex,
|
|
lib_name: ?TokenIndex,
|
|
threadlocal_token: ?TokenIndex,
|
|
comptime_token: ?TokenIndex,
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
mut_token: TokenIndex,
|
|
type_node: Node.OptionalIndex,
|
|
align_node: Node.OptionalIndex,
|
|
addrspace_node: Node.OptionalIndex,
|
|
section_node: Node.OptionalIndex,
|
|
init_node: Node.OptionalIndex,
|
|
};
|
|
|
|
pub fn firstToken(var_decl: VarDecl) TokenIndex {
|
|
return var_decl.visib_token orelse
|
|
var_decl.extern_export_token orelse
|
|
var_decl.threadlocal_token orelse
|
|
var_decl.comptime_token orelse
|
|
var_decl.ast.mut_token;
|
|
}
|
|
};
|
|
|
|
pub const AssignDestructure = struct {
|
|
comptime_token: ?TokenIndex,
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
variables: []const Node.Index,
|
|
equal_token: TokenIndex,
|
|
value_expr: Node.Index,
|
|
};
|
|
};
|
|
|
|
pub const If = struct {
|
|
/// Points to the first token after the `|`. Will either be an identifier or
|
|
/// a `*` (with an identifier immediately after it).
|
|
payload_token: ?TokenIndex,
|
|
/// Points to the identifier after the `|`.
|
|
error_token: ?TokenIndex,
|
|
/// Populated only if else_expr != .none.
|
|
else_token: TokenIndex,
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
if_token: TokenIndex,
|
|
cond_expr: Node.Index,
|
|
then_expr: Node.Index,
|
|
else_expr: Node.OptionalIndex,
|
|
};
|
|
};
|
|
|
|
pub const While = struct {
|
|
ast: Components,
|
|
inline_token: ?TokenIndex,
|
|
label_token: ?TokenIndex,
|
|
payload_token: ?TokenIndex,
|
|
error_token: ?TokenIndex,
|
|
/// Populated only if else_expr != none.
|
|
else_token: TokenIndex,
|
|
|
|
pub const Components = struct {
|
|
while_token: TokenIndex,
|
|
cond_expr: Node.Index,
|
|
cont_expr: Node.OptionalIndex,
|
|
then_expr: Node.Index,
|
|
else_expr: Node.OptionalIndex,
|
|
};
|
|
};
|
|
|
|
pub const For = struct {
|
|
ast: Components,
|
|
inline_token: ?TokenIndex,
|
|
label_token: ?TokenIndex,
|
|
payload_token: TokenIndex,
|
|
/// Populated only if else_expr != .none.
|
|
else_token: ?TokenIndex,
|
|
|
|
pub const Components = struct {
|
|
for_token: TokenIndex,
|
|
inputs: []const Node.Index,
|
|
then_expr: Node.Index,
|
|
else_expr: Node.OptionalIndex,
|
|
};
|
|
};
|
|
|
|
pub const ContainerField = struct {
|
|
comptime_token: ?TokenIndex,
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
main_token: TokenIndex,
|
|
/// Can only be `.none` after calling `convertToNonTupleLike`.
|
|
type_expr: Node.OptionalIndex,
|
|
align_expr: Node.OptionalIndex,
|
|
value_expr: Node.OptionalIndex,
|
|
tuple_like: bool,
|
|
};
|
|
|
|
pub fn firstToken(cf: ContainerField) TokenIndex {
|
|
return cf.comptime_token orelse cf.ast.main_token;
|
|
}
|
|
|
|
pub fn convertToNonTupleLike(cf: *ContainerField, tree: *const Ast) void {
|
|
if (!cf.ast.tuple_like) return;
|
|
if (tree.nodeTag(cf.ast.type_expr.unwrap().?) != .identifier) return;
|
|
|
|
cf.ast.type_expr = .none;
|
|
cf.ast.tuple_like = false;
|
|
}
|
|
};
|
|
|
|
pub const FnProto = struct {
|
|
visib_token: ?TokenIndex,
|
|
extern_export_inline_token: ?TokenIndex,
|
|
lib_name: ?TokenIndex,
|
|
name_token: ?TokenIndex,
|
|
lparen: TokenIndex,
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
proto_node: Node.Index,
|
|
fn_token: TokenIndex,
|
|
return_type: Node.OptionalIndex,
|
|
params: []const Node.Index,
|
|
align_expr: Node.OptionalIndex,
|
|
addrspace_expr: Node.OptionalIndex,
|
|
section_expr: Node.OptionalIndex,
|
|
callconv_expr: Node.OptionalIndex,
|
|
};
|
|
|
|
pub const Param = struct {
|
|
first_doc_comment: ?TokenIndex,
|
|
name_token: ?TokenIndex,
|
|
comptime_noalias: ?TokenIndex,
|
|
anytype_ellipsis3: ?TokenIndex,
|
|
type_expr: ?Node.Index,
|
|
};
|
|
|
|
pub fn firstToken(fn_proto: FnProto) TokenIndex {
|
|
return fn_proto.visib_token orelse
|
|
fn_proto.extern_export_inline_token orelse
|
|
fn_proto.ast.fn_token;
|
|
}
|
|
|
|
/// Abstracts over the fact that anytype and ... are not included
|
|
/// in the params slice, since they are simple identifiers and
|
|
/// not sub-expressions.
|
|
pub const Iterator = struct {
|
|
tree: *const Ast,
|
|
fn_proto: *const FnProto,
|
|
param_i: usize,
|
|
tok_i: TokenIndex,
|
|
tok_flag: bool,
|
|
|
|
pub fn next(it: *Iterator) ?Param {
|
|
const tree = it.tree;
|
|
while (true) {
|
|
var first_doc_comment: ?TokenIndex = null;
|
|
var comptime_noalias: ?TokenIndex = null;
|
|
var name_token: ?TokenIndex = null;
|
|
if (!it.tok_flag) {
|
|
if (it.param_i >= it.fn_proto.ast.params.len) {
|
|
return null;
|
|
}
|
|
const param_type = it.fn_proto.ast.params[it.param_i];
|
|
var tok_i = tree.firstToken(param_type) - 1;
|
|
while (true) : (tok_i -= 1) switch (tree.tokenTag(tok_i)) {
|
|
.colon => continue,
|
|
.identifier => name_token = tok_i,
|
|
.doc_comment => first_doc_comment = tok_i,
|
|
.keyword_comptime, .keyword_noalias => comptime_noalias = tok_i,
|
|
else => break,
|
|
};
|
|
it.param_i += 1;
|
|
it.tok_i = tree.lastToken(param_type) + 1;
|
|
// Look for anytype and ... params afterwards.
|
|
if (tree.tokenTag(it.tok_i) == .comma) {
|
|
it.tok_i += 1;
|
|
}
|
|
it.tok_flag = true;
|
|
return Param{
|
|
.first_doc_comment = first_doc_comment,
|
|
.comptime_noalias = comptime_noalias,
|
|
.name_token = name_token,
|
|
.anytype_ellipsis3 = null,
|
|
.type_expr = param_type,
|
|
};
|
|
}
|
|
if (tree.tokenTag(it.tok_i) == .comma) {
|
|
it.tok_i += 1;
|
|
}
|
|
if (tree.tokenTag(it.tok_i) == .r_paren) {
|
|
return null;
|
|
}
|
|
if (tree.tokenTag(it.tok_i) == .doc_comment) {
|
|
first_doc_comment = it.tok_i;
|
|
while (tree.tokenTag(it.tok_i) == .doc_comment) {
|
|
it.tok_i += 1;
|
|
}
|
|
}
|
|
switch (tree.tokenTag(it.tok_i)) {
|
|
.ellipsis3 => {
|
|
it.tok_flag = false; // Next iteration should return null.
|
|
return Param{
|
|
.first_doc_comment = first_doc_comment,
|
|
.comptime_noalias = null,
|
|
.name_token = null,
|
|
.anytype_ellipsis3 = it.tok_i,
|
|
.type_expr = null,
|
|
};
|
|
},
|
|
.keyword_noalias, .keyword_comptime => {
|
|
comptime_noalias = it.tok_i;
|
|
it.tok_i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
if (tree.tokenTag(it.tok_i) == .identifier and
|
|
tree.tokenTag(it.tok_i + 1) == .colon)
|
|
{
|
|
name_token = it.tok_i;
|
|
it.tok_i += 2;
|
|
}
|
|
if (tree.tokenTag(it.tok_i) == .keyword_anytype) {
|
|
it.tok_i += 1;
|
|
return Param{
|
|
.first_doc_comment = first_doc_comment,
|
|
.comptime_noalias = comptime_noalias,
|
|
.name_token = name_token,
|
|
.anytype_ellipsis3 = it.tok_i - 1,
|
|
.type_expr = null,
|
|
};
|
|
}
|
|
it.tok_flag = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn iterate(fn_proto: *const FnProto, tree: *const Ast) Iterator {
|
|
return .{
|
|
.tree = tree,
|
|
.fn_proto = fn_proto,
|
|
.param_i = 0,
|
|
.tok_i = fn_proto.lparen + 1,
|
|
.tok_flag = true,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const StructInit = struct {
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
lbrace: TokenIndex,
|
|
fields: []const Node.Index,
|
|
type_expr: Node.OptionalIndex,
|
|
};
|
|
};
|
|
|
|
pub const ArrayInit = struct {
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
lbrace: TokenIndex,
|
|
elements: []const Node.Index,
|
|
type_expr: Node.OptionalIndex,
|
|
};
|
|
};
|
|
|
|
pub const ArrayType = struct {
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
lbracket: TokenIndex,
|
|
elem_count: Node.Index,
|
|
sentinel: Node.OptionalIndex,
|
|
elem_type: Node.Index,
|
|
};
|
|
};
|
|
|
|
pub const PtrType = struct {
|
|
size: std.builtin.Type.Pointer.Size,
|
|
allowzero_token: ?TokenIndex,
|
|
const_token: ?TokenIndex,
|
|
volatile_token: ?TokenIndex,
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
main_token: TokenIndex,
|
|
align_node: Node.OptionalIndex,
|
|
addrspace_node: Node.OptionalIndex,
|
|
sentinel: Node.OptionalIndex,
|
|
bit_range_start: Node.OptionalIndex,
|
|
bit_range_end: Node.OptionalIndex,
|
|
child_type: Node.Index,
|
|
};
|
|
};
|
|
|
|
pub const Slice = struct {
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
sliced: Node.Index,
|
|
lbracket: TokenIndex,
|
|
start: Node.Index,
|
|
end: Node.OptionalIndex,
|
|
sentinel: Node.OptionalIndex,
|
|
};
|
|
};
|
|
|
|
pub const ContainerDecl = struct {
|
|
layout_token: ?TokenIndex,
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
main_token: TokenIndex,
|
|
/// Populated when main_token is Keyword_union.
|
|
enum_token: ?TokenIndex,
|
|
members: []const Node.Index,
|
|
arg: Node.OptionalIndex,
|
|
};
|
|
};
|
|
|
|
pub const Switch = struct {
|
|
ast: Components,
|
|
label_token: ?TokenIndex,
|
|
|
|
pub const Components = struct {
|
|
switch_token: TokenIndex,
|
|
condition: Node.Index,
|
|
cases: []const Node.Index,
|
|
};
|
|
};
|
|
|
|
pub const SwitchCase = struct {
|
|
inline_token: ?TokenIndex,
|
|
/// Points to the first token after the `|`. Will either be an identifier or
|
|
/// a `*` (with an identifier immediately after it).
|
|
payload_token: ?TokenIndex,
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
/// If empty, this is an else case
|
|
values: []const Node.Index,
|
|
arrow_token: TokenIndex,
|
|
target_expr: Node.Index,
|
|
};
|
|
};
|
|
|
|
pub const Asm = struct {
|
|
ast: Components,
|
|
volatile_token: ?TokenIndex,
|
|
outputs: []const Node.Index,
|
|
inputs: []const Node.Index,
|
|
|
|
pub const Components = struct {
|
|
asm_token: TokenIndex,
|
|
template: Node.Index,
|
|
items: []const Node.Index,
|
|
clobbers: Node.OptionalIndex,
|
|
rparen: TokenIndex,
|
|
};
|
|
};
|
|
|
|
pub const AsmLegacy = struct {
|
|
ast: Components,
|
|
volatile_token: ?TokenIndex,
|
|
first_clobber: ?TokenIndex,
|
|
outputs: []const Node.Index,
|
|
inputs: []const Node.Index,
|
|
|
|
pub const Components = struct {
|
|
asm_token: TokenIndex,
|
|
template: Node.Index,
|
|
items: []const Node.Index,
|
|
rparen: TokenIndex,
|
|
};
|
|
};
|
|
|
|
pub const Call = struct {
|
|
ast: Components,
|
|
|
|
pub const Components = struct {
|
|
lparen: TokenIndex,
|
|
fn_expr: Node.Index,
|
|
params: []const Node.Index,
|
|
};
|
|
};
|
|
};
|
|
|
|
pub const Error = struct {
|
|
tag: Tag,
|
|
is_note: bool = false,
|
|
/// True if `token` points to the token before the token causing an issue.
|
|
token_is_prev: bool = false,
|
|
token: TokenIndex,
|
|
extra: union {
|
|
none: void,
|
|
expected_tag: Token.Tag,
|
|
offset: usize,
|
|
} = .{ .none = {} },
|
|
|
|
pub const Tag = enum {
|
|
asterisk_after_ptr_deref,
|
|
chained_comparison_operators,
|
|
decl_between_fields,
|
|
expected_block,
|
|
expected_block_or_assignment,
|
|
expected_block_or_expr,
|
|
expected_block_or_field,
|
|
expected_container_members,
|
|
expected_expr,
|
|
expected_expr_or_assignment,
|
|
expected_expr_or_var_decl,
|
|
expected_fn,
|
|
expected_inlinable,
|
|
expected_labelable,
|
|
expected_param_list,
|
|
expected_prefix_expr,
|
|
expected_primary_type_expr,
|
|
expected_pub_item,
|
|
expected_return_type,
|
|
expected_semi_or_else,
|
|
expected_semi_or_lbrace,
|
|
expected_statement,
|
|
expected_suffix_op,
|
|
expected_type_expr,
|
|
expected_var_decl,
|
|
expected_var_decl_or_fn,
|
|
expected_loop_payload,
|
|
expected_container,
|
|
extern_fn_body,
|
|
extra_addrspace_qualifier,
|
|
extra_align_qualifier,
|
|
extra_allowzero_qualifier,
|
|
extra_const_qualifier,
|
|
extra_volatile_qualifier,
|
|
ptr_mod_on_array_child_type,
|
|
invalid_bit_range,
|
|
same_line_doc_comment,
|
|
unattached_doc_comment,
|
|
test_doc_comment,
|
|
comptime_doc_comment,
|
|
varargs_nonfinal,
|
|
expected_continue_expr,
|
|
expected_semi_after_decl,
|
|
expected_semi_after_stmt,
|
|
expected_comma_after_field,
|
|
expected_comma_after_arg,
|
|
expected_comma_after_param,
|
|
expected_comma_after_initializer,
|
|
expected_comma_after_switch_prong,
|
|
expected_comma_after_for_operand,
|
|
expected_comma_after_capture,
|
|
expected_initializer,
|
|
mismatched_binary_op_whitespace,
|
|
invalid_ampersand_ampersand,
|
|
c_style_container,
|
|
expected_var_const,
|
|
wrong_equal_var_decl,
|
|
var_const_decl,
|
|
extra_for_capture,
|
|
for_input_not_captured,
|
|
|
|
zig_style_container,
|
|
previous_field,
|
|
next_field,
|
|
|
|
/// `expected_tag` is populated.
|
|
expected_token,
|
|
|
|
/// `offset` is populated
|
|
invalid_byte,
|
|
};
|
|
};
|
|
|
|
/// Index into `extra_data`.
|
|
pub const ExtraIndex = enum(u32) {
|
|
_,
|
|
};
|
|
|
|
pub const Node = struct {
|
|
tag: Tag,
|
|
main_token: TokenIndex,
|
|
data: Data,
|
|
|
|
/// Index into `nodes`.
|
|
pub const Index = enum(u32) {
|
|
root = 0,
|
|
_,
|
|
|
|
pub fn toOptional(i: Index) OptionalIndex {
|
|
const result: OptionalIndex = @enumFromInt(@intFromEnum(i));
|
|
assert(result != .none);
|
|
return result;
|
|
}
|
|
|
|
pub fn toOffset(base: Index, destination: Index) Offset {
|
|
const base_i64: i64 = @intFromEnum(base);
|
|
const destination_i64: i64 = @intFromEnum(destination);
|
|
return @enumFromInt(destination_i64 - base_i64);
|
|
}
|
|
};
|
|
|
|
/// Index into `nodes`, or null.
|
|
pub const OptionalIndex = enum(u32) {
|
|
root = 0,
|
|
none = std.math.maxInt(u32),
|
|
_,
|
|
|
|
pub fn unwrap(oi: OptionalIndex) ?Index {
|
|
return if (oi == .none) null else @enumFromInt(@intFromEnum(oi));
|
|
}
|
|
|
|
pub fn fromOptional(oi: ?Index) OptionalIndex {
|
|
return if (oi) |i| i.toOptional() else .none;
|
|
}
|
|
};
|
|
|
|
/// A relative node index.
|
|
pub const Offset = enum(i32) {
|
|
zero = 0,
|
|
_,
|
|
|
|
pub fn toOptional(o: Offset) OptionalOffset {
|
|
const result: OptionalOffset = @enumFromInt(@intFromEnum(o));
|
|
assert(result != .none);
|
|
return result;
|
|
}
|
|
|
|
pub fn toAbsolute(offset: Offset, base: Index) Index {
|
|
return @enumFromInt(@as(i64, @intFromEnum(base)) + @intFromEnum(offset));
|
|
}
|
|
};
|
|
|
|
/// A relative node index, or null.
|
|
pub const OptionalOffset = enum(i32) {
|
|
none = std.math.maxInt(i32),
|
|
_,
|
|
|
|
pub fn unwrap(oo: OptionalOffset) ?Offset {
|
|
return if (oo == .none) null else @enumFromInt(@intFromEnum(oo));
|
|
}
|
|
};
|
|
|
|
comptime {
|
|
// Goal is to keep this under one byte for efficiency.
|
|
assert(@sizeOf(Tag) == 1);
|
|
|
|
if (!std.debug.runtime_safety) {
|
|
assert(@sizeOf(Data) == 8);
|
|
}
|
|
}
|
|
|
|
/// The FooComma/FooSemicolon variants exist to ease the implementation of
|
|
/// `Ast.lastToken()`
|
|
pub const Tag = enum {
|
|
/// The root node which is guaranteed to be at `Node.Index.root`.
|
|
/// The meaning of the `data` field depends on whether it is a `.zig` or
|
|
/// `.zon` file.
|
|
///
|
|
/// The `main_token` field is the first token for the source file.
|
|
root,
|
|
/// `test {}`,
|
|
/// `test "name" {}`,
|
|
/// `test identifier {}`.
|
|
///
|
|
/// The `data` field is a `.opt_token_and_node`:
|
|
/// 1. a `OptionalTokenIndex` to the test name token (must be string literal or identifier), if any.
|
|
/// 2. a `Node.Index` to the block.
|
|
///
|
|
/// The `main_token` field is the `test` token.
|
|
test_decl,
|
|
/// The `data` field is a `.extra_and_opt_node`:
|
|
/// 1. a `ExtraIndex` to `GlobalVarDecl`.
|
|
/// 2. a `Node.OptionalIndex` to the initialization expression.
|
|
///
|
|
/// The `main_token` field is the `var` or `const` token.
|
|
///
|
|
/// The initialization expression can't be `.none` unless it is part of
|
|
/// a `assign_destructure` node or a parsing error occured.
|
|
global_var_decl,
|
|
/// `var a: b align(c) = d`.
|
|
/// `const main_token: type_node align(align_node) = init_expr`.
|
|
///
|
|
/// The `data` field is a `.extra_and_opt_node`:
|
|
/// 1. a `ExtraIndex` to `LocalVarDecl`.
|
|
/// 2. a `Node.OptionalIndex` to the initialization expression-
|
|
///
|
|
/// The `main_token` field is the `var` or `const` token.
|
|
///
|
|
/// The initialization expression can't be `.none` unless it is part of
|
|
/// a `assign_destructure` node or a parsing error occured.
|
|
local_var_decl,
|
|
/// `var a: b = c`.
|
|
/// `const name_token: type_expr = init_expr`.
|
|
/// Can be local or global.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_opt_node`:
|
|
/// 1. a `Node.OptionalIndex` to the type expression, if any.
|
|
/// 2. a `Node.OptionalIndex` to the initialization expression.
|
|
///
|
|
/// The `main_token` field is the `var` or `const` token.
|
|
///
|
|
/// The initialization expression can't be `.none` unless it is part of
|
|
/// a `assign_destructure` node or a parsing error occured.
|
|
simple_var_decl,
|
|
/// `var a align(b) = c`.
|
|
/// `const name_token align(align_expr) = init_expr`.
|
|
/// Can be local or global.
|
|
///
|
|
/// The `data` field is a `.node_and_opt_node`:
|
|
/// 1. a `Node.Index` to the alignment expression.
|
|
/// 2. a `Node.OptionalIndex` to the initialization expression.
|
|
///
|
|
/// The `main_token` field is the `var` or `const` token.
|
|
///
|
|
/// The initialization expression can't be `.none` unless it is part of
|
|
/// a `assign_destructure` node or a parsing error occured.
|
|
aligned_var_decl,
|
|
/// `errdefer expr`,
|
|
/// `errdefer |payload| expr`.
|
|
///
|
|
/// The `data` field is a `.opt_token_and_node`:
|
|
/// 1. a `OptionalTokenIndex` to the payload identifier, if any.
|
|
/// 2. a `Node.Index` to the deferred expression.
|
|
///
|
|
/// The `main_token` field is the `errdefer` token.
|
|
@"errdefer",
|
|
/// `defer expr`.
|
|
///
|
|
/// The `data` field is a `.node` to the deferred expression.
|
|
///
|
|
/// The `main_token` field is the `defer`.
|
|
@"defer",
|
|
/// `lhs catch rhs`,
|
|
/// `lhs catch |err| rhs`.
|
|
///
|
|
/// The `main_token` field is the `catch` token.
|
|
///
|
|
/// The error payload is determined by looking at the next token after
|
|
/// the `catch` token.
|
|
@"catch",
|
|
/// `lhs.a`.
|
|
///
|
|
/// The `data` field is a `.node_and_token`:
|
|
/// 1. a `Node.Index` to the left side of the field access.
|
|
/// 2. a `TokenIndex` to the field name identifier.
|
|
///
|
|
/// The `main_token` field is the `.` token.
|
|
field_access,
|
|
/// `lhs.?`.
|
|
///
|
|
/// The `data` field is a `.node_and_token`:
|
|
/// 1. a `Node.Index` to the left side of the optional unwrap.
|
|
/// 2. a `TokenIndex` to the `?` token.
|
|
///
|
|
/// The `main_token` field is the `.` token.
|
|
unwrap_optional,
|
|
/// `lhs == rhs`. The `main_token` field is the `==` token.
|
|
equal_equal,
|
|
/// `lhs != rhs`. The `main_token` field is the `!=` token.
|
|
bang_equal,
|
|
/// `lhs < rhs`. The `main_token` field is the `<` token.
|
|
less_than,
|
|
/// `lhs > rhs`. The `main_token` field is the `>` token.
|
|
greater_than,
|
|
/// `lhs <= rhs`. The `main_token` field is the `<=` token.
|
|
less_or_equal,
|
|
/// `lhs >= rhs`. The `main_token` field is the `>=` token.
|
|
greater_or_equal,
|
|
/// `lhs *= rhs`. The `main_token` field is the `*=` token.
|
|
assign_mul,
|
|
/// `lhs /= rhs`. The `main_token` field is the `/=` token.
|
|
assign_div,
|
|
/// `lhs %= rhs`. The `main_token` field is the `%=` token.
|
|
assign_mod,
|
|
/// `lhs += rhs`. The `main_token` field is the `+=` token.
|
|
assign_add,
|
|
/// `lhs -= rhs`. The `main_token` field is the `-=` token.
|
|
assign_sub,
|
|
/// `lhs <<= rhs`. The `main_token` field is the `<<=` token.
|
|
assign_shl,
|
|
/// `lhs <<|= rhs`. The `main_token` field is the `<<|=` token.
|
|
assign_shl_sat,
|
|
/// `lhs >>= rhs`. The `main_token` field is the `>>=` token.
|
|
assign_shr,
|
|
/// `lhs &= rhs`. The `main_token` field is the `&=` token.
|
|
assign_bit_and,
|
|
/// `lhs ^= rhs`. The `main_token` field is the `^=` token.
|
|
assign_bit_xor,
|
|
/// `lhs |= rhs`. The `main_token` field is the `|=` token.
|
|
assign_bit_or,
|
|
/// `lhs *%= rhs`. The `main_token` field is the `*%=` token.
|
|
assign_mul_wrap,
|
|
/// `lhs +%= rhs`. The `main_token` field is the `+%=` token.
|
|
assign_add_wrap,
|
|
/// `lhs -%= rhs`. The `main_token` field is the `-%=` token.
|
|
assign_sub_wrap,
|
|
/// `lhs *|= rhs`. The `main_token` field is the `*%=` token.
|
|
assign_mul_sat,
|
|
/// `lhs +|= rhs`. The `main_token` field is the `+|=` token.
|
|
assign_add_sat,
|
|
/// `lhs -|= rhs`. The `main_token` field is the `-|=` token.
|
|
assign_sub_sat,
|
|
/// `lhs = rhs`. The `main_token` field is the `=` token.
|
|
assign,
|
|
/// `a, b, ... = rhs`.
|
|
///
|
|
/// The `data` field is a `.extra_and_node`:
|
|
/// 1. a `ExtraIndex`. Further explained below.
|
|
/// 2. a `Node.Index` to the initialization expression.
|
|
///
|
|
/// The `main_token` field is the `=` token.
|
|
///
|
|
/// The `ExtraIndex` stores the following data:
|
|
/// ```
|
|
/// elem_count: u32,
|
|
/// variables: [elem_count]Node.Index,
|
|
/// ```
|
|
///
|
|
/// Each node in `variables` has one of the following tags:
|
|
/// - `global_var_decl`
|
|
/// - `local_var_decl`
|
|
/// - `simple_var_decl`
|
|
/// - `aligned_var_decl`
|
|
/// - Any expression node
|
|
///
|
|
/// The first 4 tags correspond to a `var` or `const` lhs node (note
|
|
/// that their initialization expression is always `.none`).
|
|
/// An expression node corresponds to a standard assignment LHS (which
|
|
/// must be evaluated as an lvalue). There may be a preceding
|
|
/// `comptime` token, which does not create a corresponding `comptime`
|
|
/// node so must be manually detected.
|
|
assign_destructure,
|
|
/// `lhs || rhs`. The `main_token` field is the `||` token.
|
|
merge_error_sets,
|
|
/// `lhs * rhs`. The `main_token` field is the `*` token.
|
|
mul,
|
|
/// `lhs / rhs`. The `main_token` field is the `/` token.
|
|
div,
|
|
/// `lhs % rhs`. The `main_token` field is the `%` token.
|
|
mod,
|
|
/// `lhs ** rhs`. The `main_token` field is the `**` token.
|
|
array_mult,
|
|
/// `lhs *% rhs`. The `main_token` field is the `*%` token.
|
|
mul_wrap,
|
|
/// `lhs *| rhs`. The `main_token` field is the `*|` token.
|
|
mul_sat,
|
|
/// `lhs + rhs`. The `main_token` field is the `+` token.
|
|
add,
|
|
/// `lhs - rhs`. The `main_token` field is the `-` token.
|
|
sub,
|
|
/// `lhs ++ rhs`. The `main_token` field is the `++` token.
|
|
array_cat,
|
|
/// `lhs +% rhs`. The `main_token` field is the `+%` token.
|
|
add_wrap,
|
|
/// `lhs -% rhs`. The `main_token` field is the `-%` token.
|
|
sub_wrap,
|
|
/// `lhs +| rhs`. The `main_token` field is the `+|` token.
|
|
add_sat,
|
|
/// `lhs -| rhs`. The `main_token` field is the `-|` token.
|
|
sub_sat,
|
|
/// `lhs << rhs`. The `main_token` field is the `<<` token.
|
|
shl,
|
|
/// `lhs <<| rhs`. The `main_token` field is the `<<|` token.
|
|
shl_sat,
|
|
/// `lhs >> rhs`. The `main_token` field is the `>>` token.
|
|
shr,
|
|
/// `lhs & rhs`. The `main_token` field is the `&` token.
|
|
bit_and,
|
|
/// `lhs ^ rhs`. The `main_token` field is the `^` token.
|
|
bit_xor,
|
|
/// `lhs | rhs`. The `main_token` field is the `|` token.
|
|
bit_or,
|
|
/// `lhs orelse rhs`. The `main_token` field is the `orelse` token.
|
|
@"orelse",
|
|
/// `lhs and rhs`. The `main_token` field is the `and` token.
|
|
bool_and,
|
|
/// `lhs or rhs`. The `main_token` field is the `or` token.
|
|
bool_or,
|
|
/// `!expr`. The `main_token` field is the `!` token.
|
|
bool_not,
|
|
/// `-expr`. The `main_token` field is the `-` token.
|
|
negation,
|
|
/// `~expr`. The `main_token` field is the `~` token.
|
|
bit_not,
|
|
/// `-%expr`. The `main_token` field is the `-%` token.
|
|
negation_wrap,
|
|
/// `&expr`. The `main_token` field is the `&` token.
|
|
address_of,
|
|
/// `try expr`. The `main_token` field is the `try` token.
|
|
@"try",
|
|
/// `?expr`. The `main_token` field is the `?` token.
|
|
optional_type,
|
|
/// `[lhs]rhs`. The `main_token` field is the `[` token.
|
|
array_type,
|
|
/// `[lhs:a]b`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to the length expression.
|
|
/// 2. a `ExtraIndex` to `ArrayTypeSentinel`.
|
|
///
|
|
/// The `main_token` field is the `[` token.
|
|
array_type_sentinel,
|
|
/// `[*]align(lhs) rhs`,
|
|
/// `*align(lhs) rhs`,
|
|
/// `[]rhs`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_node`:
|
|
/// 1. a `Node.OptionalIndex` to the alignment expression, if any.
|
|
/// 2. a `Node.Index` to the element type expression.
|
|
///
|
|
/// The `main_token` is the asterisk if a single item pointer or the
|
|
/// lbracket if a slice, many-item pointer, or C-pointer.
|
|
/// The `main_token` might be a ** token, which is shared with a
|
|
/// parent/child pointer type and may require special handling.
|
|
ptr_type_aligned,
|
|
/// `[*:lhs]rhs`,
|
|
/// `*rhs`,
|
|
/// `[:lhs]rhs`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_node`:
|
|
/// 1. a `Node.OptionalIndex` to the sentinel expression, if any.
|
|
/// 2. a `Node.Index` to the element type expression.
|
|
///
|
|
/// The `main_token` is the asterisk if a single item pointer or the
|
|
/// lbracket if a slice, many-item pointer, or C-pointer.
|
|
/// The `main_token` might be a ** token, which is shared with a
|
|
/// parent/child pointer type and may require special handling.
|
|
ptr_type_sentinel,
|
|
/// The `data` field is a `.extra_and_node`:
|
|
/// 1. a `ExtraIndex` to `PtrType`.
|
|
/// 2. a `Node.Index` to the element type expression.
|
|
///
|
|
/// The `main_token` is the asterisk if a single item pointer or the
|
|
/// lbracket if a slice, many-item pointer, or C-pointer.
|
|
/// The `main_token` might be a ** token, which is shared with a
|
|
/// parent/child pointer type and may require special handling.
|
|
ptr_type,
|
|
/// The `data` field is a `.extra_and_node`:
|
|
/// 1. a `ExtraIndex` to `PtrTypeBitRange`.
|
|
/// 2. a `Node.Index` to the element type expression.
|
|
///
|
|
/// The `main_token` is the asterisk if a single item pointer or the
|
|
/// lbracket if a slice, many-item pointer, or C-pointer.
|
|
/// The `main_token` might be a ** token, which is shared with a
|
|
/// parent/child pointer type and may require special handling.
|
|
ptr_type_bit_range,
|
|
/// `lhs[rhs..]`
|
|
///
|
|
/// The `main_token` field is the `[` token.
|
|
slice_open,
|
|
/// `sliced[start..end]`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to the sliced expression.
|
|
/// 2. a `ExtraIndex` to `Slice`.
|
|
///
|
|
/// The `main_token` field is the `[` token.
|
|
slice,
|
|
/// `sliced[start..end :sentinel]`,
|
|
/// `sliced[start.. :sentinel]`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to the sliced expression.
|
|
/// 2. a `ExtraIndex` to `SliceSentinel`.
|
|
///
|
|
/// The `main_token` field is the `[` token.
|
|
slice_sentinel,
|
|
/// `expr.*`.
|
|
///
|
|
/// The `data` field is a `.node` to expr.
|
|
///
|
|
/// The `main_token` field is the `*` token.
|
|
deref,
|
|
/// `lhs[rhs]`.
|
|
///
|
|
/// The `main_token` field is the `[` token.
|
|
array_access,
|
|
/// `lhs{rhs}`.
|
|
///
|
|
/// The `main_token` field is the `{` token.
|
|
array_init_one,
|
|
/// Same as `array_init_one` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
array_init_one_comma,
|
|
/// `.{a}`,
|
|
/// `.{a, b}`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_opt_node`:
|
|
/// 1. a `Node.OptionalIndex` to the first element. Never `.none`
|
|
/// 2. a `Node.OptionalIndex` to the second element, if any.
|
|
///
|
|
/// The `main_token` field is the `{` token.
|
|
array_init_dot_two,
|
|
/// Same as `array_init_dot_two` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
array_init_dot_two_comma,
|
|
/// `.{a, b, c}`.
|
|
///
|
|
/// The `data` field is a `.extra_range` that stores a `Node.Index` for
|
|
/// each element.
|
|
///
|
|
/// The `main_token` field is the `{` token.
|
|
array_init_dot,
|
|
/// Same as `array_init_dot` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
array_init_dot_comma,
|
|
/// `a{b, c}`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to the type expression.
|
|
/// 2. a `ExtraIndex` to a `SubRange` that stores a `Node.Index` for
|
|
/// each element.
|
|
///
|
|
/// The `main_token` field is the `{` token.
|
|
array_init,
|
|
/// Same as `array_init` except there is known to be a trailing comma
|
|
/// before the final rbrace.
|
|
array_init_comma,
|
|
/// `a{.x = b}`, `a{}`.
|
|
///
|
|
/// The `data` field is a `.node_and_opt_node`:
|
|
/// 1. a `Node.Index` to the type expression.
|
|
/// 2. a `Node.OptionalIndex` to the first field initialization, if any.
|
|
///
|
|
/// The `main_token` field is the `{` token.
|
|
///
|
|
/// The field name is determined by looking at the tokens preceding the
|
|
/// field initialization.
|
|
struct_init_one,
|
|
/// Same as `struct_init_one` except there is known to be a trailing comma
|
|
/// before the final rbrace.
|
|
struct_init_one_comma,
|
|
/// `.{.x = a, .y = b}`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_opt_node`:
|
|
/// 1. a `Node.OptionalIndex` to the first field initialization. Never `.none`
|
|
/// 2. a `Node.OptionalIndex` to the second field initialization, if any.
|
|
///
|
|
/// The `main_token` field is the '{' token.
|
|
///
|
|
/// The field name is determined by looking at the tokens preceding the
|
|
/// field initialization.
|
|
struct_init_dot_two,
|
|
/// Same as `struct_init_dot_two` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
struct_init_dot_two_comma,
|
|
/// `.{.x = a, .y = b, .z = c}`.
|
|
///
|
|
/// The `data` field is a `.extra_range` that stores a `Node.Index` for
|
|
/// each field initialization.
|
|
///
|
|
/// The `main_token` field is the `{` token.
|
|
///
|
|
/// The field name is determined by looking at the tokens preceding the
|
|
/// field initialization.
|
|
struct_init_dot,
|
|
/// Same as `struct_init_dot` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
struct_init_dot_comma,
|
|
/// `a{.x = b, .y = c}`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to the type expression.
|
|
/// 2. a `ExtraIndex` to a `SubRange` that stores a `Node.Index` for
|
|
/// each field initialization.
|
|
///
|
|
/// The `main_token` field is the `{` token.
|
|
///
|
|
/// The field name is determined by looking at the tokens preceding the
|
|
/// field initialization.
|
|
struct_init,
|
|
/// Same as `struct_init` except there is known to be a trailing comma
|
|
/// before the final rbrace.
|
|
struct_init_comma,
|
|
/// `a(b)`, `a()`.
|
|
///
|
|
/// The `data` field is a `.node_and_opt_node`:
|
|
/// 1. a `Node.Index` to the function expression.
|
|
/// 2. a `Node.OptionalIndex` to the first argument, if any.
|
|
///
|
|
/// The `main_token` field is the `(` token.
|
|
call_one,
|
|
/// Same as `call_one` except there is known to be a trailing comma
|
|
/// before the final rparen.
|
|
call_one_comma,
|
|
/// `a(b, c, d)`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to the function expression.
|
|
/// 2. a `ExtraIndex` to a `SubRange` that stores a `Node.Index` for
|
|
/// each argument.
|
|
///
|
|
/// The `main_token` field is the `(` token.
|
|
call,
|
|
/// Same as `call` except there is known to be a trailing comma before
|
|
/// the final rparen.
|
|
call_comma,
|
|
/// `switch(a) {}`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to the switch operand.
|
|
/// 2. a `ExtraIndex` to a `SubRange` that stores a `Node.Index` for
|
|
/// each switch case.
|
|
///
|
|
/// `The `main_token` field` is the identifier of a preceding label, if any; otherwise `switch`.
|
|
@"switch",
|
|
/// Same as `switch` except there is known to be a trailing comma before
|
|
/// the final rbrace.
|
|
switch_comma,
|
|
/// `a => b`,
|
|
/// `else => b`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_node`:
|
|
/// 1. a `Node.OptionalIndex` where `.none` means `else`.
|
|
/// 2. a `Node.Index` to the target expression.
|
|
///
|
|
/// The `main_token` field is the `=>` token.
|
|
switch_case_one,
|
|
/// Same as `switch_case_one` but the case is inline.
|
|
switch_case_inline_one,
|
|
/// `a, b, c => d`.
|
|
///
|
|
/// The `data` field is a `.extra_and_node`:
|
|
/// 1. a `ExtraIndex` to a `SubRange` that stores a `Node.Index` for
|
|
/// each switch item.
|
|
/// 2. a `Node.Index` to the target expression.
|
|
///
|
|
/// The `main_token` field is the `=>` token.
|
|
switch_case,
|
|
/// Same as `switch_case` but the case is inline.
|
|
switch_case_inline,
|
|
/// `lhs...rhs`.
|
|
///
|
|
/// The `main_token` field is the `...` token.
|
|
switch_range,
|
|
/// `while (a) b`,
|
|
/// `while (a) |x| b`.
|
|
while_simple,
|
|
/// `while (a) : (b) c`,
|
|
/// `while (a) |x| : (b) c`.
|
|
while_cont,
|
|
/// `while (a) : (b) c else d`,
|
|
/// `while (a) |x| : (b) c else d`,
|
|
/// `while (a) |x| : (b) c else |y| d`.
|
|
/// The continue expression part `: (b)` may be omitted.
|
|
@"while",
|
|
/// `for (a) b`.
|
|
for_simple,
|
|
/// `for (lhs[0..inputs]) lhs[inputs + 1] else lhs[inputs + 2]`. `For[rhs]`.
|
|
@"for",
|
|
/// `lhs..rhs`, `lhs..`.
|
|
for_range,
|
|
/// `if (a) b`.
|
|
/// `if (b) |x| b`.
|
|
if_simple,
|
|
/// `if (a) b else c`.
|
|
/// `if (a) |x| b else c`.
|
|
/// `if (a) |x| b else |y| d`.
|
|
@"if",
|
|
/// `suspend expr`.
|
|
///
|
|
/// The `data` field is a `.node` to expr.
|
|
///
|
|
/// The `main_token` field is the `suspend` token.
|
|
@"suspend",
|
|
/// `resume expr`.
|
|
///
|
|
/// The `data` field is a `.node` to expr.
|
|
///
|
|
/// The `main_token` field is the `resume` token.
|
|
@"resume",
|
|
/// `continue :label expr`,
|
|
/// `continue expr`,
|
|
/// `continue :label`,
|
|
/// `continue`.
|
|
///
|
|
/// The `data` field is a `.opt_token_and_opt_node`:
|
|
/// 1. a `OptionalTokenIndex` to the label identifier, if any.
|
|
/// 2. a `Node.OptionalIndex` to the target expression, if any.
|
|
///
|
|
/// The `main_token` field is the `continue` token.
|
|
@"continue",
|
|
/// `break :label expr`,
|
|
/// `break expr`,
|
|
/// `break :label`,
|
|
/// `break`.
|
|
///
|
|
/// The `data` field is a `.opt_token_and_opt_node`:
|
|
/// 1. a `OptionalTokenIndex` to the label identifier, if any.
|
|
/// 2. a `Node.OptionalIndex` to the target expression, if any.
|
|
///
|
|
/// The `main_token` field is the `break` token.
|
|
@"break",
|
|
/// `return expr`, `return`.
|
|
///
|
|
/// The `data` field is a `.opt_node` to the return value, if any.
|
|
///
|
|
/// The `main_token` field is the `return` token.
|
|
@"return",
|
|
/// `fn (a: type_expr) return_type`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_opt_node`:
|
|
/// 1. a `Node.OptionalIndex` to the first parameter type expression, if any.
|
|
/// 2. a `Node.OptionalIndex` to the return type expression. Can't be
|
|
/// `.none` unless a parsing error occured.
|
|
///
|
|
/// The `main_token` field is the `fn` token.
|
|
///
|
|
/// `anytype` and `...` parameters are omitted from the AST tree.
|
|
/// Extern function declarations use this tag.
|
|
fn_proto_simple,
|
|
/// `fn (a: b, c: d) return_type`.
|
|
///
|
|
/// The `data` field is a `.extra_and_opt_node`:
|
|
/// 1. a `ExtraIndex` to a `SubRange` that stores a `Node.Index` for
|
|
/// each parameter type expression.
|
|
/// 2. a `Node.OptionalIndex` to the return type expression. Can't be
|
|
/// `.none` unless a parsing error occured.
|
|
///
|
|
/// The `main_token` field is the `fn` token.
|
|
///
|
|
/// `anytype` and `...` parameters are omitted from the AST tree.
|
|
/// Extern function declarations use this tag.
|
|
fn_proto_multi,
|
|
/// `fn (a: b) addrspace(e) linksection(f) callconv(g) return_type`.
|
|
/// zero or one parameters.
|
|
///
|
|
/// The `data` field is a `.extra_and_opt_node`:
|
|
/// 1. a `Node.ExtraIndex` to `FnProtoOne`.
|
|
/// 2. a `Node.OptionalIndex` to the return type expression. Can't be
|
|
/// `.none` unless a parsing error occured.
|
|
///
|
|
/// The `main_token` field is the `fn` token.
|
|
///
|
|
/// `anytype` and `...` parameters are omitted from the AST tree.
|
|
/// Extern function declarations use this tag.
|
|
fn_proto_one,
|
|
/// `fn (a: b, c: d) addrspace(e) linksection(f) callconv(g) return_type`.
|
|
///
|
|
/// The `data` field is a `.extra_and_opt_node`:
|
|
/// 1. a `Node.ExtraIndex` to `FnProto`.
|
|
/// 2. a `Node.OptionalIndex` to the return type expression. Can't be
|
|
/// `.none` unless a parsing error occured.
|
|
///
|
|
/// The `main_token` field is the `fn` token.
|
|
///
|
|
/// `anytype` and `...` parameters are omitted from the AST tree.
|
|
/// Extern function declarations use this tag.
|
|
fn_proto,
|
|
/// Extern function declarations use the fn_proto tags rather than this one.
|
|
///
|
|
/// The `data` field is a `.node_and_node`:
|
|
/// 1. a `Node.Index` to `fn_proto_*`.
|
|
/// 2. a `Node.Index` to function body block.
|
|
///
|
|
/// The `main_token` field is the `fn` token.
|
|
fn_decl,
|
|
/// `anyframe->return_type`.
|
|
///
|
|
/// The `data` field is a `.token_and_node`:
|
|
/// 1. a `TokenIndex` to the `->` token.
|
|
/// 2. a `Node.Index` to the function frame return type expression.
|
|
///
|
|
/// The `main_token` field is the `anyframe` token.
|
|
anyframe_type,
|
|
/// The `data` field is unused.
|
|
anyframe_literal,
|
|
/// The `data` field is unused.
|
|
char_literal,
|
|
/// The `data` field is unused.
|
|
number_literal,
|
|
/// The `data` field is unused.
|
|
unreachable_literal,
|
|
/// The `data` field is unused.
|
|
///
|
|
/// Most identifiers will not have explicit AST nodes, however for
|
|
/// expressions which could be one of many different kinds of AST nodes,
|
|
/// there will be an identifier AST node for it.
|
|
identifier,
|
|
/// `.foo`.
|
|
///
|
|
/// The `data` field is unused.
|
|
///
|
|
/// The `main_token` field is the identifier.
|
|
enum_literal,
|
|
/// The `data` field is unused.
|
|
///
|
|
/// The `main_token` field is the string literal token.
|
|
string_literal,
|
|
/// The `data` field is a `.token_and_token`:
|
|
/// 1. a `TokenIndex` to the first `.multiline_string_literal_line` token.
|
|
/// 2. a `TokenIndex` to the last `.multiline_string_literal_line` token.
|
|
///
|
|
/// The `main_token` field is the first token index (redundant with `data`).
|
|
multiline_string_literal,
|
|
/// `(expr)`.
|
|
///
|
|
/// The `data` field is a `.node_and_token`:
|
|
/// 1. a `Node.Index` to the sub-expression
|
|
/// 2. a `TokenIndex` to the `)` token.
|
|
///
|
|
/// The `main_token` field is the `(` token.
|
|
grouped_expression,
|
|
/// `@a(b, c)`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_opt_node`:
|
|
/// 1. a `Node.OptionalIndex` to the first argument, if any.
|
|
/// 2. a `Node.OptionalIndex` to the second argument, if any.
|
|
///
|
|
/// The `main_token` field is the builtin token.
|
|
builtin_call_two,
|
|
/// Same as `builtin_call_two` except there is known to be a trailing comma
|
|
/// before the final rparen.
|
|
builtin_call_two_comma,
|
|
/// `@a(b, c, d)`.
|
|
///
|
|
/// The `data` field is a `.extra_range` that stores a `Node.Index` for
|
|
/// each argument.
|
|
///
|
|
/// The `main_token` field is the builtin token.
|
|
builtin_call,
|
|
/// Same as `builtin_call` except there is known to be a trailing comma
|
|
/// before the final rparen.
|
|
builtin_call_comma,
|
|
/// `error{a, b}`.
|
|
///
|
|
/// The `data` field is a `.token_and_token`:
|
|
/// 1. a `TokenIndex` to the `{` token.
|
|
/// 2. a `TokenIndex` to the `}` token.
|
|
///
|
|
/// The `main_token` field is the `error`.
|
|
error_set_decl,
|
|
/// `struct {}`, `union {}`, `opaque {}`, `enum {}`.
|
|
///
|
|
/// The `data` field is a `.extra_range` that stores a `Node.Index` for
|
|
/// each container member.
|
|
///
|
|
/// The `main_token` field is the `struct`, `union`, `opaque` or `enum` token.
|
|
container_decl,
|
|
/// Same as `container_decl` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
container_decl_trailing,
|
|
/// `struct {lhs, rhs}`, `union {lhs, rhs}`, `opaque {lhs, rhs}`, `enum {lhs, rhs}`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_opt_node`:
|
|
/// 1. a `Node.OptionalIndex` to the first container member, if any.
|
|
/// 2. a `Node.OptionalIndex` to the second container member, if any.
|
|
///
|
|
/// The `main_token` field is the `struct`, `union`, `opaque` or `enum` token.
|
|
container_decl_two,
|
|
/// Same as `container_decl_two` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
container_decl_two_trailing,
|
|
/// `struct(arg)`, `union(arg)`, `enum(arg)`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to arg.
|
|
/// 2. a `ExtraIndex` to a `SubRange` that stores a `Node.Index` for
|
|
/// each container member.
|
|
///
|
|
/// The `main_token` field is the `struct`, `union` or `enum` token.
|
|
container_decl_arg,
|
|
/// Same as `container_decl_arg` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
container_decl_arg_trailing,
|
|
/// `union(enum) {}`.
|
|
///
|
|
/// The `data` field is a `.extra_range` that stores a `Node.Index` for
|
|
/// each container member.
|
|
///
|
|
/// The `main_token` field is the `union` token.
|
|
///
|
|
/// A tagged union with explicitly provided enums will instead be
|
|
/// represented by `container_decl_arg`.
|
|
tagged_union,
|
|
/// Same as `tagged_union` except there is known to be a trailing comma
|
|
/// before the final rbrace.
|
|
tagged_union_trailing,
|
|
/// `union(enum) {lhs, rhs}`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_opt_node`:
|
|
/// 1. a `Node.OptionalIndex` to the first container member, if any.
|
|
/// 2. a `Node.OptionalIndex` to the second container member, if any.
|
|
///
|
|
/// The `main_token` field is the `union` token.
|
|
///
|
|
/// A tagged union with explicitly provided enums will instead be
|
|
/// represented by `container_decl_arg`.
|
|
tagged_union_two,
|
|
/// Same as `tagged_union_two` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
tagged_union_two_trailing,
|
|
/// `union(enum(arg)) {}`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to arg.
|
|
/// 2. a `ExtraIndex` to a `SubRange` that stores a `Node.Index` for
|
|
/// each container member.
|
|
///
|
|
/// The `main_token` field is the `union` token.
|
|
tagged_union_enum_tag,
|
|
/// Same as `tagged_union_enum_tag` except there is known to be a
|
|
/// trailing comma before the final rbrace.
|
|
tagged_union_enum_tag_trailing,
|
|
/// `a: lhs = rhs,`,
|
|
/// `a: lhs,`.
|
|
///
|
|
/// The `data` field is a `.node_and_opt_node`:
|
|
/// 1. a `Node.Index` to the field type expression.
|
|
/// 2. a `Node.OptionalIndex` to the default value expression, if any.
|
|
///
|
|
/// The `main_token` field is the field name identifier.
|
|
///
|
|
/// `lastToken()` does not include the possible trailing comma.
|
|
container_field_init,
|
|
/// `a: lhs align(rhs),`.
|
|
///
|
|
/// The `data` field is a `.node_and_node`:
|
|
/// 1. a `Node.Index` to the field type expression.
|
|
/// 2. a `Node.Index` to the alignment expression.
|
|
///
|
|
/// The `main_token` field is the field name identifier.
|
|
///
|
|
/// `lastToken()` does not include the possible trailing comma.
|
|
container_field_align,
|
|
/// `a: lhs align(c) = d,`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to the field type expression.
|
|
/// 2. a `ExtraIndex` to `ContainerField`.
|
|
///
|
|
/// The `main_token` field is the field name identifier.
|
|
///
|
|
/// `lastToken()` does not include the possible trailing comma.
|
|
container_field,
|
|
/// `comptime expr`.
|
|
///
|
|
/// The `data` field is a `.node` to expr.
|
|
///
|
|
/// The `main_token` field is the `comptime` token.
|
|
@"comptime",
|
|
/// `nosuspend expr`.
|
|
///
|
|
/// The `data` field is a `.node` to expr.
|
|
///
|
|
/// The `main_token` field is the `nosuspend` token.
|
|
@"nosuspend",
|
|
/// `{lhs rhs}`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_opt_node`:
|
|
/// 1. a `Node.OptionalIndex` to the first statement, if any.
|
|
/// 2. a `Node.OptionalIndex` to the second statement, if any.
|
|
///
|
|
/// The `main_token` field is the `{` token.
|
|
block_two,
|
|
/// Same as `block_two` except there is known to be a trailing
|
|
/// comma before the final rbrace.
|
|
block_two_semicolon,
|
|
/// `{a b}`.
|
|
///
|
|
/// The `data` field is a `.extra_range` that stores a `Node.Index` for
|
|
/// each statement.
|
|
///
|
|
/// The `main_token` field is the `{` token.
|
|
block,
|
|
/// Same as `block` except there is known to be a trailing comma before
|
|
/// the final rbrace.
|
|
block_semicolon,
|
|
/// `asm(a)`.
|
|
///
|
|
/// The `main_token` field is the `asm` token.
|
|
asm_simple,
|
|
/// `asm(lhs, a)`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to lhs.
|
|
/// 2. a `ExtraIndex` to `AsmLegacy`.
|
|
///
|
|
/// The `main_token` field is the `asm` token.
|
|
asm_legacy,
|
|
/// `asm(a, b)`.
|
|
///
|
|
/// The `data` field is a `.node_and_extra`:
|
|
/// 1. a `Node.Index` to a.
|
|
/// 2. a `ExtraIndex` to `Asm`.
|
|
///
|
|
/// The `main_token` field is the `asm` token.
|
|
@"asm",
|
|
/// `[a] "b" (c)`.
|
|
/// `[a] "b" (-> lhs)`.
|
|
///
|
|
/// The `data` field is a `.opt_node_and_token`:
|
|
/// 1. a `Node.OptionalIndex` to lhs, if any.
|
|
/// 2. a `TokenIndex` to the `)` token.
|
|
///
|
|
/// The `main_token` field is `a`.
|
|
asm_output,
|
|
/// `[a] "b" (lhs)`.
|
|
///
|
|
/// The `data` field is a `.node_and_token`:
|
|
/// 1. a `Node.Index` to lhs.
|
|
/// 2. a `TokenIndex` to the `)` token.
|
|
///
|
|
/// The `main_token` field is `a`.
|
|
asm_input,
|
|
/// `error.a`.
|
|
///
|
|
/// The `data` field is unused.
|
|
///
|
|
/// The `main_token` field is `error` token.
|
|
error_value,
|
|
/// `lhs!rhs`.
|
|
///
|
|
/// The `main_token` field is the `!` token.
|
|
error_union,
|
|
|
|
pub fn isContainerField(tag: Tag) bool {
|
|
return switch (tag) {
|
|
.container_field_init,
|
|
.container_field_align,
|
|
.container_field,
|
|
=> true,
|
|
|
|
else => false,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Data = union {
|
|
node: Index,
|
|
opt_node: OptionalIndex,
|
|
token: TokenIndex,
|
|
node_and_node: struct { Index, Index },
|
|
opt_node_and_opt_node: struct { OptionalIndex, OptionalIndex },
|
|
node_and_opt_node: struct { Index, OptionalIndex },
|
|
opt_node_and_node: struct { OptionalIndex, Index },
|
|
node_and_extra: struct { Index, ExtraIndex },
|
|
extra_and_node: struct { ExtraIndex, Index },
|
|
extra_and_opt_node: struct { ExtraIndex, OptionalIndex },
|
|
node_and_token: struct { Index, TokenIndex },
|
|
token_and_node: struct { TokenIndex, Index },
|
|
token_and_token: struct { TokenIndex, TokenIndex },
|
|
opt_node_and_token: struct { OptionalIndex, TokenIndex },
|
|
opt_token_and_node: struct { OptionalTokenIndex, Index },
|
|
opt_token_and_opt_node: struct { OptionalTokenIndex, OptionalIndex },
|
|
opt_token_and_opt_token: struct { OptionalTokenIndex, OptionalTokenIndex },
|
|
@"for": struct { ExtraIndex, For },
|
|
extra_range: SubRange,
|
|
};
|
|
|
|
pub const LocalVarDecl = struct {
|
|
type_node: Index,
|
|
align_node: Index,
|
|
};
|
|
|
|
pub const ArrayTypeSentinel = struct {
|
|
sentinel: Index,
|
|
elem_type: Index,
|
|
};
|
|
|
|
pub const PtrType = struct {
|
|
sentinel: OptionalIndex,
|
|
align_node: OptionalIndex,
|
|
addrspace_node: OptionalIndex,
|
|
};
|
|
|
|
pub const PtrTypeBitRange = struct {
|
|
sentinel: OptionalIndex,
|
|
align_node: Index,
|
|
addrspace_node: OptionalIndex,
|
|
bit_range_start: Index,
|
|
bit_range_end: Index,
|
|
};
|
|
|
|
pub const SubRange = struct {
|
|
/// Index into extra_data.
|
|
start: ExtraIndex,
|
|
/// Index into extra_data.
|
|
end: ExtraIndex,
|
|
};
|
|
|
|
pub const If = struct {
|
|
then_expr: Index,
|
|
else_expr: Index,
|
|
};
|
|
|
|
pub const ContainerField = struct {
|
|
align_expr: Index,
|
|
value_expr: Index,
|
|
};
|
|
|
|
pub const GlobalVarDecl = struct {
|
|
/// Populated if there is an explicit type ascription.
|
|
type_node: OptionalIndex,
|
|
/// Populated if align(A) is present.
|
|
align_node: OptionalIndex,
|
|
/// Populated if addrspace(A) is present.
|
|
addrspace_node: OptionalIndex,
|
|
/// Populated if linksection(A) is present.
|
|
section_node: OptionalIndex,
|
|
};
|
|
|
|
pub const Slice = struct {
|
|
start: Index,
|
|
end: Index,
|
|
};
|
|
|
|
pub const SliceSentinel = struct {
|
|
start: Index,
|
|
/// May be .none if the slice is "open"
|
|
end: OptionalIndex,
|
|
sentinel: Index,
|
|
};
|
|
|
|
pub const While = struct {
|
|
cont_expr: OptionalIndex,
|
|
then_expr: Index,
|
|
else_expr: Index,
|
|
};
|
|
|
|
pub const WhileCont = struct {
|
|
cont_expr: Index,
|
|
then_expr: Index,
|
|
};
|
|
|
|
pub const For = packed struct(u32) {
|
|
inputs: u31,
|
|
has_else: bool,
|
|
};
|
|
|
|
pub const FnProtoOne = struct {
|
|
/// Populated if there is exactly 1 parameter. Otherwise there are 0 parameters.
|
|
param: OptionalIndex,
|
|
/// Populated if align(A) is present.
|
|
align_expr: OptionalIndex,
|
|
/// Populated if addrspace(A) is present.
|
|
addrspace_expr: OptionalIndex,
|
|
/// Populated if linksection(A) is present.
|
|
section_expr: OptionalIndex,
|
|
/// Populated if callconv(A) is present.
|
|
callconv_expr: OptionalIndex,
|
|
};
|
|
|
|
pub const FnProto = struct {
|
|
params_start: ExtraIndex,
|
|
params_end: ExtraIndex,
|
|
/// Populated if align(A) is present.
|
|
align_expr: OptionalIndex,
|
|
/// Populated if addrspace(A) is present.
|
|
addrspace_expr: OptionalIndex,
|
|
/// Populated if linksection(A) is present.
|
|
section_expr: OptionalIndex,
|
|
/// Populated if callconv(A) is present.
|
|
callconv_expr: OptionalIndex,
|
|
};
|
|
|
|
/// To be removed after 0.15.0 is tagged
|
|
pub const AsmLegacy = struct {
|
|
items_start: ExtraIndex,
|
|
items_end: ExtraIndex,
|
|
/// Needed to make lastToken() work.
|
|
rparen: TokenIndex,
|
|
};
|
|
|
|
pub const Asm = struct {
|
|
items_start: ExtraIndex,
|
|
items_end: ExtraIndex,
|
|
clobbers: OptionalIndex,
|
|
/// Needed to make lastToken() work.
|
|
rparen: TokenIndex,
|
|
};
|
|
};
|
|
|
|
pub fn nodeToSpan(tree: *const Ast, node: Ast.Node.Index) Span {
|
|
return tokensToSpan(
|
|
tree,
|
|
tree.firstToken(node),
|
|
tree.lastToken(node),
|
|
tree.nodeMainToken(node),
|
|
);
|
|
}
|
|
|
|
pub fn tokenToSpan(tree: *const Ast, token: Ast.TokenIndex) Span {
|
|
return tokensToSpan(tree, token, token, token);
|
|
}
|
|
|
|
pub fn tokensToSpan(tree: *const Ast, start: Ast.TokenIndex, end: Ast.TokenIndex, main: Ast.TokenIndex) Span {
|
|
var start_tok = start;
|
|
var end_tok = end;
|
|
|
|
if (tree.tokensOnSameLine(start, end)) {
|
|
// do nothing
|
|
} else if (tree.tokensOnSameLine(start, main)) {
|
|
end_tok = main;
|
|
} else if (tree.tokensOnSameLine(main, end)) {
|
|
start_tok = main;
|
|
} else {
|
|
start_tok = main;
|
|
end_tok = main;
|
|
}
|
|
const start_off = tree.tokenStart(start_tok);
|
|
const end_off = tree.tokenStart(end_tok) + @as(u32, @intCast(tree.tokenSlice(end_tok).len));
|
|
return Span{ .start = start_off, .end = end_off, .main = tree.tokenStart(main) };
|
|
}
|
|
|
|
test {
|
|
_ = Parse;
|
|
_ = Render;
|
|
}
|