zig/lib/compiler/aro_translate_c/ast.zig

2976 lines
99 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,
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,
fn_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 }
static_local_var,
/// const ExternLocal_name = struct { init }
extern_local_var,
/// var name = init.*
mut_str,
func,
warning,
@"struct",
@"union",
@"comptime",
@"defer",
array_init,
tuple,
container_init,
container_init_dot,
helpers_cast,
/// _ = 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,
/// @import("std").zig.c_builtins.<name>
import_c_builtin,
/// @intCast(operand)
int_cast,
/// @constCast(operand)
const_cast,
/// @volatileCast(operand)
volatile_cast,
/// @import("std").zig.c_translation.promoteIntLiteral(value, type, base)
helpers_promoteIntLiteral,
/// @import("std").zig.c_translation.signedRemainder(lhs, rhs)
signed_remainder,
/// @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,
/// @import("std").zig.c_translation.MacroArithmetic.<op>(lhs, rhs)
macro_arithmetic,
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,
/// @import("std").zig.c_translation.sizeof(operand)
helpers_sizeof,
/// @import("std").zig.c_translation.FlexibleArrayType(lhs, rhs)
helpers_flexible_array_type,
/// @import("std").zig.c_translation.shuffleVectorIndex(lhs, rhs)
helpers_shuffle_vector_index,
/// @import("std").zig.c_translation.Macro.<operand>
helpers_macro,
/// @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,
/// [0]type{}
empty_array,
/// [1]type{val} ** count
array_filler,
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",
=> @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,
.helpers_sizeof,
.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,
=> 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,
.signed_remainder,
.as,
.array_cat,
.ellipsis3,
.assign,
.array_access,
.std_mem_zeroinit,
.helpers_flexible_array_type,
.helpers_shuffle_vector_index,
.vector,
.div_exact,
.offset_of,
.helpers_cast,
=> Payload.BinOp,
.integer_literal,
.float_literal,
.string_literal,
.char_literal,
.enum_literal,
.identifier,
.fn_identifier,
.warning,
.type,
.helpers_macro,
.import_c_builtin,
=> 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" => Payload.Record,
.tuple => Payload.TupleInit,
.container_init => Payload.ContainerInit,
.container_init_dot => Payload.ContainerInitDot,
.helpers_promoteIntLiteral => Payload.PromoteIntLiteral,
.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, .static_local_var, .extern_local_var, .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,
.macro_arithmetic => Payload.MacroArithmetic,
};
}
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 @as(Tag, @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 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: ?std.builtin.CallingConvention,
params: []Param,
return_type: Node,
body: ?Node,
alignment: ?c_uint,
},
};
pub const Param = struct {
is_noalias: bool,
name: ?[]const u8,
type: Node,
};
pub const Record = struct {
base: Payload,
data: struct {
layout: enum { @"packed", @"extern", none },
fields: []Field,
functions: []Node,
variables: []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: usize,
};
};
pub const Pointer = struct {
base: Payload,
data: struct {
elem_type: Node,
is_const: bool,
is_volatile: 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: usize,
},
};
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 PromoteIntLiteral = struct {
base: Payload,
data: struct {
value: Node,
type: Node,
base: Node,
},
};
pub const StringSlice = struct {
base: Payload,
data: struct {
string: Node,
end: usize,
},
};
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 MacroArithmetic = struct {
base: Payload,
data: struct {
op: Operator,
lhs: Node,
rhs: Node,
},
pub const Operator = enum { div, rem };
};
};
/// 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.ArrayList(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;
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 = .{
.lhs = undefined,
.rhs = undefined,
},
});
const root_members = blk: {
var result = std.ArrayList(NodeIndex).init(gpa);
defer result.deinit();
for (nodes) |node| {
const res = try renderNode(&ctx, node);
if (node.tag() == .warning) continue;
try result.append(res);
}
break :blk try ctx.listToSpan(result.items);
};
ctx.nodes.items(.data)[0] = .{
.lhs = root_members.start,
.rhs = root_members.end,
};
try ctx.tokens.append(gpa, .{
.tag = .eof,
.start = @as(u32, @intCast(ctx.buf.items.len)),
});
return std.zig.Ast{
.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.ArrayList(u8),
nodes: std.zig.Ast.NodeList = .{},
extra_data: std.ArrayListUnmanaged(std.zig.Ast.Node.Index) = .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.writer().print(format ++ " ", args);
try c.tokens.append(c.gpa, .{
.tag = tag,
.start = @as(u32, @intCast(start_index)),
});
return @as(u32, @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, "{p}", .{std.zig.fmtId(bytes)});
}
fn listToSpan(c: *Context, list: []const NodeIndex) Allocator.Error!NodeSubRange {
try c.extra_data.appendSlice(c.gpa, list);
return NodeSubRange{
.start = @as(NodeIndex, @intCast(c.extra_data.items.len - list.len)),
.end = @as(NodeIndex, @intCast(c.extra_data.items.len)),
};
}
fn addNode(c: *Context, elem: std.zig.Ast.Node) Allocator.Error!NodeIndex {
const result = @as(NodeIndex, @intCast(c.nodes.len));
try c.nodes.append(c.gpa, elem);
return result;
}
fn addExtra(c: *Context, extra: anytype) Allocator.Error!NodeIndex {
const fields = std.meta.fields(@TypeOf(extra));
try c.extra_data.ensureUnusedCapacity(c.gpa, fields.len);
const result = @as(u32, @intCast(c.extra_data.items.len));
inline for (fields) |field| {
comptime std.debug.assert(field.type == NodeIndex);
c.extra_data.appendAssumeCapacity(@field(extra, field.name));
}
return result;
}
};
fn renderNodes(c: *Context, nodes: []const Node) Allocator.Error!NodeSubRange {
var result = std.ArrayList(NodeIndex).init(c.gpa);
defer result.deinit();
for (nodes) |node| {
const res = try renderNode(c, node);
if (node.tag() == .warning) continue;
try result.append(res);
}
return try c.listToSpan(result.items);
}
fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
switch (node.tag()) {
.declaration => unreachable,
.warning => {
const payload = node.castTag(.warning).?.data;
try c.buf.append('\n');
try c.buf.appendSlice(payload);
try c.buf.append('\n');
return @as(NodeIndex, 0); // error: integer value 0 cannot be coerced to type 'std.mem.Allocator.Error!u32'
},
.helpers_cast => {
const payload = node.castTag(.helpers_cast).?.data;
const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "cast" });
return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
},
.helpers_promoteIntLiteral => {
const payload = node.castTag(.helpers_promoteIntLiteral).?.data;
const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "promoteIntLiteral" });
return renderCall(c, import_node, &.{ payload.type, payload.value, payload.base });
},
.helpers_sizeof => {
const payload = node.castTag(.helpers_sizeof).?.data;
const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "sizeof" });
return renderCall(c, import_node, &.{payload});
},
.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 });
},
.helpers_flexible_array_type => {
const payload = node.castTag(.helpers_flexible_array_type).?.data;
const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "FlexibleArrayType" });
return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
},
.helpers_shuffle_vector_index => {
const payload = node.castTag(.helpers_shuffle_vector_index).?.data;
const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "shuffleVectorIndex" });
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;
// Cosmetic: avoids an unnecesary address_of on most function calls.
const lhs = if (payload.lhs.tag() == .fn_identifier)
try c.addNode(.{
.tag = .identifier,
.main_token = try c.addIdentifier(payload.lhs.castTag(.fn_identifier).?.data),
.data = undefined,
})
else
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,
}),
.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 = .{
.lhs = 0,
.rhs = undefined,
},
}),
.return_void => return c.addNode(.{
.tag = .@"return",
.main_token = try c.addToken(.keyword_return, "return"),
.data = .{
.lhs = 0,
.rhs = undefined,
},
}),
.@"break" => return c.addNode(.{
.tag = .@"break",
.main_token = try c.addToken(.keyword_break, "break"),
.data = .{
.lhs = 0,
.rhs = 0,
},
}),
.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 = .{
.lhs = break_label,
.rhs = try renderNode(c, payload.val),
},
});
},
.@"return" => {
const payload = node.castTag(.@"return").?.data;
return c.addNode(.{
.tag = .@"return",
.main_token = try c.addToken(.keyword_return, "return"),
.data = .{
.lhs = try renderNode(c, payload),
.rhs = undefined,
},
});
},
.@"comptime" => {
const payload = node.castTag(.@"comptime").?.data;
return c.addNode(.{
.tag = .@"comptime",
.main_token = try c.addToken(.keyword_comptime, "comptime"),
.data = .{
.lhs = try renderNode(c, payload),
.rhs = undefined,
},
});
},
.@"defer" => {
const payload = node.castTag(.@"defer").?.data;
return c.addNode(.{
.tag = .@"defer",
.main_token = try c.addToken(.keyword_defer, "defer"),
.data = .{
.lhs = undefined,
.rhs = 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 = .{
.lhs = try renderNode(c, payload),
.rhs = 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,
});
},
.fn_identifier => {
// C semantics are that a function identifier has address
// value (implicit in stage1, explicit in stage2), except in
// the context of an address_of, which is handled there.
const payload = node.castTag(.fn_identifier).?.data;
const tok = try c.addToken(.ampersand, "&");
const arg = try c.addNode(.{
.tag = .identifier,
.main_token = try c.addIdentifier(payload),
.data = undefined,
});
return c.addNode(.{
.tag = .address_of,
.main_token = tok,
.data = .{
.lhs = arg,
.rhs = 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,
});
},
.helpers_macro => {
const payload = node.castTag(.helpers_macro).?.data;
const chain = [_][]const u8{
"zig",
"c_translation",
"Macros",
payload,
};
return renderStdImport(c, &chain);
},
.import_c_builtin => {
const payload = node.castTag(.import_c_builtin).?.data;
const chain = [_][]const u8{
"zig",
"c_builtins",
payload,
};
return renderStdImport(c, &chain);
},
.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 = .{
.lhs = string,
.rhs = 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, "\"{}\"", .{std.zig.fmtEscapes(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 = .{
.lhs = err_msg,
.rhs = 0,
},
});
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = const_tok,
.data = .{
.lhs = 0,
.rhs = compile_error,
},
});
},
.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 = .{
.lhs = 0,
.rhs = init,
},
});
},
.static_local_var => {
const payload = node.castTag(.static_local_var).?.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 = .{
.lhs = try renderNode(c, payload.init),
.rhs = 0,
},
});
_ = try c.addToken(.r_brace, "}");
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = const_tok,
.data = .{
.lhs = 0,
.rhs = container_def,
},
});
},
.extern_local_var => {
const payload = node.castTag(.extern_local_var).?.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 = .{
.lhs = try renderNode(c, payload.init),
.rhs = 0,
},
});
_ = try c.addToken(.r_brace, "}");
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = const_tok,
.data = .{
.lhs = 0,
.rhs = container_def,
},
});
},
.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 = .{
.lhs = try renderNodeGrouped(c, payload.init),
.rhs = undefined,
},
.main_token = try c.addToken(.period_asterisk, ".*"),
});
_ = try c.addToken(.semicolon, ";");
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = var_tok,
.data = .{ .lhs = 0, .rhs = deref },
});
},
.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 = .{
.lhs = 0,
.rhs = init,
},
});
},
.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});
},
.signed_remainder => {
const payload = node.castTag(.signed_remainder).?.data;
const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "signedRemainder" });
return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
},
.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 },
});
},
.macro_arithmetic => {
const payload = node.castTag(.macro_arithmetic).?.data;
const op = @tagName(payload.op);
const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "MacroArithmetic", op });
return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
},
.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});
},
.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 = if (payload.tag() == .fn_identifier)
try c.addNode(.{
.tag = .identifier,
.main_token = try c.addIdentifier(payload.castTag(.fn_identifier).?.data),
.data = undefined,
})
else
try renderNodeGrouped(c, payload);
return c.addNode(.{
.tag = .address_of,
.main_token = ampersand,
.data = .{
.lhs = base,
.rhs = undefined,
},
});
},
.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 = .{
.lhs = operand,
.rhs = undefined,
},
});
},
.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 = .{
.lhs = operand,
.rhs = 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");
const elem_type = try renderNodeGrouped(c, payload.elem_type);
return c.addNode(.{
.tag = .ptr_type_aligned,
.main_token = main_token,
.data = .{
.lhs = 0,
.rhs = 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 = .{
.lhs = 0,
.rhs = 0,
},
});
},
.block_single => {
const payload = node.castTag(.block_single).?.data;
const l_brace = try c.addToken(.l_brace, "{");
const stmt = try renderNode(c, payload);
try addSemicolonIfNeeded(c, payload);
_ = try c.addToken(.r_brace, "}");
return c.addNode(.{
.tag = .block_two_semicolon,
.main_token = l_brace,
.data = .{
.lhs = stmt,
.rhs = 0,
},
});
},
.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.ArrayList(NodeIndex).init(c.gpa);
defer stmts.deinit();
for (payload.stmts) |stmt| {
const res = try renderNode(c, stmt);
if (res == 0) 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 = .{
.lhs = span.start,
.rhs = span.end,
},
});
},
.func => return renderFunc(c, node),
.pub_inline_fn => return renderMacroFunc(c, node),
.discard => {
const payload = node.castTag(.discard).?.data;
if (payload.should_skip) return @as(NodeIndex, 0);
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 c.addNode(.{
.tag = .assign,
.main_token = main_token,
.data = .{
.lhs = lhs,
.rhs = try renderNode(c, addr_of),
},
});
} else {
return c.addNode(.{
.tag = .assign,
.main_token = main_token,
.data = .{
.lhs = lhs,
.rhs = try renderNode(c, payload.value),
},
});
}
},
.@"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 = 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 0;
const body = try renderNode(c, payload.body);
if (cont_expr == 0) {
return c.addNode(.{
.tag = .while_simple,
.main_token = while_tok,
.data = .{
.lhs = cond,
.rhs = body,
},
});
} else {
return c.addNode(.{
.tag = .while_cont,
.main_token = while_tok,
.data = .{
.lhs = cond,
.rhs = try c.addExtra(std.zig.Ast.Node.WhileCont{
.cont_expr = cont_expr,
.then_expr = 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 = .{
.lhs = cond,
.rhs = 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 = .{
.lhs = cond,
.rhs = 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 = .{
.lhs = cond,
.rhs = 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 = .{
.lhs = try renderNodeGrouped(c, payload),
.rhs = undefined,
},
});
_ = try c.addToken(.r_paren, ")");
const then_expr = try c.addNode(.{
.tag = .@"break",
.main_token = try c.addToken(.keyword_break, "break"),
.data = .{
.lhs = 0,
.rhs = 0,
},
});
return c.addNode(.{
.tag = .if_simple,
.main_token = if_tok,
.data = .{
.lhs = cond,
.rhs = 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 = .{
.lhs = cond,
.rhs = 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 = .{
.lhs = 0,
.rhs = try renderNode(c, payload),
},
});
},
.switch_prong => {
const payload = node.castTag(.switch_prong).?.data;
var items = try c.gpa.alloc(NodeIndex, @max(payload.cases.len, 1));
defer c.gpa.free(items);
items[0] = 0;
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 = .{
.lhs = items[0],
.rhs = try renderNode(c, payload.cond),
},
});
} else {
const span = try c.listToSpan(items);
return c.addNode(.{
.tag = .switch_case,
.main_token = try c.addToken(.equal_angle_bracket_right, "=>"),
.data = .{
.lhs = try c.addExtra(NodeSubRange{
.start = span.start,
.end = span.end,
}),
.rhs = 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 = .{
.lhs = 0,
.rhs = 0,
},
});
},
.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 = .{
.lhs = lhs,
.rhs = 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 = .{
.lhs = type_expr,
.rhs = val,
},
});
return c.addNode(.{
.tag = .array_cat,
.main_token = try c.addToken(.asterisk_asterisk, "**"),
.data = .{
.lhs = init,
.rhs = 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 renderArrayType(c, 0, 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" => return renderRecord(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 = if (payload.type) |enum_const_type| blk: {
_ = try c.addToken(.colon, ":");
break :blk try renderNode(c, enum_const_type);
} else 0;
_ = 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 = .{
.lhs = type_node,
.rhs = init_node,
},
});
},
.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, @max(payload.len, 2));
defer c.gpa.free(inits);
inits[0] = 0;
inits[1] = 0;
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 = .{
.lhs = inits[0],
.rhs = inits[1],
},
});
} else {
const span = try c.listToSpan(inits);
return c.addNode(.{
.tag = .array_init_dot,
.main_token = l_brace,
.data = .{
.lhs = span.start,
.rhs = span.end,
},
});
}
},
.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, @max(payload.len, 2));
defer c.gpa.free(inits);
inits[0] = 0;
inits[1] = 0;
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 = .{
.lhs = inits[0],
.rhs = inits[1],
},
});
} else {
const span = try c.listToSpan(inits);
return c.addNode(.{
.tag = .struct_init_dot_comma,
.main_token = l_brace,
.data = .{
.lhs = span.start,
.rhs = span.end,
},
});
}
},
.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, @max(payload.inits.len, 1));
defer c.gpa.free(inits);
inits[0] = 0;
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, "}");
return switch (payload.inits.len) {
0 => c.addNode(.{
.tag = .struct_init_one,
.main_token = l_brace,
.data = .{
.lhs = lhs,
.rhs = 0,
},
}),
1 => c.addNode(.{
.tag = .struct_init_one_comma,
.main_token = l_brace,
.data = .{
.lhs = lhs,
.rhs = inits[0],
},
}),
else => blk: {
const span = try c.listToSpan(inits);
break :blk c.addNode(.{
.tag = .struct_init_comma,
.main_token = l_brace,
.data = .{
.lhs = lhs,
.rhs = try c.addExtra(NodeSubRange{
.start = span.start,
.end = span.end,
}),
},
});
},
};
},
.@"anytype" => unreachable, // Handled in renderParams
}
}
fn renderRecord(c: *Context, node: Node) !NodeIndex {
const payload = @as(*Payload.Record, @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
try c.addToken(.keyword_union, "union");
_ = try c.addToken(.l_brace, "{");
const num_vars = payload.variables.len;
const num_funcs = payload.functions.len;
const total_members = payload.fields.len + num_vars + num_funcs;
const members = try c.gpa.alloc(NodeIndex, @max(total_members, 2));
defer c.gpa.free(members);
members[0] = 0;
members[1] = 0;
for (payload.fields, 0..) |field, i| {
const name_tok = try c.addTokenFmt(.identifier, "{p}", .{std.zig.fmtId(field.name)});
_ = try c.addToken(.colon, ":");
const type_expr = try renderNode(c, field.type);
const align_expr = 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 0;
const value_expr = if (field.default_value) |value| blk: {
_ = try c.addToken(.equal, "=");
break :blk try renderNode(c, value);
} else 0;
members[i] = try c.addNode(if (align_expr == 0) .{
.tag = .container_field_init,
.main_token = name_tok,
.data = .{
.lhs = type_expr,
.rhs = value_expr,
},
} else if (value_expr == 0) .{
.tag = .container_field_align,
.main_token = name_tok,
.data = .{
.lhs = type_expr,
.rhs = align_expr,
},
} else .{
.tag = .container_field,
.main_token = name_tok,
.data = .{
.lhs = type_expr,
.rhs = try c.addExtra(std.zig.Ast.Node.ContainerField{
.align_expr = align_expr,
.value_expr = value_expr,
}),
},
});
_ = try c.addToken(.comma, ",");
}
for (payload.variables, 0..) |variable, i| {
members[payload.fields.len + i] = try renderNode(c, variable);
}
for (payload.functions, 0..) |function, i| {
members[payload.fields.len + num_vars + i] = try renderNode(c, function);
}
_ = try c.addToken(.r_brace, "}");
if (total_members == 0) {
return c.addNode(.{
.tag = .container_decl_two,
.main_token = kind_tok,
.data = .{
.lhs = 0,
.rhs = 0,
},
});
} else if (total_members <= 2) {
return c.addNode(.{
.tag = if (num_funcs == 0) .container_decl_two_trailing else .container_decl_two,
.main_token = kind_tok,
.data = .{
.lhs = members[0],
.rhs = members[1],
},
});
} else {
const span = try c.listToSpan(members);
return c.addNode(.{
.tag = if (num_funcs == 0) .container_decl_trailing else .container_decl,
.main_token = kind_tok,
.data = .{
.lhs = span.start,
.rhs = span.end,
},
});
}
}
fn renderFieldAccess(c: *Context, lhs: NodeIndex, field_name: []const u8) !NodeIndex {
return c.addNode(.{
.tag = .field_access,
.main_token = try c.addToken(.period, "."),
.data = .{
.lhs = lhs,
.rhs = try c.addTokenFmt(.identifier, "{p}", .{std.zig.fmtId(field_name)}),
},
});
}
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, @max(inits.len, 1));
defer c.gpa.free(rendered);
rendered[0] = 0;
for (inits, 0..) |init, i| {
rendered[i] = try renderNode(c, init);
_ = try c.addToken(.comma, ",");
}
_ = try c.addToken(.r_brace, "}");
if (inits.len < 2) {
return c.addNode(.{
.tag = .array_init_one_comma,
.main_token = l_brace,
.data = .{
.lhs = lhs,
.rhs = rendered[0],
},
});
} else {
const span = try c.listToSpan(rendered);
return c.addNode(.{
.tag = .array_init_comma,
.main_token = l_brace,
.data = .{
.lhs = lhs,
.rhs = try c.addExtra(NodeSubRange{
.start = span.start,
.end = span.end,
}),
},
});
}
}
fn renderArrayType(c: *Context, len: usize, 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 = .{
.lhs = len_expr,
.rhs = elem_type_expr,
},
});
}
fn renderNullSentinelArrayType(c: *Context, len: usize, 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 = .{
.lhs = len_expr,
.rhs = 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", .static_local_var, .extern_local_var, .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,
.signed_remainder,
.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,
.helpers_sizeof,
.helpers_cast,
.helpers_promoteIntLiteral,
.helpers_shuffle_vector_index,
.helpers_flexible_array_type,
.std_mem_zeroinit,
.integer_literal,
.float_literal,
.string_literal,
.string_slice,
.char_literal,
.enum_literal,
.identifier,
.fn_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,
.static_local_var,
.extern_local_var,
.mut_str,
.macro_arithmetic,
=> {
// no grouping needed
return renderNode(c, node);
},
.opaque_literal,
.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 = .{
.lhs = try renderNode(c, node),
.rhs = 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,
.helpers_macro,
.import_c_builtin,
=> {
// 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 = .{
.lhs = try renderNodeGrouped(c, payload),
.rhs = undefined,
},
});
}
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 = .{
.lhs = lhs,
.rhs = 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 = .{
.lhs = lhs,
.rhs = 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 = .{
.lhs = std_node,
.rhs = 0,
},
});
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 = .{
.lhs = lhs,
.rhs = 0,
},
}),
1 => blk: {
const arg = try renderNode(c, args[0]);
break :blk try c.addNode(.{
.tag = .call_one,
.main_token = lparen,
.data = .{
.lhs = lhs,
.rhs = arg,
},
});
},
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 = .{
.lhs = lhs,
.rhs = 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 = 0;
var arg_2: NodeIndex = 0;
var arg_3: NodeIndex = 0;
var arg_4: NodeIndex = 0;
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 = .{
.lhs = arg_1,
.rhs = 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 = .{
.lhs = params.start,
.rhs = 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 = 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 0;
const section_node = 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, "\"{}\"", .{std.zig.fmtEscapes(some)}),
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
break :blk res;
} else 0;
const init_node = if (payload.init) |some| blk: {
_ = try c.addToken(.equal, "=");
break :blk try renderNode(c, some);
} else 0;
_ = try c.addToken(.semicolon, ";");
if (section_node == 0) {
if (align_node == 0) {
return c.addNode(.{
.tag = .simple_var_decl,
.main_token = mut_tok,
.data = .{
.lhs = type_node,
.rhs = init_node,
},
});
} else {
return c.addNode(.{
.tag = .local_var_decl,
.main_token = mut_tok,
.data = .{
.lhs = try c.addExtra(std.zig.Ast.Node.LocalVarDecl{
.type_node = type_node,
.align_node = align_node,
}),
.rhs = init_node,
},
});
}
} else {
return c.addNode(.{
.tag = .global_var_decl,
.main_token = mut_tok,
.data = .{
.lhs = try c.addExtra(std.zig.Ast.Node.GlobalVarDecl{
.type_node = type_node,
.align_node = align_node,
.section_node = section_node,
.addrspace_node = 0,
}),
.rhs = init_node,
},
});
}
}
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 = 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 0;
const section_expr = 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, "\"{}\"", .{std.zig.fmtEscapes(some)}),
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
break :blk res;
} else 0;
const callconv_expr = if (payload.explicit_callconv) |some| blk: {
_ = try c.addToken(.keyword_callconv, "callconv");
_ = try c.addToken(.l_paren, "(");
_ = try c.addToken(.period, ".");
const res = try c.addNode(.{
.tag = .enum_literal,
.main_token = try c.addTokenFmt(.identifier, "{s}", .{@tagName(some)}),
.data = undefined,
});
_ = try c.addToken(.r_paren, ")");
break :blk res;
} else 0;
const return_type_expr = try renderNode(c, payload.return_type);
const fn_proto = try blk: {
if (align_expr == 0 and section_expr == 0 and callconv_expr == 0) {
if (params.items.len < 2)
break :blk c.addNode(.{
.tag = .fn_proto_simple,
.main_token = fn_token,
.data = .{
.lhs = params.items[0],
.rhs = return_type_expr,
},
})
else
break :blk c.addNode(.{
.tag = .fn_proto_multi,
.main_token = fn_token,
.data = .{
.lhs = try c.addExtra(NodeSubRange{
.start = span.start,
.end = span.end,
}),
.rhs = return_type_expr,
},
});
}
if (params.items.len < 2)
break :blk c.addNode(.{
.tag = .fn_proto_one,
.main_token = fn_token,
.data = .{
.lhs = try c.addExtra(std.zig.Ast.Node.FnProtoOne{
.param = params.items[0],
.align_expr = align_expr,
.addrspace_expr = 0, // TODO
.section_expr = section_expr,
.callconv_expr = callconv_expr,
}),
.rhs = return_type_expr,
},
})
else
break :blk c.addNode(.{
.tag = .fn_proto,
.main_token = fn_token,
.data = .{
.lhs = try c.addExtra(std.zig.Ast.Node.FnProto{
.params_start = span.start,
.params_end = span.end,
.align_expr = align_expr,
.addrspace_expr = 0, // TODO
.section_expr = section_expr,
.callconv_expr = callconv_expr,
}),
.rhs = return_type_expr,
},
});
};
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 = .{
.lhs = fn_proto,
.rhs = 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 = .{
.lhs = params.items[0],
.rhs = return_type_expr,
},
});
} else {
break :blk try c.addNode(.{
.tag = .fn_proto_multi,
.main_token = fn_token,
.data = .{
.lhs = try c.addExtra(std.zig.Ast.Node.SubRange{
.start = span.start,
.end = span.end,
}),
.rhs = return_type_expr,
},
});
}
};
return c.addNode(.{
.tag = .fn_decl,
.main_token = fn_token,
.data = .{
.lhs = fn_proto,
.rhs = try renderNode(c, payload.body),
},
});
}
fn renderParams(c: *Context, params: []Payload.Param, is_var_args: bool) !std.ArrayList(NodeIndex) {
_ = try c.addToken(.l_paren, "(");
var rendered = try std.ArrayList(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, ")");
if (rendered.items.len == 0) rendered.appendAssumeCapacity(0);
return rendered;
}