2025-09-24 20:01:18 -07:00

3064 lines
103 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
pub const Node = extern union {
/// If the tag value is less than Tag.no_payload_count, then no pointer
/// dereference is needed.
tag_if_small_enough: usize,
ptr_otherwise: *Payload,
pub const Tag = enum {
/// Declarations add themselves to the correct scopes and should not be emitted as this tag.
declaration,
null_literal,
undefined_literal,
/// opaque {}
opaque_literal,
true_literal,
false_literal,
empty_block,
return_void,
zero_literal,
one_literal,
@"unreachable",
void_type,
noreturn_type,
@"anytype",
@"continue",
@"break",
// After this, the tag requires a payload.
integer_literal,
float_literal,
string_literal,
char_literal,
enum_literal,
/// "string"[0..end]
string_slice,
identifier,
@"if",
/// if (!operand) break;
if_not_break,
@"while",
/// while (true) operand
while_true,
@"switch",
/// else => operand,
switch_else,
/// items => body,
switch_prong,
break_val,
@"return",
field_access,
array_access,
call,
var_decl,
/// const name = struct { init }
wrapped_local,
/// var name = init.*
mut_str,
func,
warning,
@"struct",
@"union",
@"opaque",
@"comptime",
@"defer",
array_init,
tuple,
container_init,
container_init_dot,
/// _ = operand;
discard,
// a + b
add,
// a = b
add_assign,
// c = (a = b)
add_wrap,
add_wrap_assign,
sub,
sub_assign,
sub_wrap,
sub_wrap_assign,
mul,
mul_assign,
mul_wrap,
mul_wrap_assign,
div,
div_assign,
shl,
shl_assign,
shr,
shr_assign,
mod,
mod_assign,
@"and",
@"or",
less_than,
less_than_equal,
greater_than,
greater_than_equal,
equal,
not_equal,
bit_and,
bit_and_assign,
bit_or,
bit_or_assign,
bit_xor,
bit_xor_assign,
array_cat,
ellipsis3,
assign,
/// @intCast(operand)
int_cast,
/// @constCast(operand)
const_cast,
/// @volatileCast(operand)
volatile_cast,
/// @divTrunc(lhs, rhs)
div_trunc,
/// @intFromBool(operand)
int_from_bool,
/// @as(lhs, rhs)
as,
/// @truncate(operand)
truncate,
/// @bitCast(operand)
bit_cast,
/// @floatCast(operand)
float_cast,
/// @intFromFloat(operand)
int_from_float,
/// @floatFromInt(operand)
float_from_int,
/// @ptrFromInt(operand)
ptr_from_int,
/// @intFromPtr(operand)
int_from_ptr,
/// @alignCast(operand)
align_cast,
/// @ptrCast(operand)
ptr_cast,
/// @divExact(lhs, rhs)
div_exact,
/// @offsetOf(lhs, rhs)
offset_of,
/// @splat(operand)
vector_zero_init,
/// @shuffle(type, a, b, mask)
shuffle,
/// @extern(ty, .{ .name = n })
builtin_extern,
/// @byteSwap(operand)
byte_swap,
/// @ceil(operand)
ceil,
/// @cos(operand)
cos,
/// @sin(operand)
sin,
/// @exp(operand)
exp,
/// @exp2(operand)
exp2,
/// @exp10(operand)
exp10,
/// @abs(operand)
abs,
/// @log(operand)
log,
/// @log2(operand)
log2,
/// @log10(operand)
log10,
/// @round(operand)
round,
/// @sqrt(operand)
sqrt,
/// @trunc(operand)
trunc,
/// @floor(operand)
floor,
/// __helpers.<name>(argshelper_call)
helper_call,
/// __helpers.<name>
helper_ref,
asm_simple,
negate,
negate_wrap,
bit_not,
not,
address_of,
/// .?
unwrap,
/// .*
deref,
block,
/// { operand }
block_single,
sizeof,
alignof,
typeof,
typeinfo,
type,
optional_type,
c_pointer,
single_pointer,
array_type,
null_sentinel_array_type,
/// @Vector(lhs, rhs)
vector,
/// @import("std").mem.zeroes(operand)
std_mem_zeroes,
/// @import("std").mem.zeroInit(lhs, rhs)
std_mem_zeroinit,
// pub const name = @compileError(msg);
fail_decl,
// var actual = mangled;
arg_redecl,
/// pub const alias = actual;
alias,
/// const name = init;
var_simple,
/// pub const name = init;
pub_var_simple,
/// pub? const name (: type)? = value
enum_constant,
/// pub inline fn name(params) return_type body
pub_inline_fn,
/// array_type{}
empty_array,
/// [1]type{val} ** count
array_filler,
/// comptime { if (!(lhs)) @compileError(rhs); }
static_assert,
pub const last_no_payload_tag = Tag.@"break";
pub const no_payload_count = @intFromEnum(last_no_payload_tag) + 1;
pub fn Type(comptime t: Tag) type {
return switch (t) {
.declaration,
.null_literal,
.undefined_literal,
.opaque_literal,
.true_literal,
.false_literal,
.empty_block,
.return_void,
.zero_literal,
.one_literal,
.void_type,
.noreturn_type,
.@"anytype",
.@"continue",
.@"break",
.@"unreachable",
=> @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
.std_mem_zeroes,
.@"return",
.@"comptime",
.@"defer",
.asm_simple,
.negate,
.negate_wrap,
.bit_not,
.not,
.optional_type,
.address_of,
.unwrap,
.deref,
.int_from_ptr,
.empty_array,
.while_true,
.if_not_break,
.switch_else,
.block_single,
.int_from_bool,
.sizeof,
.alignof,
.typeof,
.typeinfo,
.align_cast,
.truncate,
.bit_cast,
.float_cast,
.int_from_float,
.float_from_int,
.ptr_from_int,
.ptr_cast,
.int_cast,
.const_cast,
.volatile_cast,
.vector_zero_init,
.byte_swap,
.ceil,
.cos,
.sin,
.exp,
.exp2,
.exp10,
.abs,
.log,
.log2,
.log10,
.round,
.sqrt,
.trunc,
.floor,
=> Payload.UnOp,
.add,
.add_assign,
.add_wrap,
.add_wrap_assign,
.sub,
.sub_assign,
.sub_wrap,
.sub_wrap_assign,
.mul,
.mul_assign,
.mul_wrap,
.mul_wrap_assign,
.div,
.div_assign,
.shl,
.shl_assign,
.shr,
.shr_assign,
.mod,
.mod_assign,
.@"and",
.@"or",
.less_than,
.less_than_equal,
.greater_than,
.greater_than_equal,
.equal,
.not_equal,
.bit_and,
.bit_and_assign,
.bit_or,
.bit_or_assign,
.bit_xor,
.bit_xor_assign,
.div_trunc,
.as,
.array_cat,
.ellipsis3,
.assign,
.array_access,
.std_mem_zeroinit,
.vector,
.div_exact,
.offset_of,
.static_assert,
=> Payload.BinOp,
.integer_literal,
.float_literal,
.string_literal,
.char_literal,
.enum_literal,
.identifier,
.warning,
.type,
=> Payload.Value,
.discard => Payload.Discard,
.@"if" => Payload.If,
.@"while" => Payload.While,
.@"switch", .array_init, .switch_prong => Payload.Switch,
.break_val => Payload.BreakVal,
.call => Payload.Call,
.var_decl => Payload.VarDecl,
.func => Payload.Func,
.@"struct", .@"union", .@"opaque" => Payload.Container,
.tuple => Payload.TupleInit,
.container_init => Payload.ContainerInit,
.container_init_dot => Payload.ContainerInitDot,
.block => Payload.Block,
.c_pointer, .single_pointer => Payload.Pointer,
.array_type, .null_sentinel_array_type => Payload.Array,
.arg_redecl, .alias, .fail_decl => Payload.ArgRedecl,
.var_simple, .pub_var_simple, .wrapped_local, .mut_str => Payload.SimpleVarDecl,
.enum_constant => Payload.EnumConstant,
.array_filler => Payload.ArrayFiller,
.pub_inline_fn => Payload.PubInlineFn,
.field_access => Payload.FieldAccess,
.string_slice => Payload.StringSlice,
.shuffle => Payload.Shuffle,
.builtin_extern => Payload.Extern,
.helper_call => Payload.HelperCall,
.helper_ref => Payload.HelperRef,
};
}
pub fn init(comptime t: Tag) Node {
comptime std.debug.assert(@intFromEnum(t) < Tag.no_payload_count);
return .{ .tag_if_small_enough = @intFromEnum(t) };
}
pub fn create(comptime t: Tag, ally: Allocator, data: Data(t)) error{OutOfMemory}!Node {
const ptr = try ally.create(t.Type());
ptr.* = .{
.base = .{ .tag = t },
.data = data,
};
return Node{ .ptr_otherwise = &ptr.base };
}
pub fn Data(comptime t: Tag) type {
return std.meta.fieldInfo(t.Type(), .data).type;
}
};
pub fn tag(self: Node) Tag {
if (self.tag_if_small_enough < Tag.no_payload_count) {
return @enumFromInt(@as(std.meta.Tag(Tag), @intCast(self.tag_if_small_enough)));
} else {
return self.ptr_otherwise.tag;
}
}
pub fn castTag(self: Node, comptime t: Tag) ?*t.Type() {
if (self.tag_if_small_enough < Tag.no_payload_count)
return null;
if (self.ptr_otherwise.tag == t)
return @alignCast(@fieldParentPtr("base", self.ptr_otherwise));
return null;
}
pub fn initPayload(payload: *Payload) Node {
std.debug.assert(@intFromEnum(payload.tag) >= Tag.no_payload_count);
return .{ .ptr_otherwise = payload };
}
pub fn isNoreturn(node: Node, break_counts: bool) bool {
switch (node.tag()) {
.block => {
const block_node = node.castTag(.block).?;
if (block_node.data.stmts.len == 0) return false;
const last = block_node.data.stmts[block_node.data.stmts.len - 1];
return last.isNoreturn(break_counts);
},
.@"switch" => {
const switch_node = node.castTag(.@"switch").?;
for (switch_node.data.cases) |case| {
const body = if (case.castTag(.switch_else)) |some|
some.data
else if (case.castTag(.switch_prong)) |some|
some.data.cond
else
unreachable;
if (!body.isNoreturn(break_counts)) return false;
}
return true;
},
.@"return", .return_void => return true,
.@"break" => if (break_counts) return true,
else => {},
}
return false;
}
pub fn isBoolRes(res: Node) bool {
switch (res.tag()) {
.@"or",
.@"and",
.equal,
.not_equal,
.less_than,
.less_than_equal,
.greater_than,
.greater_than_equal,
.not,
.false_literal,
.true_literal,
=> return true,
else => return false,
}
}
};
pub const Payload = struct {
tag: Node.Tag,
pub const Value = struct {
base: Payload,
data: []const u8,
};
pub const UnOp = struct {
base: Payload,
data: Node,
};
pub const BinOp = struct {
base: Payload,
data: struct {
lhs: Node,
rhs: Node,
},
};
pub const Discard = struct {
base: Payload,
data: struct {
should_skip: bool,
value: Node,
},
};
pub const If = struct {
base: Payload,
data: struct {
cond: Node,
then: Node,
@"else": ?Node,
},
};
pub const While = struct {
base: Payload,
data: struct {
cond: Node,
body: Node,
cont_expr: ?Node,
},
};
pub const Switch = struct {
base: Payload,
data: struct {
cond: Node,
cases: []Node,
},
};
pub const BreakVal = struct {
base: Payload,
data: struct {
label: ?[]const u8,
val: Node,
},
};
pub const Call = struct {
base: Payload,
data: struct {
lhs: Node,
args: []Node,
},
};
pub const VarDecl = struct {
base: Payload,
data: struct {
is_pub: bool,
is_const: bool,
is_extern: bool,
is_export: bool,
is_threadlocal: bool,
alignment: ?c_uint,
linksection_string: ?[]const u8,
name: []const u8,
type: Node,
init: ?Node,
},
};
pub const Func = struct {
base: Payload,
data: struct {
is_pub: bool,
is_extern: bool,
is_export: bool,
is_inline: bool,
is_var_args: bool,
name: ?[]const u8,
linksection_string: ?[]const u8,
explicit_callconv: ?CallingConvention,
params: []Param,
return_type: Node,
body: ?Node,
alignment: ?c_uint,
},
pub const CallingConvention = enum {
c,
x86_64_sysv,
x86_64_win,
x86_stdcall,
x86_fastcall,
x86_thiscall,
x86_vectorcall,
x86_regcall,
aarch64_vfabi,
aarch64_sve_pcs,
arm_aapcs,
arm_aapcs_vfp,
m68k_rtd,
riscv_vector,
};
};
pub const Param = struct {
is_noalias: bool,
name: ?[]const u8,
type: Node,
};
pub const Container = struct {
base: Payload,
data: struct {
layout: enum { @"packed", @"extern", none },
fields: []Field,
decls: []Node,
},
pub const Field = struct {
name: []const u8,
type: Node,
alignment: ?c_uint,
default_value: ?Node,
};
};
pub const TupleInit = struct {
base: Payload,
data: []Node,
};
pub const ContainerInit = struct {
base: Payload,
data: struct {
lhs: Node,
inits: []Initializer,
},
pub const Initializer = struct {
name: []const u8,
value: Node,
};
};
pub const ContainerInitDot = struct {
base: Payload,
data: []Initializer,
pub const Initializer = struct {
name: []const u8,
value: Node,
};
};
pub const Block = struct {
base: Payload,
data: struct {
label: ?[]const u8,
stmts: []Node,
},
};
pub const Array = struct {
base: Payload,
data: ArrayTypeInfo,
pub const ArrayTypeInfo = struct {
elem_type: Node,
len: u64,
};
};
pub const Pointer = struct {
base: Payload,
data: struct {
elem_type: Node,
is_const: bool,
is_volatile: bool,
is_allowzero: bool,
},
};
pub const ArgRedecl = struct {
base: Payload,
data: struct {
actual: []const u8,
mangled: []const u8,
},
};
pub const SimpleVarDecl = struct {
base: Payload,
data: struct {
name: []const u8,
init: Node,
},
};
pub const EnumConstant = struct {
base: Payload,
data: struct {
name: []const u8,
is_public: bool,
type: ?Node,
value: Node,
},
};
pub const ArrayFiller = struct {
base: Payload,
data: struct {
type: Node,
filler: Node,
count: u64,
},
};
pub const PubInlineFn = struct {
base: Payload,
data: struct {
name: []const u8,
params: []Param,
return_type: Node,
body: Node,
},
};
pub const FieldAccess = struct {
base: Payload,
data: struct {
lhs: Node,
field_name: []const u8,
},
};
pub const StringSlice = struct {
base: Payload,
data: struct {
string: Node,
end: u64,
},
};
pub const Shuffle = struct {
base: Payload,
data: struct {
element_type: Node,
a: Node,
b: Node,
mask_vector: Node,
},
};
pub const Extern = struct {
base: Payload,
data: struct {
type: Node,
name: Node,
},
};
pub const HelperCall = struct {
base: Payload,
data: struct {
name: []const u8,
args: []const Node,
},
};
pub const HelperRef = struct {
base: Payload,
data: []const u8,
};
};
/// Converts the nodes into a Zig Ast.
/// Caller must free the source slice.
pub fn render(gpa: Allocator, nodes: []const Node) !std.zig.Ast {
var ctx: Context = .{
.gpa = gpa,
.buf = std.array_list.Managed(u8).init(gpa),
};
defer ctx.buf.deinit();
defer ctx.nodes.deinit(gpa);
defer ctx.extra_data.deinit(gpa);
defer ctx.tokens.deinit(gpa);
// Estimate that each top level node has 10 child nodes.
const estimated_node_count = nodes.len * 10 + 1; // +1 for the .root node
try ctx.nodes.ensureTotalCapacity(gpa, estimated_node_count);
// Estimate that each each node has 2 tokens.
const estimated_tokens_count = estimated_node_count * 2;
try ctx.tokens.ensureTotalCapacity(gpa, estimated_tokens_count);
// Estimate that each each token is 3 bytes long.
const estimated_buf_len = estimated_tokens_count * 3;
try ctx.buf.ensureTotalCapacity(estimated_buf_len);
ctx.nodes.appendAssumeCapacity(.{
.tag = .root,
.main_token = 0,
.data = undefined,
});
const root_members = blk: {
var result = std.array_list.Managed(NodeIndex).init(gpa);
defer result.deinit();
for (nodes) |node| {
const res = (try renderNodeOpt(&ctx, node)) orelse continue;
try result.append(res);
}
break :blk try ctx.listToSpan(result.items);
};
ctx.nodes.items(.data)[0] = .{ .extra_range = .{
.start = root_members.start,
.end = root_members.end,
} };
try ctx.tokens.append(gpa, .{
.tag = .eof,
.start = @as(u32, @intCast(ctx.buf.items.len)),
});
return .{
.source = try ctx.buf.toOwnedSliceSentinel(0),
.tokens = ctx.tokens.toOwnedSlice(),
.nodes = ctx.nodes.toOwnedSlice(),
.extra_data = try ctx.extra_data.toOwnedSlice(gpa),
.errors = &.{},
.mode = .zig,
};
}
const NodeIndex = std.zig.Ast.Node.Index;
const NodeSubRange = std.zig.Ast.Node.SubRange;
const TokenIndex = std.zig.Ast.TokenIndex;
const TokenTag = std.zig.Token.Tag;
const Context = struct {
gpa: Allocator,
buf: std.array_list.Managed(u8),
nodes: std.zig.Ast.NodeList = .{},
extra_data: std.ArrayListUnmanaged(u32) = .empty,
tokens: std.zig.Ast.TokenList = .{},
fn addTokenFmt(c: *Context, tag: TokenTag, comptime format: []const u8, args: anytype) Allocator.Error!TokenIndex {
const start_index = c.buf.items.len;
try c.buf.print(format ++ " ", args);
try c.tokens.append(c.gpa, .{
.tag = tag,
.start = @intCast(start_index),
});
return @intCast(c.tokens.len - 1);
}
fn addToken(c: *Context, tag: TokenTag, bytes: []const u8) Allocator.Error!TokenIndex {
return c.addTokenFmt(tag, "{s}", .{bytes});
}
fn addIdentifier(c: *Context, bytes: []const u8) Allocator.Error!TokenIndex {
if (std.zig.primitives.isPrimitive(bytes))
return c.addTokenFmt(.identifier, "@\"{s}\"", .{bytes});
return c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtId(bytes)});
}
fn listToSpan(c: *Context, list: []const NodeIndex) Allocator.Error!NodeSubRange {
try c.extra_data.appendSlice(c.gpa, @ptrCast(list));
return .{
.start = @enumFromInt(c.extra_data.items.len - list.len),
.end = @enumFromInt(c.extra_data.items.len),
};
}
fn addNode(c: *Context, elem: std.zig.Ast.Node) Allocator.Error!NodeIndex {
const result: NodeIndex = @enumFromInt(c.nodes.len);
try c.nodes.append(c.gpa, elem);
return result;
}
fn addExtra(c: *Context, extra: anytype) Allocator.Error!std.zig.Ast.ExtraIndex {
const fields = std.meta.fields(@TypeOf(extra));
try c.extra_data.ensureUnusedCapacity(c.gpa, fields.len);
const result: std.zig.Ast.ExtraIndex = @enumFromInt(c.extra_data.items.len);
inline for (fields) |field| {
const data: u32 = switch (field.type) {
NodeIndex,
std.zig.Ast.Node.OptionalIndex,
std.zig.Ast.OptionalTokenIndex,
std.zig.Ast.ExtraIndex,
=> @intFromEnum(@field(extra, field.name)),
TokenIndex,
=> @field(extra, field.name),
else => @compileError("unexpected field type"),
};
c.extra_data.appendAssumeCapacity(data);
}
return result;
}
};
fn renderNodeOpt(c: *Context, node: Node) Allocator.Error!?NodeIndex {
switch (node.tag()) {
.warning => {
const payload = node.castTag(.warning).?.data;
try c.buf.appendSlice(payload);
try c.buf.append('\n');
return null;
},
.discard => {
const payload = node.castTag(.discard).?.data;
if (payload.should_skip) return null;
return try renderNode(c, node);
},
else => return try renderNode(c, node),
}
}
fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
switch (node.tag()) {
.declaration => unreachable,
.warning => unreachable,
.discard => {
const payload = node.castTag(.discard).?.data;
std.debug.assert(!payload.should_skip);
const lhs = try c.addNode(.{
.tag = .identifier,
.main_token = try c.addToken(.identifier, "_"),
.data = undefined,
});
const main_token = try c.addToken(.equal, "=");
if (payload.value.tag() == .identifier) {
// Render as `_ = &foo;` to avoid tripping "pointless discard" and "local variable never mutated" errors.
var addr_of_pl: Payload.UnOp = .{
.base = .{ .tag = .address_of },
.data = payload.value,
};
const addr_of: Node = .{ .ptr_otherwise = &addr_of_pl.base };
return try c.addNode(.{
.tag = .assign,
.main_token = main_token,
.data = .{ .node_and_node = .{
lhs, try renderNode(c, addr_of),
} },
});
} else {
return try c.addNode(.{
.tag = .assign,
.main_token = main_token,
.data = .{ .node_and_node = .{
lhs, try renderNode(c, payload.value),
} },
});
}
},
.std_mem_zeroes => {
const payload = node.castTag(.std_mem_zeroes).?.data;
const import_node = try renderStdImport(c, &.{ "mem", "zeroes" });
return renderCall(c, import_node, &.{payload});
},
.std_mem_zeroinit => {
const payload = node.castTag(.std_mem_zeroinit).?.data;
const import_node = try renderStdImport(c, &.{ "mem", "zeroInit" });
return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
},
.vector => {
const payload = node.castTag(.vector).?.data;
return renderBuiltinCall(c, "@Vector", &.{ payload.lhs, payload.rhs });
},
.call => {
const payload = node.castTag(.call).?.data;
const lhs = try renderNodeGrouped(c, payload.lhs);
return renderCall(c, lhs, payload.args);
},
.null_literal => return c.addNode(.{
.tag = .identifier,
.main_token = try c.addToken(.identifier, "null"),
.data = undefined,
}),
.undefined_literal => return c.addNode(.{
.tag = .identifier,
.main_token = try c.addToken(.identifier, "undefined"),
.data = undefined,
}),
.true_literal => return c.addNode(.{
.tag = .identifier,
.main_token = try c.addToken(.identifier, "true"),
.data = undefined,
}),
.false_literal => return c.addNode(.{
.tag = .identifier,
.main_token = try c.addToken(.identifier, "false"),
.data = undefined,
}),
.zero_literal => return c.addNode(.{
.tag = .number_literal,
.main_token = try c.addToken(.number_literal, "0"),
.data = undefined,
}),
.one_literal => return c.addNode(.{
.tag = .number_literal,
.main_token = try c.addToken(.number_literal, "1"),
.data = undefined,
}),
.@"unreachable" => return c.addNode(.{
.tag = .unreachable_literal,
.main_token = try c.addToken(.keyword_unreachable, "unreachable"),
.data = undefined,
}),
.void_type => return c.addNode(.{
.tag = .identifier,
.main_token = try c.addToken(.identifier, "void"),
.data = undefined,
}),
.noreturn_type => return c.addNode(.{
.tag = .identifier,
.main_token = try c.addToken(.identifier, "noreturn"),
.data = undefined,
}),
.@"continue" => return c.addNode(.{
.tag = .@"continue",
.main_token = try c.addToken(.keyword_continue, "continue"),
.data = .{ .opt_token_and_opt_node = .{
.none, .none,
} },
}),
.return_void => return c.addNode(.{
.tag = .@"return",
.main_token = try c.addToken(.keyword_return, "return"),
.data = .{ .opt_node = .none },
}),
.@"break" => return c.addNode(.{
.tag = .@"break",
.main_token = try c.addToken(.keyword_break, "break"),
.data = .{ .opt_token_and_opt_node = .{
.none, .none,
} },
}),
.break_val => {
const payload = node.castTag(.break_val).?.data;
const tok = try c.addToken(.keyword_break, "break");
const break_label = if (payload.label) |some| blk: {
_ = try c.addToken(.colon, ":");
break :blk try c.addIdentifier(some);
} else 0;
return c.addNode(.{
.tag = .@"break",
.main_token = tok,
.data = .{ .opt_token_and_opt_node = .{
.fromToken(break_label), (try renderNode(c, payload.val)).toOptional(),
} },
});
},
.@"return" => {
const payload = node.castTag(.@"return").?.data;
return c.addNode(.{
.tag = .@"return",
.main_token = try c.addToken(.keyword_return, "return"),
.data = .{ .opt_node = (try renderNode(c, payload)).toOptional() },
});
},
.@"comptime" => {
const payload = node.castTag(.@"comptime").?.data;
return c.addNode(.{
.tag = .@"comptime",
.main_token = try c.addToken(.keyword_comptime, "comptime"),
.data = .{
.node = try renderNode(c, payload),
},
});
},
.@"defer" => {
const payload = node.castTag(.@"defer").?.data;
return c.addNode(.{
.tag = .@"defer",
.main_token = try c.addToken(.keyword_defer, "defer"),
.data = .{
.node = try renderNode(c, payload),
},
});
},
.asm_simple => {
const payload = node.castTag(.asm_simple).?.data;
const asm_token = try c.addToken(.keyword_asm, "asm");
_ = try c.addToken(.l_paren, "(");
return c.addNode(.{
.tag = .asm_simple,
.main_token = asm_token,
.data = .{ .node_and_token = .{
try renderNode(c, payload),
try c.addToken(.r_paren, ")"),
} },
});
},
.type => {
const payload = node.castTag(.type).?.data;
return c.addNode(.{
.tag = .identifier,
.main_token = try c.addToken(.identifier, payload),
.data = undefined,
});
},
.identifier => {
const payload = node.castTag(.identifier).?.data;
return c.addNode(.{
.tag = .identifier,
.main_token = try c.addIdentifier(payload),
.data = undefined,
});
},
.float_literal => {
const payload = node.castTag(.float_literal).?.data;
return c.addNode(.{
.tag = .number_literal,
.main_token = try c.addToken(.number_literal, payload),
.data = undefined,
});
},
.integer_literal => {
const payload = node.castTag(.integer_literal).?.data;
return c.addNode(.{
.tag = .number_literal,
.main_token = try c.addToken(.number_literal, payload),
.data = undefined,
});
},
.string_literal => {
const payload = node.castTag(.string_literal).?.data;
return c.addNode(.{
.tag = .string_literal,
.main_token = try c.addToken(.string_literal, payload),
.data = undefined,
});
},
.char_literal => {
const payload = node.castTag(.char_literal).?.data;
return c.addNode(.{
.tag = .char_literal,
.main_token = try c.addToken(.char_literal, payload),
.data = undefined,
});
},
.enum_literal => {
const payload = node.castTag(.enum_literal).?.data;
_ = try c.addToken(.period, ".");
return c.addNode(.{
.tag = .enum_literal,
.main_token = try c.addToken(.identifier, payload),
.data = undefined,
});
},
.string_slice => {
const payload = node.castTag(.string_slice).?.data;
const string = try renderNode(c, payload.string);
const l_bracket = try c.addToken(.l_bracket, "[");
const start = try c.addNode(.{
.tag = .number_literal,
.main_token = try c.addToken(.number_literal, "0"),
.data = undefined,
});
_ = try c.addToken(.ellipsis2, "..");
const end = try c.addNode(.{
.tag = .number_literal,
.main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.end}),
.data = undefined,
});
_ = try c.addToken(.r_bracket, "]");
return c.addNode(.{
.tag = .slice,
.main_token = l_bracket,
.data = .{ .node_and_extra = .{
string, try c.addExtra(std.zig.Ast.Node.Slice{
.start = start,
.end = end,
}),
} },
});
},
.fail_decl => {
const payload = node.castTag(.fail_decl).?.data;
// pub const name = @compileError(msg);
_ = try c.addToken(.keyword_pub, "pub");
const const_tok = try c.addToken(.keyword_const, "const");
_ = try c.addIdentifier(payload.actual);
_ = try c.addToken(.equal, "=");
const compile_error_tok = try c.addToken(.builtin, "@compileError");
_ = try c.addToken(.l_paren, "(");
const err_msg_tok = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(payload.mangled)});
const err_msg = try c.addNode(.{
.tag = .string_literal,
.main_token = err_msg_tok,
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
const compile_error = try c.addNode(.{
.tag = .builtin_call_two,
.main_token = compile_error_tok,
.data = .{ .opt_node_and_opt_node = .{
err_msg.toOptional(), .none,
} },
});
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = const_tok,
.data = .{
.opt_node_and_opt_node = .{
.none, // Type expression
compile_error.toOptional(), // Init expression
},
},
});
},
.pub_var_simple, .var_simple => {
const payload = @as(*Payload.SimpleVarDecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
if (node.tag() == .pub_var_simple) _ = try c.addToken(.keyword_pub, "pub");
const const_tok = try c.addToken(.keyword_const, "const");
_ = try c.addIdentifier(payload.name);
_ = try c.addToken(.equal, "=");
const init = try renderNode(c, payload.init);
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = const_tok,
.data = .{
.opt_node_and_opt_node = .{
.none, // Type expression
init.toOptional(), // Init expression
},
},
});
},
.wrapped_local => {
const payload = node.castTag(.wrapped_local).?.data;
const const_tok = try c.addToken(.keyword_const, "const");
_ = try c.addIdentifier(payload.name);
_ = try c.addToken(.equal, "=");
const kind_tok = try c.addToken(.keyword_struct, "struct");
_ = try c.addToken(.l_brace, "{");
const container_def = try c.addNode(.{
.tag = .container_decl_two_trailing,
.main_token = kind_tok,
.data = .{ .opt_node_and_opt_node = .{
(try renderNode(c, payload.init)).toOptional(), .none,
} },
});
_ = try c.addToken(.r_brace, "}");
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = const_tok,
.data = .{
.opt_node_and_opt_node = .{
.none, // Type expression
container_def.toOptional(), // Init expression
},
},
});
},
.mut_str => {
const payload = node.castTag(.mut_str).?.data;
const var_tok = try c.addToken(.keyword_var, "var");
_ = try c.addIdentifier(payload.name);
_ = try c.addToken(.equal, "=");
const deref = try c.addNode(.{
.tag = .deref,
.data = .{
.node = try renderNodeGrouped(c, payload.init),
},
.main_token = try c.addToken(.period_asterisk, ".*"),
});
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = var_tok,
.data = .{
.opt_node_and_opt_node = .{
.none, // Type expression
deref.toOptional(), // Init expression
},
},
});
},
.var_decl => return renderVar(c, node),
.arg_redecl, .alias => {
const payload = @as(*Payload.ArgRedecl, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
if (node.tag() == .alias) _ = try c.addToken(.keyword_pub, "pub");
const mut_tok = if (node.tag() == .alias)
try c.addToken(.keyword_const, "const")
else
try c.addToken(.keyword_var, "var");
_ = try c.addIdentifier(payload.actual);
_ = try c.addToken(.equal, "=");
const init = try c.addNode(.{
.tag = .identifier,
.main_token = try c.addIdentifier(payload.mangled),
.data = undefined,
});
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = mut_tok,
.data = .{
.opt_node_and_opt_node = .{
.none, // Type expression
init.toOptional(), // Init expression
},
},
});
},
.int_cast => {
const payload = node.castTag(.int_cast).?.data;
return renderBuiltinCall(c, "@intCast", &.{payload});
},
.const_cast => {
const payload = node.castTag(.const_cast).?.data;
return renderBuiltinCall(c, "@constCast", &.{payload});
},
.volatile_cast => {
const payload = node.castTag(.volatile_cast).?.data;
return renderBuiltinCall(c, "@volatileCast", &.{payload});
},
.div_trunc => {
const payload = node.castTag(.div_trunc).?.data;
return renderBuiltinCall(c, "@divTrunc", &.{ payload.lhs, payload.rhs });
},
.int_from_bool => {
const payload = node.castTag(.int_from_bool).?.data;
return renderBuiltinCall(c, "@intFromBool", &.{payload});
},
.as => {
const payload = node.castTag(.as).?.data;
return renderBuiltinCall(c, "@as", &.{ payload.lhs, payload.rhs });
},
.truncate => {
const payload = node.castTag(.truncate).?.data;
return renderBuiltinCall(c, "@truncate", &.{payload});
},
.bit_cast => {
const payload = node.castTag(.bit_cast).?.data;
return renderBuiltinCall(c, "@bitCast", &.{payload});
},
.float_cast => {
const payload = node.castTag(.float_cast).?.data;
return renderBuiltinCall(c, "@floatCast", &.{payload});
},
.int_from_float => {
const payload = node.castTag(.int_from_float).?.data;
return renderBuiltinCall(c, "@intFromFloat", &.{payload});
},
.float_from_int => {
const payload = node.castTag(.float_from_int).?.data;
return renderBuiltinCall(c, "@floatFromInt", &.{payload});
},
.ptr_from_int => {
const payload = node.castTag(.ptr_from_int).?.data;
return renderBuiltinCall(c, "@ptrFromInt", &.{payload});
},
.int_from_ptr => {
const payload = node.castTag(.int_from_ptr).?.data;
return renderBuiltinCall(c, "@intFromPtr", &.{payload});
},
.align_cast => {
const payload = node.castTag(.align_cast).?.data;
return renderBuiltinCall(c, "@alignCast", &.{payload});
},
.ptr_cast => {
const payload = node.castTag(.ptr_cast).?.data;
return renderBuiltinCall(c, "@ptrCast", &.{payload});
},
.div_exact => {
const payload = node.castTag(.div_exact).?.data;
return renderBuiltinCall(c, "@divExact", &.{ payload.lhs, payload.rhs });
},
.offset_of => {
const payload = node.castTag(.offset_of).?.data;
return renderBuiltinCall(c, "@offsetOf", &.{ payload.lhs, payload.rhs });
},
.sizeof => {
const payload = node.castTag(.sizeof).?.data;
return renderBuiltinCall(c, "@sizeOf", &.{payload});
},
.shuffle => {
const payload = node.castTag(.shuffle).?.data;
return renderBuiltinCall(c, "@shuffle", &.{
payload.element_type,
payload.a,
payload.b,
payload.mask_vector,
});
},
.builtin_extern => {
const payload = node.castTag(.builtin_extern).?.data;
var info_inits: [1]Payload.ContainerInitDot.Initializer = .{
.{ .name = "name", .value = payload.name },
};
var info_payload: Payload.ContainerInitDot = .{
.base = .{ .tag = .container_init_dot },
.data = &info_inits,
};
return renderBuiltinCall(c, "@extern", &.{
payload.type,
.{ .ptr_otherwise = &info_payload.base },
});
},
.helper_call => {
const payload = node.castTag(.helper_call).?.data;
const helpers_tok = try c.addNode(.{
.tag = .identifier,
.main_token = try c.addIdentifier("__helpers"),
.data = undefined,
});
const func = try renderFieldAccess(c, helpers_tok, payload.name);
return renderCall(c, func, payload.args);
},
.helper_ref => {
const payload = node.castTag(.helper_ref).?.data;
const helpers_tok = try c.addNode(.{
.tag = .identifier,
.main_token = try c.addIdentifier("__helpers"),
.data = undefined,
});
return renderFieldAccess(c, helpers_tok, payload);
},
.alignof => {
const payload = node.castTag(.alignof).?.data;
return renderBuiltinCall(c, "@alignOf", &.{payload});
},
.typeof => {
const payload = node.castTag(.typeof).?.data;
return renderBuiltinCall(c, "@TypeOf", &.{payload});
},
.typeinfo => {
const payload = node.castTag(.typeinfo).?.data;
return renderBuiltinCall(c, "@typeInfo", &.{payload});
},
.byte_swap => {
const payload = node.castTag(.byte_swap).?.data;
return renderBuiltinCall(c, "@byteSwap", &.{payload});
},
.ceil => {
const payload = node.castTag(.ceil).?.data;
return renderBuiltinCall(c, "@ceil", &.{payload});
},
.cos => {
const payload = node.castTag(.cos).?.data;
return renderBuiltinCall(c, "@cos", &.{payload});
},
.sin => {
const payload = node.castTag(.sin).?.data;
return renderBuiltinCall(c, "@sin", &.{payload});
},
.exp => {
const payload = node.castTag(.exp).?.data;
return renderBuiltinCall(c, "@exp", &.{payload});
},
.exp2 => {
const payload = node.castTag(.exp2).?.data;
return renderBuiltinCall(c, "@exp2", &.{payload});
},
.exp10 => {
const payload = node.castTag(.exp10).?.data;
return renderBuiltinCall(c, "@exp10", &.{payload});
},
.abs => {
const payload = node.castTag(.abs).?.data;
return renderBuiltinCall(c, "@abs", &.{payload});
},
.log => {
const payload = node.castTag(.log).?.data;
return renderBuiltinCall(c, "@log", &.{payload});
},
.log2 => {
const payload = node.castTag(.log2).?.data;
return renderBuiltinCall(c, "@log2", &.{payload});
},
.log10 => {
const payload = node.castTag(.log10).?.data;
return renderBuiltinCall(c, "@log10", &.{payload});
},
.round => {
const payload = node.castTag(.round).?.data;
return renderBuiltinCall(c, "@round", &.{payload});
},
.sqrt => {
const payload = node.castTag(.sqrt).?.data;
return renderBuiltinCall(c, "@sqrt", &.{payload});
},
.trunc => {
const payload = node.castTag(.trunc).?.data;
return renderBuiltinCall(c, "@trunc", &.{payload});
},
.floor => {
const payload = node.castTag(.floor).?.data;
return renderBuiltinCall(c, "@floor", &.{payload});
},
.negate => return renderPrefixOp(c, node, .negation, .minus, "-"),
.negate_wrap => return renderPrefixOp(c, node, .negation_wrap, .minus_percent, "-%"),
.bit_not => return renderPrefixOp(c, node, .bit_not, .tilde, "~"),
.not => return renderPrefixOp(c, node, .bool_not, .bang, "!"),
.optional_type => return renderPrefixOp(c, node, .optional_type, .question_mark, "?"),
.address_of => {
const payload = node.castTag(.address_of).?.data;
const ampersand = try c.addToken(.ampersand, "&");
const base = try renderNodeGrouped(c, payload);
return c.addNode(.{
.tag = .address_of,
.main_token = ampersand,
.data = .{
.node = base,
},
});
},
.deref => {
const payload = node.castTag(.deref).?.data;
const operand = try renderNodeGrouped(c, payload);
const deref_tok = try c.addToken(.period_asterisk, ".*");
return c.addNode(.{
.tag = .deref,
.main_token = deref_tok,
.data = .{
.node = operand,
},
});
},
.unwrap => {
const payload = node.castTag(.unwrap).?.data;
const operand = try renderNodeGrouped(c, payload);
const period = try c.addToken(.period, ".");
const question_mark = try c.addToken(.question_mark, "?");
return c.addNode(.{
.tag = .unwrap_optional,
.main_token = period,
.data = .{ .node_and_token = .{
operand, question_mark,
} },
});
},
.c_pointer, .single_pointer => {
const payload = @as(*Payload.Pointer, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
const main_token = if (node.tag() == .single_pointer)
try c.addToken(.asterisk, "*")
else blk: {
const res = try c.addToken(.l_bracket, "[");
_ = try c.addToken(.asterisk, "*");
_ = try c.addIdentifier("c");
_ = try c.addToken(.r_bracket, "]");
break :blk res;
};
if (payload.is_const) _ = try c.addToken(.keyword_const, "const");
if (payload.is_volatile) _ = try c.addToken(.keyword_volatile, "volatile");
if (payload.is_allowzero) _ = try c.addToken(.keyword_allowzero, "allowzero");
const elem_type = try renderNodeGrouped(c, payload.elem_type);
return c.addNode(.{
.tag = .ptr_type_aligned,
.main_token = main_token,
.data = .{
.opt_node_and_node = .{
.none, // Align node
elem_type,
},
},
});
},
.add => return renderBinOpGrouped(c, node, .add, .plus, "+"),
.add_assign => return renderBinOp(c, node, .assign_add, .plus_equal, "+="),
.add_wrap => return renderBinOpGrouped(c, node, .add_wrap, .plus_percent, "+%"),
.add_wrap_assign => return renderBinOp(c, node, .assign_add_wrap, .plus_percent_equal, "+%="),
.sub => return renderBinOpGrouped(c, node, .sub, .minus, "-"),
.sub_assign => return renderBinOp(c, node, .assign_sub, .minus_equal, "-="),
.sub_wrap => return renderBinOpGrouped(c, node, .sub_wrap, .minus_percent, "-%"),
.sub_wrap_assign => return renderBinOp(c, node, .assign_sub_wrap, .minus_percent_equal, "-%="),
.mul => return renderBinOpGrouped(c, node, .mul, .asterisk, "*"),
.mul_assign => return renderBinOp(c, node, .assign_mul, .asterisk_equal, "*="),
.mul_wrap => return renderBinOpGrouped(c, node, .mul_wrap, .asterisk_percent, "*%"),
.mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="),
.div => return renderBinOpGrouped(c, node, .div, .slash, "/"),
.div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="),
.shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"),
.shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="),
.shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"),
.shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="),
.mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"),
.mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="),
.@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"),
.@"or" => return renderBinOpGrouped(c, node, .bool_or, .keyword_or, "or"),
.less_than => return renderBinOpGrouped(c, node, .less_than, .angle_bracket_left, "<"),
.less_than_equal => return renderBinOpGrouped(c, node, .less_or_equal, .angle_bracket_left_equal, "<="),
.greater_than => return renderBinOpGrouped(c, node, .greater_than, .angle_bracket_right, ">="),
.greater_than_equal => return renderBinOpGrouped(c, node, .greater_or_equal, .angle_bracket_right_equal, ">="),
.equal => return renderBinOpGrouped(c, node, .equal_equal, .equal_equal, "=="),
.not_equal => return renderBinOpGrouped(c, node, .bang_equal, .bang_equal, "!="),
.bit_and => return renderBinOpGrouped(c, node, .bit_and, .ampersand, "&"),
.bit_and_assign => return renderBinOp(c, node, .assign_bit_and, .ampersand_equal, "&="),
.bit_or => return renderBinOpGrouped(c, node, .bit_or, .pipe, "|"),
.bit_or_assign => return renderBinOp(c, node, .assign_bit_or, .pipe_equal, "|="),
.bit_xor => return renderBinOpGrouped(c, node, .bit_xor, .caret, "^"),
.bit_xor_assign => return renderBinOp(c, node, .assign_bit_xor, .caret_equal, "^="),
.array_cat => return renderBinOp(c, node, .array_cat, .plus_plus, "++"),
.ellipsis3 => return renderBinOpGrouped(c, node, .switch_range, .ellipsis3, "..."),
.assign => return renderBinOp(c, node, .assign, .equal, "="),
.empty_block => {
const l_brace = try c.addToken(.l_brace, "{");
_ = try c.addToken(.r_brace, "}");
return c.addNode(.{
.tag = .block_two,
.main_token = l_brace,
.data = .{ .opt_node_and_opt_node = .{
.none, .none,
} },
});
},
.block_single => {
const payload = node.castTag(.block_single).?.data;
const l_brace = try c.addToken(.l_brace, "{");
const stmt = (try renderNodeOpt(c, payload)) orelse {
_ = try c.addToken(.r_brace, "}");
return c.addNode(.{
.tag = .block_two,
.main_token = l_brace,
.data = .{ .opt_node_and_opt_node = .{
.none, .none,
} },
});
};
try addSemicolonIfNeeded(c, payload);
_ = try c.addToken(.r_brace, "}");
return c.addNode(.{
.tag = .block_two_semicolon,
.main_token = l_brace,
.data = .{ .opt_node_and_opt_node = .{
stmt.toOptional(), .none,
} },
});
},
.block => {
const payload = node.castTag(.block).?.data;
if (payload.label) |some| {
_ = try c.addIdentifier(some);
_ = try c.addToken(.colon, ":");
}
const l_brace = try c.addToken(.l_brace, "{");
var stmts = std.array_list.Managed(NodeIndex).init(c.gpa);
defer stmts.deinit();
for (payload.stmts) |stmt| {
const res = (try renderNodeOpt(c, stmt)) orelse continue;
try addSemicolonIfNeeded(c, stmt);
try stmts.append(res);
}
const span = try c.listToSpan(stmts.items);
_ = try c.addToken(.r_brace, "}");
const semicolon = c.tokens.items(.tag)[c.tokens.len - 2] == .semicolon;
return c.addNode(.{
.tag = if (semicolon) .block_semicolon else .block,
.main_token = l_brace,
.data = .{ .extra_range = span },
});
},
.func => return renderFunc(c, node),
.pub_inline_fn => return renderMacroFunc(c, node),
.@"while" => {
const payload = node.castTag(.@"while").?.data;
const while_tok = try c.addToken(.keyword_while, "while");
_ = try c.addToken(.l_paren, "(");
const cond = try renderNode(c, payload.cond);
_ = try c.addToken(.r_paren, ")");
const cont_expr_opt = if (payload.cont_expr) |some| blk: {
_ = try c.addToken(.colon, ":");
_ = try c.addToken(.l_paren, "(");
const res = try renderNode(c, some);
_ = try c.addToken(.r_paren, ")");
break :blk res;
} else null;
const body = try renderNode(c, payload.body);
if (cont_expr_opt) |cont_expr| {
return c.addNode(.{
.tag = .while_cont,
.main_token = while_tok,
.data = .{ .node_and_extra = .{
cond,
try c.addExtra(std.zig.Ast.Node.WhileCont{
.cont_expr = cont_expr,
.then_expr = body,
}),
} },
});
} else {
return c.addNode(.{
.tag = .while_simple,
.main_token = while_tok,
.data = .{ .node_and_node = .{
cond, body,
} },
});
}
},
.while_true => {
const payload = node.castTag(.while_true).?.data;
const while_tok = try c.addToken(.keyword_while, "while");
_ = try c.addToken(.l_paren, "(");
const cond = try c.addNode(.{
.tag = .identifier,
.main_token = try c.addToken(.identifier, "true"),
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
const body = try renderNode(c, payload);
return c.addNode(.{
.tag = .while_simple,
.main_token = while_tok,
.data = .{ .node_and_node = .{
cond, body,
} },
});
},
.@"if" => {
const payload = node.castTag(.@"if").?.data;
const if_tok = try c.addToken(.keyword_if, "if");
_ = try c.addToken(.l_paren, "(");
const cond = try renderNode(c, payload.cond);
_ = try c.addToken(.r_paren, ")");
const then_expr = try renderNode(c, payload.then);
const else_node = payload.@"else" orelse return c.addNode(.{
.tag = .if_simple,
.main_token = if_tok,
.data = .{ .node_and_node = .{
cond, then_expr,
} },
});
_ = try c.addToken(.keyword_else, "else");
const else_expr = try renderNode(c, else_node);
return c.addNode(.{
.tag = .@"if",
.main_token = if_tok,
.data = .{ .node_and_extra = .{
cond,
try c.addExtra(std.zig.Ast.Node.If{
.then_expr = then_expr,
.else_expr = else_expr,
}),
} },
});
},
.if_not_break => {
const payload = node.castTag(.if_not_break).?.data;
const if_tok = try c.addToken(.keyword_if, "if");
_ = try c.addToken(.l_paren, "(");
const cond = try c.addNode(.{
.tag = .bool_not,
.main_token = try c.addToken(.bang, "!"),
.data = .{
.node = try renderNodeGrouped(c, payload),
},
});
_ = try c.addToken(.r_paren, ")");
const then_expr = try c.addNode(.{
.tag = .@"break",
.main_token = try c.addToken(.keyword_break, "break"),
.data = .{ .opt_token_and_opt_node = .{
.none, .none,
} },
});
return c.addNode(.{
.tag = .if_simple,
.main_token = if_tok,
.data = .{ .node_and_node = .{
cond, then_expr,
} },
});
},
.@"switch" => {
const payload = node.castTag(.@"switch").?.data;
const switch_tok = try c.addToken(.keyword_switch, "switch");
_ = try c.addToken(.l_paren, "(");
const cond = try renderNode(c, payload.cond);
_ = try c.addToken(.r_paren, ")");
_ = try c.addToken(.l_brace, "{");
var cases = try c.gpa.alloc(NodeIndex, payload.cases.len);
defer c.gpa.free(cases);
for (payload.cases, 0..) |case, i| {
cases[i] = try renderNode(c, case);
_ = try c.addToken(.comma, ",");
}
const span = try c.listToSpan(cases);
_ = try c.addToken(.r_brace, "}");
return c.addNode(.{
.tag = .switch_comma,
.main_token = switch_tok,
.data = .{ .node_and_extra = .{
cond,
try c.addExtra(NodeSubRange{
.start = span.start,
.end = span.end,
}),
} },
});
},
.switch_else => {
const payload = node.castTag(.switch_else).?.data;
_ = try c.addToken(.keyword_else, "else");
return c.addNode(.{
.tag = .switch_case_one,
.main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
.data = .{ .opt_node_and_node = .{
.none, try renderNode(c, payload),
} },
});
},
.switch_prong => {
const payload = node.castTag(.switch_prong).?.data;
var items = try c.gpa.alloc(NodeIndex, payload.cases.len);
defer c.gpa.free(items);
for (payload.cases, 0..) |item, i| {
if (i != 0) _ = try c.addToken(.comma, ",");
items[i] = try renderNode(c, item);
}
_ = try c.addToken(.r_brace, "}");
if (items.len < 2) {
return c.addNode(.{
.tag = .switch_case_one,
.main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
.data = .{ .opt_node_and_node = .{
if (payload.cases.len == 1) items[0].toOptional() else .none,
try renderNode(c, payload.cond),
} },
});
} else {
return c.addNode(.{
.tag = .switch_case,
.main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
.data = .{ .extra_and_node = .{
try c.addExtra(try c.listToSpan(items)),
try renderNode(c, payload.cond),
} },
});
}
},
.opaque_literal => {
const opaque_tok = try c.addToken(.keyword_opaque, "opaque");
_ = try c.addToken(.l_brace, "{");
_ = try c.addToken(.r_brace, "}");
return c.addNode(.{
.tag = .container_decl_two,
.main_token = opaque_tok,
.data = .{ .opt_node_and_opt_node = .{
.none, .none,
} },
});
},
.array_access => {
const payload = node.castTag(.array_access).?.data;
const lhs = try renderNodeGrouped(c, payload.lhs);
const l_bracket = try c.addToken(.l_bracket, "[");
const index_expr = try renderNode(c, payload.rhs);
_ = try c.addToken(.r_bracket, "]");
return c.addNode(.{
.tag = .array_access,
.main_token = l_bracket,
.data = .{ .node_and_node = .{
lhs, index_expr,
} },
});
},
.array_type => {
const payload = node.castTag(.array_type).?.data;
return renderArrayType(c, payload.len, payload.elem_type);
},
.null_sentinel_array_type => {
const payload = node.castTag(.null_sentinel_array_type).?.data;
return renderNullSentinelArrayType(c, payload.len, payload.elem_type);
},
.array_filler => {
const payload = node.castTag(.array_filler).?.data;
const type_expr = try renderArrayType(c, 1, payload.type);
const l_brace = try c.addToken(.l_brace, "{");
const val = try renderNode(c, payload.filler);
_ = try c.addToken(.r_brace, "}");
const init = try c.addNode(.{
.tag = .array_init_one,
.main_token = l_brace,
.data = .{ .node_and_node = .{
type_expr, val,
} },
});
return c.addNode(.{
.tag = .array_cat,
.main_token = try c.addToken(.asterisk_asterisk, "**"),
.data = .{ .node_and_node = .{
init,
try c.addNode(.{
.tag = .number_literal,
.main_token = try c.addTokenFmt(.number_literal, "{d}", .{payload.count}),
.data = undefined,
}),
} },
});
},
.empty_array => {
const payload = node.castTag(.empty_array).?.data;
const type_expr = try renderNode(c, payload);
return renderArrayInit(c, type_expr, &.{});
},
.array_init => {
const payload = node.castTag(.array_init).?.data;
const type_expr = try renderNode(c, payload.cond);
return renderArrayInit(c, type_expr, payload.cases);
},
.vector_zero_init => {
const payload = node.castTag(.vector_zero_init).?.data;
return renderBuiltinCall(c, "@splat", &.{payload});
},
.field_access => {
const payload = node.castTag(.field_access).?.data;
const lhs = try renderNodeGrouped(c, payload.lhs);
return renderFieldAccess(c, lhs, payload.field_name);
},
.@"struct", .@"union", .@"opaque" => return renderContainer(c, node),
.enum_constant => {
const payload = node.castTag(.enum_constant).?.data;
if (payload.is_public) _ = try c.addToken(.keyword_pub, "pub");
const const_tok = try c.addToken(.keyword_const, "const");
_ = try c.addIdentifier(payload.name);
const type_node_opt = if (payload.type) |enum_const_type| blk: {
_ = try c.addToken(.colon, ":");
break :blk try renderNode(c, enum_const_type);
} else null;
_ = try c.addToken(.equal, "=");
const init_node = try renderNode(c, payload.value);
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = const_tok,
.data = .{ .opt_node_and_opt_node = .{
.fromOptional(type_node_opt),
init_node.toOptional(),
} },
});
},
.tuple => {
const payload = node.castTag(.tuple).?.data;
_ = try c.addToken(.period, ".");
const l_brace = try c.addToken(.l_brace, "{");
var inits = try c.gpa.alloc(NodeIndex, payload.len);
defer c.gpa.free(inits);
for (payload, 0..) |init, i| {
if (i != 0) _ = try c.addToken(.comma, ",");
inits[i] = try renderNode(c, init);
}
_ = try c.addToken(.r_brace, "}");
if (payload.len < 3) {
return c.addNode(.{
.tag = .array_init_dot_two,
.main_token = l_brace,
.data = .{ .opt_node_and_opt_node = .{
if (inits.len >= 1) inits[0].toOptional() else .none,
if (inits.len >= 2) inits[1].toOptional() else .none,
} },
});
} else {
return c.addNode(.{
.tag = .array_init_dot,
.main_token = l_brace,
.data = .{ .extra_range = try c.listToSpan(inits) },
});
}
},
.container_init_dot => {
const payload = node.castTag(.container_init_dot).?.data;
_ = try c.addToken(.period, ".");
const l_brace = try c.addToken(.l_brace, "{");
var inits = try c.gpa.alloc(NodeIndex, payload.len);
defer c.gpa.free(inits);
for (payload, 0..) |init, i| {
_ = try c.addToken(.period, ".");
_ = try c.addIdentifier(init.name);
_ = try c.addToken(.equal, "=");
inits[i] = try renderNode(c, init.value);
_ = try c.addToken(.comma, ",");
}
_ = try c.addToken(.r_brace, "}");
if (payload.len < 3) {
return c.addNode(.{
.tag = .struct_init_dot_two_comma,
.main_token = l_brace,
.data = .{ .opt_node_and_opt_node = .{
if (inits.len >= 1) inits[0].toOptional() else .none,
if (inits.len >= 2) inits[1].toOptional() else .none,
} },
});
} else {
return c.addNode(.{
.tag = .struct_init_dot_comma,
.main_token = l_brace,
.data = .{ .extra_range = try c.listToSpan(inits) },
});
}
},
.container_init => {
const payload = node.castTag(.container_init).?.data;
const lhs = try renderNode(c, payload.lhs);
const l_brace = try c.addToken(.l_brace, "{");
var inits = try c.gpa.alloc(NodeIndex, payload.inits.len);
defer c.gpa.free(inits);
for (payload.inits, 0..) |init, i| {
_ = try c.addToken(.period, ".");
_ = try c.addIdentifier(init.name);
_ = try c.addToken(.equal, "=");
inits[i] = try renderNode(c, init.value);
_ = try c.addToken(.comma, ",");
}
_ = try c.addToken(.r_brace, "}");
switch (inits.len) {
0 => return c.addNode(.{
.tag = .struct_init_one,
.main_token = l_brace,
.data = .{ .node_and_opt_node = .{
lhs, .none,
} },
}),
1 => return c.addNode(.{
.tag = .struct_init_one_comma,
.main_token = l_brace,
.data = .{ .node_and_opt_node = .{
lhs, inits[0].toOptional(),
} },
}),
else => return c.addNode(.{
.tag = .struct_init_comma,
.main_token = l_brace,
.data = .{ .node_and_extra = .{
lhs,
try c.addExtra(try c.listToSpan(inits)),
} },
}),
}
},
.static_assert => {
const payload = node.castTag(.static_assert).?.data;
const comptime_tok = try c.addToken(.keyword_comptime, "comptime");
const l_brace = try c.addToken(.l_brace, "{");
const if_tok = try c.addToken(.keyword_if, "if");
_ = try c.addToken(.l_paren, "(");
const cond = try c.addNode(.{
.tag = .bool_not,
.main_token = try c.addToken(.bang, "!"),
.data = .{
.node = try renderNodeGrouped(c, payload.lhs),
},
});
_ = try c.addToken(.r_paren, ")");
const compile_error_tok = try c.addToken(.builtin, "@compileError");
_ = try c.addToken(.l_paren, "(");
const err_msg = try renderNode(c, payload.rhs);
_ = try c.addToken(.r_paren, ")");
const compile_error = try c.addNode(.{
.tag = .builtin_call_two,
.main_token = compile_error_tok,
.data = .{ .opt_node_and_opt_node = .{
err_msg.toOptional(), .none,
} },
});
const if_node = try c.addNode(.{
.tag = .if_simple,
.main_token = if_tok,
.data = .{ .node_and_node = .{
cond, compile_error,
} },
});
_ = try c.addToken(.semicolon, ";");
_ = try c.addToken(.r_brace, "}");
const block_node = try c.addNode(.{
.tag = .block_two_semicolon,
.main_token = l_brace,
.data = .{ .opt_node_and_opt_node = .{
if_node.toOptional(), .none,
} },
});
return c.addNode(.{
.tag = .@"comptime",
.main_token = comptime_tok,
.data = .{
.node = block_node,
},
});
},
.@"anytype" => unreachable, // Handled in renderParams
}
}
fn renderContainer(c: *Context, node: Node) !NodeIndex {
const payload = @as(*Payload.Container, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
if (payload.layout == .@"packed")
_ = try c.addToken(.keyword_packed, "packed")
else if (payload.layout == .@"extern")
_ = try c.addToken(.keyword_extern, "extern");
const kind_tok = if (node.tag() == .@"struct")
try c.addToken(.keyword_struct, "struct")
else if (node.tag() == .@"union")
try c.addToken(.keyword_union, "union")
else if (node.tag() == .@"opaque")
try c.addToken(.keyword_opaque, "opaque")
else
unreachable;
_ = try c.addToken(.l_brace, "{");
const num_decls = payload.decls.len;
const total_members = payload.fields.len + num_decls;
const members = try c.gpa.alloc(NodeIndex, total_members);
defer c.gpa.free(members);
for (payload.fields, 0..) |field, i| {
const name_tok = try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field.name, .{ .allow_primitive = true })});
_ = try c.addToken(.colon, ":");
const type_expr = try renderNode(c, field.type);
const align_expr_opt = if (field.alignment) |alignment| blk: {
_ = try c.addToken(.keyword_align, "align");
_ = try c.addToken(.l_paren, "(");
const align_expr = try c.addNode(.{
.tag = .number_literal,
.main_token = try c.addTokenFmt(.number_literal, "{d}", .{alignment}),
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
break :blk align_expr;
} else null;
const value_expr_opt = if (field.default_value) |value| blk: {
_ = try c.addToken(.equal, "=");
break :blk try renderNode(c, value);
} else null;
if (align_expr_opt) |align_expr| {
if (value_expr_opt) |value_expr| {
members[i] = try c.addNode(.{
.tag = .container_field,
.main_token = name_tok,
.data = .{ .node_and_extra = .{
type_expr,
try c.addExtra(std.zig.Ast.Node.ContainerField{
.align_expr = align_expr,
.value_expr = value_expr,
}),
} },
});
} else {
members[i] = try c.addNode(.{
.tag = .container_field_align,
.main_token = name_tok,
.data = .{ .node_and_node = .{
type_expr,
align_expr,
} },
});
}
} else {
members[i] = try c.addNode(.{
.tag = .container_field_init,
.main_token = name_tok,
.data = .{ .node_and_opt_node = .{
type_expr,
.fromOptional(value_expr_opt),
} },
});
}
_ = try c.addToken(.comma, ",");
}
for (members[payload.fields.len..], payload.decls) |*member, decl| {
member.* = try renderNode(c, decl);
}
const trailing = switch (c.tokens.items(.tag)[c.tokens.len - 1]) {
.comma, .semicolon => true,
else => false,
};
_ = try c.addToken(.r_brace, "}");
if (total_members == 0) {
return c.addNode(.{
.tag = .container_decl_two,
.main_token = kind_tok,
.data = .{ .opt_node_and_opt_node = .{
.none, .none,
} },
});
} else if (total_members <= 2) {
return c.addNode(.{
.tag = if (trailing) .container_decl_two_trailing else .container_decl_two,
.main_token = kind_tok,
.data = .{ .opt_node_and_opt_node = .{
if (members.len >= 1) members[0].toOptional() else .none,
if (members.len >= 2) members[1].toOptional() else .none,
} },
});
} else {
const span = try c.listToSpan(members);
return c.addNode(.{
.tag = if (trailing) .container_decl_trailing else .container_decl,
.main_token = kind_tok,
.data = .{ .extra_range = span },
});
}
}
fn renderFieldAccess(c: *Context, lhs: NodeIndex, field_name: []const u8) !NodeIndex {
return c.addNode(.{
.tag = .field_access,
.main_token = try c.addToken(.period, "."),
.data = .{ .node_and_token = .{
lhs, try c.addTokenFmt(.identifier, "{f}", .{std.zig.fmtIdFlags(field_name, .{ .allow_primitive = true })}),
} },
});
}
fn renderArrayInit(c: *Context, lhs: NodeIndex, inits: []const Node) !NodeIndex {
const l_brace = try c.addToken(.l_brace, "{");
var rendered = try c.gpa.alloc(NodeIndex, inits.len);
defer c.gpa.free(rendered);
for (inits, 0..) |init, i| {
rendered[i] = try renderNode(c, init);
_ = try c.addToken(.comma, ",");
}
_ = try c.addToken(.r_brace, "}");
switch (inits.len) {
0 => return c.addNode(.{
.tag = .struct_init_one,
.main_token = l_brace,
.data = .{ .node_and_opt_node = .{
lhs, .none,
} },
}),
1 => return c.addNode(.{
.tag = .array_init_one_comma,
.main_token = l_brace,
.data = .{ .node_and_node = .{
lhs, rendered[0],
} },
}),
else => return c.addNode(.{
.tag = .array_init_comma,
.main_token = l_brace,
.data = .{ .node_and_extra = .{
lhs,
try c.addExtra(try c.listToSpan(rendered)),
} },
}),
}
}
fn renderArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex {
const l_bracket = try c.addToken(.l_bracket, "[");
const len_expr = try c.addNode(.{
.tag = .number_literal,
.main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}),
.data = undefined,
});
_ = try c.addToken(.r_bracket, "]");
const elem_type_expr = try renderNode(c, elem_type);
return c.addNode(.{
.tag = .array_type,
.main_token = l_bracket,
.data = .{ .node_and_node = .{
len_expr, elem_type_expr,
} },
});
}
fn renderNullSentinelArrayType(c: *Context, len: u64, elem_type: Node) !NodeIndex {
const l_bracket = try c.addToken(.l_bracket, "[");
const len_expr = try c.addNode(.{
.tag = .number_literal,
.main_token = try c.addTokenFmt(.number_literal, "{d}", .{len}),
.data = undefined,
});
_ = try c.addToken(.colon, ":");
const sentinel_expr = try c.addNode(.{
.tag = .number_literal,
.main_token = try c.addToken(.number_literal, "0"),
.data = undefined,
});
_ = try c.addToken(.r_bracket, "]");
const elem_type_expr = try renderNode(c, elem_type);
return c.addNode(.{
.tag = .array_type_sentinel,
.main_token = l_bracket,
.data = .{ .node_and_extra = .{
len_expr,
try c.addExtra(std.zig.Ast.Node.ArrayTypeSentinel{
.sentinel = sentinel_expr,
.elem_type = elem_type_expr,
}),
} },
});
}
fn addSemicolonIfNeeded(c: *Context, node: Node) !void {
switch (node.tag()) {
.warning => unreachable,
.var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch", .wrapped_local, .mut_str => {},
.while_true => {
const payload = node.castTag(.while_true).?.data;
return addSemicolonIfNotBlock(c, payload);
},
.@"while" => {
const payload = node.castTag(.@"while").?.data;
return addSemicolonIfNotBlock(c, payload.body);
},
.@"if" => {
const payload = node.castTag(.@"if").?.data;
if (payload.@"else") |some|
return addSemicolonIfNeeded(c, some);
return addSemicolonIfNotBlock(c, payload.then);
},
else => _ = try c.addToken(.semicolon, ";"),
}
}
fn addSemicolonIfNotBlock(c: *Context, node: Node) !void {
switch (node.tag()) {
.block, .empty_block, .block_single => {},
else => _ = try c.addToken(.semicolon, ";"),
}
}
fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
switch (node.tag()) {
.declaration => unreachable,
.null_literal,
.undefined_literal,
.true_literal,
.false_literal,
.return_void,
.zero_literal,
.one_literal,
.void_type,
.noreturn_type,
.@"anytype",
.div_trunc,
.int_cast,
.const_cast,
.volatile_cast,
.as,
.truncate,
.bit_cast,
.float_cast,
.int_from_float,
.float_from_int,
.ptr_from_int,
.std_mem_zeroes,
.int_from_ptr,
.sizeof,
.alignof,
.typeof,
.typeinfo,
.vector,
.std_mem_zeroinit,
.integer_literal,
.float_literal,
.string_literal,
.string_slice,
.char_literal,
.enum_literal,
.identifier,
.field_access,
.ptr_cast,
.type,
.array_access,
.align_cast,
.optional_type,
.c_pointer,
.single_pointer,
.unwrap,
.deref,
.not,
.negate,
.negate_wrap,
.bit_not,
.func,
.call,
.array_type,
.null_sentinel_array_type,
.int_from_bool,
.div_exact,
.offset_of,
.shuffle,
.builtin_extern,
.wrapped_local,
.mut_str,
.helper_call,
.helper_ref,
.byte_swap,
.ceil,
.cos,
.sin,
.exp,
.exp2,
.exp10,
.abs,
.log,
.log2,
.log10,
.round,
.sqrt,
.trunc,
.floor,
=> {
// no grouping needed
return renderNode(c, node);
},
.opaque_literal,
.@"opaque",
.empty_array,
.block_single,
.add,
.add_wrap,
.sub,
.sub_wrap,
.mul,
.mul_wrap,
.div,
.shl,
.shr,
.mod,
.@"and",
.@"or",
.less_than,
.less_than_equal,
.greater_than,
.greater_than_equal,
.equal,
.not_equal,
.bit_and,
.bit_or,
.bit_xor,
.empty_block,
.array_cat,
.array_filler,
.@"if",
.@"struct",
.@"union",
.array_init,
.vector_zero_init,
.tuple,
.container_init,
.container_init_dot,
.block,
.address_of,
=> return c.addNode(.{
.tag = .grouped_expression,
.main_token = try c.addToken(.l_paren, "("),
.data = .{ .node_and_token = .{
try renderNode(c, node),
try c.addToken(.r_paren, ")"),
} },
}),
.ellipsis3,
.switch_prong,
.warning,
.var_decl,
.fail_decl,
.arg_redecl,
.alias,
.var_simple,
.pub_var_simple,
.enum_constant,
.@"while",
.@"switch",
.@"break",
.break_val,
.pub_inline_fn,
.discard,
.@"continue",
.@"return",
.@"comptime",
.@"defer",
.asm_simple,
.while_true,
.if_not_break,
.switch_else,
.add_assign,
.add_wrap_assign,
.sub_assign,
.sub_wrap_assign,
.mul_assign,
.mul_wrap_assign,
.div_assign,
.shl_assign,
.shr_assign,
.mod_assign,
.bit_and_assign,
.bit_or_assign,
.bit_xor_assign,
.assign,
.static_assert,
.@"unreachable",
=> {
// these should never appear in places where grouping might be needed.
unreachable;
},
}
}
fn renderPrefixOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex {
const payload = @as(*Payload.UnOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
return c.addNode(.{
.tag = tag,
.main_token = try c.addToken(tok_tag, bytes),
.data = .{
.node = try renderNodeGrouped(c, payload),
},
});
}
fn renderBinOpGrouped(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex {
const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
const lhs = try renderNodeGrouped(c, payload.lhs);
return c.addNode(.{
.tag = tag,
.main_token = try c.addToken(tok_tag, bytes),
.data = .{ .node_and_node = .{
lhs, try renderNodeGrouped(c, payload.rhs),
} },
});
}
fn renderBinOp(c: *Context, node: Node, tag: std.zig.Ast.Node.Tag, tok_tag: TokenTag, bytes: []const u8) !NodeIndex {
const payload = @as(*Payload.BinOp, @alignCast(@fieldParentPtr("base", node.ptr_otherwise))).data;
const lhs = try renderNode(c, payload.lhs);
return c.addNode(.{
.tag = tag,
.main_token = try c.addToken(tok_tag, bytes),
.data = .{ .node_and_node = .{
lhs, try renderNode(c, payload.rhs),
} },
});
}
fn renderStdImport(c: *Context, parts: []const []const u8) !NodeIndex {
const import_tok = try c.addToken(.builtin, "@import");
_ = try c.addToken(.l_paren, "(");
const std_tok = try c.addToken(.string_literal, "\"std\"");
const std_node = try c.addNode(.{
.tag = .string_literal,
.main_token = std_tok,
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
const import_node = try c.addNode(.{
.tag = .builtin_call_two,
.main_token = import_tok,
.data = .{ .opt_node_and_opt_node = .{
std_node.toOptional(), .none,
} },
});
var access_chain = import_node;
for (parts) |part| {
access_chain = try renderFieldAccess(c, access_chain, part);
}
return access_chain;
}
fn renderCall(c: *Context, lhs: NodeIndex, args: []const Node) !NodeIndex {
const lparen = try c.addToken(.l_paren, "(");
const res = switch (args.len) {
0 => try c.addNode(.{
.tag = .call_one,
.main_token = lparen,
.data = .{ .node_and_opt_node = .{
lhs, .none,
} },
}),
1 => try c.addNode(.{
.tag = .call_one,
.main_token = lparen,
.data = .{ .node_and_opt_node = .{
lhs, (try renderNode(c, args[0])).toOptional(),
} },
}),
else => blk: {
var rendered = try c.gpa.alloc(NodeIndex, args.len);
defer c.gpa.free(rendered);
for (args, 0..) |arg, i| {
if (i != 0) _ = try c.addToken(.comma, ",");
rendered[i] = try renderNode(c, arg);
}
const span = try c.listToSpan(rendered);
break :blk try c.addNode(.{
.tag = .call,
.main_token = lparen,
.data = .{ .node_and_extra = .{
lhs, try c.addExtra(NodeSubRange{
.start = span.start,
.end = span.end,
}),
} },
});
},
};
_ = try c.addToken(.r_paren, ")");
return res;
}
fn renderBuiltinCall(c: *Context, builtin: []const u8, args: []const Node) !NodeIndex {
const builtin_tok = try c.addToken(.builtin, builtin);
_ = try c.addToken(.l_paren, "(");
var arg_1: ?NodeIndex = null;
var arg_2: ?NodeIndex = null;
var arg_3: ?NodeIndex = null;
var arg_4: ?NodeIndex = null;
switch (args.len) {
0 => {},
1 => {
arg_1 = try renderNode(c, args[0]);
},
2 => {
arg_1 = try renderNode(c, args[0]);
_ = try c.addToken(.comma, ",");
arg_2 = try renderNode(c, args[1]);
},
4 => {
arg_1 = try renderNode(c, args[0]);
_ = try c.addToken(.comma, ",");
arg_2 = try renderNode(c, args[1]);
_ = try c.addToken(.comma, ",");
arg_3 = try renderNode(c, args[2]);
_ = try c.addToken(.comma, ",");
arg_4 = try renderNode(c, args[3]);
},
else => unreachable, // expand this function as needed.
}
_ = try c.addToken(.r_paren, ")");
if (args.len <= 2) {
return c.addNode(.{
.tag = .builtin_call_two,
.main_token = builtin_tok,
.data = .{ .opt_node_and_opt_node = .{
.fromOptional(arg_1), .fromOptional(arg_2),
} },
});
} else {
std.debug.assert(args.len == 4);
const params = try c.listToSpan(&.{ arg_1.?, arg_2.?, arg_3.?, arg_4.? });
return c.addNode(.{
.tag = .builtin_call,
.main_token = builtin_tok,
.data = .{ .extra_range = .{
.start = params.start,
.end = params.end,
} },
});
}
}
fn renderVar(c: *Context, node: Node) !NodeIndex {
const payload = node.castTag(.var_decl).?.data;
if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub");
if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern");
if (payload.is_export) _ = try c.addToken(.keyword_export, "export");
if (payload.is_threadlocal) _ = try c.addToken(.keyword_threadlocal, "threadlocal");
const mut_tok = if (payload.is_const)
try c.addToken(.keyword_const, "const")
else
try c.addToken(.keyword_var, "var");
_ = try c.addIdentifier(payload.name);
_ = try c.addToken(.colon, ":");
const type_node = try renderNode(c, payload.type);
const align_node_opt = if (payload.alignment) |some| blk: {
_ = try c.addToken(.keyword_align, "align");
_ = try c.addToken(.l_paren, "(");
const res = try c.addNode(.{
.tag = .number_literal,
.main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}),
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
break :blk res;
} else null;
const section_node_opt = if (payload.linksection_string) |some| blk: {
_ = try c.addToken(.keyword_linksection, "linksection");
_ = try c.addToken(.l_paren, "(");
const res = try c.addNode(.{
.tag = .string_literal,
.main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}),
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
break :blk res;
} else null;
const init_node_opt = if (payload.init) |some| blk: {
_ = try c.addToken(.equal, "=");
break :blk try renderNode(c, some);
} else null;
_ = try c.addToken(.semicolon, ";");
if (section_node_opt) |section_node| {
return c.addNode(.{
.tag = .global_var_decl,
.main_token = mut_tok,
.data = .{ .extra_and_opt_node = .{
try c.addExtra(std.zig.Ast.Node.GlobalVarDecl{
.type_node = type_node.toOptional(),
.align_node = .fromOptional(align_node_opt),
.section_node = section_node.toOptional(),
.addrspace_node = .none,
}),
.fromOptional(init_node_opt),
} },
});
} else {
if (align_node_opt) |align_node| {
return c.addNode(.{
.tag = .local_var_decl,
.main_token = mut_tok,
.data = .{ .extra_and_opt_node = .{
try c.addExtra(std.zig.Ast.Node.LocalVarDecl{
.type_node = type_node,
.align_node = align_node,
}),
.fromOptional(init_node_opt),
} },
});
} else {
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = mut_tok,
.data = .{
.opt_node_and_opt_node = .{
type_node.toOptional(), // Type expression
.fromOptional(init_node_opt), // Init expression
},
},
});
}
}
}
fn renderFunc(c: *Context, node: Node) !NodeIndex {
const payload = node.castTag(.func).?.data;
if (payload.is_pub) _ = try c.addToken(.keyword_pub, "pub");
if (payload.is_extern) _ = try c.addToken(.keyword_extern, "extern");
if (payload.is_export) _ = try c.addToken(.keyword_export, "export");
if (payload.is_inline) _ = try c.addToken(.keyword_inline, "inline");
const fn_token = try c.addToken(.keyword_fn, "fn");
if (payload.name) |some| _ = try c.addIdentifier(some);
const params = try renderParams(c, payload.params, payload.is_var_args);
defer params.deinit();
var span: NodeSubRange = undefined;
if (params.items.len > 1) span = try c.listToSpan(params.items);
const align_expr_opt = if (payload.alignment) |some| blk: {
_ = try c.addToken(.keyword_align, "align");
_ = try c.addToken(.l_paren, "(");
const res = try c.addNode(.{
.tag = .number_literal,
.main_token = try c.addTokenFmt(.number_literal, "{d}", .{some}),
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
break :blk res;
} else null;
const section_expr_opt = if (payload.linksection_string) |some| blk: {
_ = try c.addToken(.keyword_linksection, "linksection");
_ = try c.addToken(.l_paren, "(");
const res = try c.addNode(.{
.tag = .string_literal,
.main_token = try c.addTokenFmt(.string_literal, "\"{f}\"", .{std.zig.fmtString(some)}),
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
break :blk res;
} else null;
const callconv_expr_opt = if (payload.explicit_callconv) |some| blk: {
_ = try c.addToken(.keyword_callconv, "callconv");
_ = try c.addToken(.l_paren, "(");
const cc_node = switch (some) {
.c => cc_node: {
_ = try c.addToken(.period, ".");
break :cc_node try c.addNode(.{
.tag = .enum_literal,
.main_token = try c.addToken(.identifier, "c"),
.data = undefined,
});
},
.x86_64_sysv,
.x86_64_win,
.x86_stdcall,
.x86_fastcall,
.x86_thiscall,
.x86_vectorcall,
.x86_regcall,
.aarch64_vfabi,
.aarch64_sve_pcs,
.arm_aapcs,
.arm_aapcs_vfp,
.m68k_rtd,
.riscv_vector,
=> cc_node: {
// .{ .foo = .{} }
_ = try c.addToken(.period, ".");
const outer_lbrace = try c.addToken(.l_brace, "{");
_ = try c.addToken(.period, ".");
_ = try c.addToken(.identifier, @tagName(some));
_ = try c.addToken(.equal, "=");
_ = try c.addToken(.period, ".");
const inner_lbrace = try c.addToken(.l_brace, "{");
_ = try c.addToken(.r_brace, "}");
_ = try c.addToken(.r_brace, "}");
break :cc_node try c.addNode(.{
.tag = .struct_init_dot_two,
.main_token = outer_lbrace,
.data = .{ .opt_node_and_opt_node = .{
(try c.addNode(.{
.tag = .struct_init_dot_two,
.main_token = inner_lbrace,
.data = .{ .opt_node_and_opt_node = .{
.none, .none,
} },
})).toOptional(),
.none,
} },
});
},
};
_ = try c.addToken(.r_paren, ")");
break :blk cc_node;
} else null;
const return_type_expr = try renderNode(c, payload.return_type);
const fn_proto = try blk: {
if (align_expr_opt == null and section_expr_opt == null and callconv_expr_opt == null) {
if (params.items.len < 2)
break :blk c.addNode(.{
.tag = .fn_proto_simple,
.main_token = fn_token,
.data = .{ .opt_node_and_opt_node = .{
if (params.items.len == 1) params.items[0].toOptional() else .none,
return_type_expr.toOptional(),
} },
})
else
break :blk c.addNode(.{
.tag = .fn_proto_multi,
.main_token = fn_token,
.data = .{ .extra_and_opt_node = .{
try c.addExtra(span),
return_type_expr.toOptional(),
} },
});
}
if (params.items.len < 2)
break :blk c.addNode(.{
.tag = .fn_proto_one,
.main_token = fn_token,
.data = .{
.extra_and_opt_node = .{
try c.addExtra(std.zig.Ast.Node.FnProtoOne{
.param = if (params.items.len == 1) params.items[0].toOptional() else .none,
.align_expr = .fromOptional(align_expr_opt),
.addrspace_expr = .none, // TODO
.section_expr = .fromOptional(section_expr_opt),
.callconv_expr = .fromOptional(callconv_expr_opt),
}),
return_type_expr.toOptional(),
},
},
})
else
break :blk c.addNode(.{
.tag = .fn_proto,
.main_token = fn_token,
.data = .{
.extra_and_opt_node = .{
try c.addExtra(std.zig.Ast.Node.FnProto{
.params_start = span.start,
.params_end = span.end,
.align_expr = .fromOptional(align_expr_opt),
.addrspace_expr = .none, // TODO
.section_expr = .fromOptional(section_expr_opt),
.callconv_expr = .fromOptional(callconv_expr_opt),
}),
return_type_expr.toOptional(),
},
},
});
};
const payload_body = payload.body orelse {
if (payload.is_extern) {
_ = try c.addToken(.semicolon, ";");
}
return fn_proto;
};
const body = try renderNode(c, payload_body);
return c.addNode(.{
.tag = .fn_decl,
.main_token = fn_token,
.data = .{ .node_and_node = .{
fn_proto, body,
} },
});
}
fn renderMacroFunc(c: *Context, node: Node) !NodeIndex {
const payload = node.castTag(.pub_inline_fn).?.data;
_ = try c.addToken(.keyword_pub, "pub");
_ = try c.addToken(.keyword_inline, "inline");
const fn_token = try c.addToken(.keyword_fn, "fn");
_ = try c.addIdentifier(payload.name);
const params = try renderParams(c, payload.params, false);
defer params.deinit();
var span: NodeSubRange = undefined;
if (params.items.len > 1) span = try c.listToSpan(params.items);
const return_type_expr = try renderNodeGrouped(c, payload.return_type);
const fn_proto = blk: {
if (params.items.len < 2) {
break :blk try c.addNode(.{
.tag = .fn_proto_simple,
.main_token = fn_token,
.data = .{ .opt_node_and_opt_node = .{
if (params.items.len == 1) params.items[0].toOptional() else .none,
return_type_expr.toOptional(),
} },
});
} else {
break :blk try c.addNode(.{
.tag = .fn_proto_multi,
.main_token = fn_token,
.data = .{ .extra_and_opt_node = .{
try c.addExtra(span),
return_type_expr.toOptional(),
} },
});
}
};
return c.addNode(.{
.tag = .fn_decl,
.main_token = fn_token,
.data = .{ .node_and_node = .{
fn_proto, try renderNode(c, payload.body),
} },
});
}
fn renderParams(c: *Context, params: []Payload.Param, is_var_args: bool) !std.array_list.Managed(NodeIndex) {
_ = try c.addToken(.l_paren, "(");
var rendered = try std.array_list.Managed(NodeIndex).initCapacity(c.gpa, @max(params.len, 1));
errdefer rendered.deinit();
for (params, 0..) |param, i| {
if (i != 0) _ = try c.addToken(.comma, ",");
if (param.is_noalias) _ = try c.addToken(.keyword_noalias, "noalias");
if (param.name) |some| {
_ = try c.addIdentifier(some);
_ = try c.addToken(.colon, ":");
}
if (param.type.tag() == .@"anytype") {
_ = try c.addToken(.keyword_anytype, "anytype");
continue;
}
rendered.appendAssumeCapacity(try renderNode(c, param.type));
}
if (is_var_args) {
if (params.len != 0) _ = try c.addToken(.comma, ",");
_ = try c.addToken(.ellipsis3, "...");
}
_ = try c.addToken(.r_paren, ")");
return rendered;
}