mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
921 lines
35 KiB
Zig
921 lines
35 KiB
Zig
//! Ingests an `Ast` and produces a `Zoir`.
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const mem = std.mem;
|
|
const Allocator = mem.Allocator;
|
|
const StringIndexAdapter = std.hash_map.StringIndexAdapter;
|
|
const StringIndexContext = std.hash_map.StringIndexContext;
|
|
const ZonGen = @This();
|
|
const Zoir = @import("Zoir.zig");
|
|
const Ast = @import("Ast.zig");
|
|
const Writer = std.Io.Writer;
|
|
|
|
gpa: Allocator,
|
|
tree: Ast,
|
|
|
|
options: Options,
|
|
|
|
nodes: std.MultiArrayList(Zoir.Node.Repr),
|
|
extra: std.ArrayList(u32),
|
|
limbs: std.ArrayList(std.math.big.Limb),
|
|
string_bytes: std.ArrayList(u8),
|
|
string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.default_max_load_percentage),
|
|
|
|
compile_errors: std.ArrayList(Zoir.CompileError),
|
|
error_notes: std.ArrayList(Zoir.CompileError.Note),
|
|
|
|
pub const Options = struct {
|
|
/// When false, string literals are not parsed. `string_literal` nodes will contain empty
|
|
/// strings, and errors that normally occur during string parsing will not be raised.
|
|
///
|
|
/// `parseStrLit` and `strLitSizeHint` may be used to parse string literals after the fact.
|
|
parse_str_lits: bool = true,
|
|
};
|
|
|
|
pub fn generate(gpa: Allocator, tree: Ast, options: Options) Allocator.Error!Zoir {
|
|
assert(tree.mode == .zon);
|
|
|
|
var zg: ZonGen = .{
|
|
.gpa = gpa,
|
|
.tree = tree,
|
|
.options = options,
|
|
.nodes = .empty,
|
|
.extra = .empty,
|
|
.limbs = .empty,
|
|
.string_bytes = .empty,
|
|
.string_table = .empty,
|
|
.compile_errors = .empty,
|
|
.error_notes = .empty,
|
|
};
|
|
defer {
|
|
zg.nodes.deinit(gpa);
|
|
zg.extra.deinit(gpa);
|
|
zg.limbs.deinit(gpa);
|
|
zg.string_bytes.deinit(gpa);
|
|
zg.string_table.deinit(gpa);
|
|
zg.compile_errors.deinit(gpa);
|
|
zg.error_notes.deinit(gpa);
|
|
}
|
|
|
|
if (tree.errors.len == 0) {
|
|
const root_ast_node = tree.rootDecls()[0];
|
|
try zg.nodes.append(gpa, undefined); // index 0; root node
|
|
try zg.expr(root_ast_node, .root);
|
|
} else {
|
|
try zg.lowerAstErrors();
|
|
}
|
|
|
|
if (zg.compile_errors.items.len > 0) {
|
|
const string_bytes = try zg.string_bytes.toOwnedSlice(gpa);
|
|
errdefer gpa.free(string_bytes);
|
|
const compile_errors = try zg.compile_errors.toOwnedSlice(gpa);
|
|
errdefer gpa.free(compile_errors);
|
|
const error_notes = try zg.error_notes.toOwnedSlice(gpa);
|
|
errdefer gpa.free(error_notes);
|
|
|
|
return .{
|
|
.nodes = .empty,
|
|
.extra = &.{},
|
|
.limbs = &.{},
|
|
.string_bytes = string_bytes,
|
|
.compile_errors = compile_errors,
|
|
.error_notes = error_notes,
|
|
};
|
|
} else {
|
|
assert(zg.error_notes.items.len == 0);
|
|
|
|
var nodes = zg.nodes.toOwnedSlice();
|
|
errdefer nodes.deinit(gpa);
|
|
const extra = try zg.extra.toOwnedSlice(gpa);
|
|
errdefer gpa.free(extra);
|
|
const limbs = try zg.limbs.toOwnedSlice(gpa);
|
|
errdefer gpa.free(limbs);
|
|
const string_bytes = try zg.string_bytes.toOwnedSlice(gpa);
|
|
errdefer gpa.free(string_bytes);
|
|
|
|
return .{
|
|
.nodes = nodes,
|
|
.extra = extra,
|
|
.limbs = limbs,
|
|
.string_bytes = string_bytes,
|
|
.compile_errors = &.{},
|
|
.error_notes = &.{},
|
|
};
|
|
}
|
|
}
|
|
|
|
fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator.Error!void {
|
|
const gpa = zg.gpa;
|
|
const tree = zg.tree;
|
|
|
|
switch (tree.nodeTag(node)) {
|
|
.root => unreachable,
|
|
.test_decl => unreachable,
|
|
.container_field_init => unreachable,
|
|
.container_field_align => unreachable,
|
|
.container_field => unreachable,
|
|
.fn_decl => unreachable,
|
|
.global_var_decl => unreachable,
|
|
.local_var_decl => unreachable,
|
|
.simple_var_decl => unreachable,
|
|
.aligned_var_decl => unreachable,
|
|
.@"defer" => unreachable,
|
|
.@"errdefer" => unreachable,
|
|
.switch_case => unreachable,
|
|
.switch_case_inline => unreachable,
|
|
.switch_case_one => unreachable,
|
|
.switch_case_inline_one => unreachable,
|
|
.switch_range => unreachable,
|
|
.asm_output => unreachable,
|
|
.asm_input => unreachable,
|
|
.for_range => unreachable,
|
|
.assign => unreachable,
|
|
.assign_destructure => unreachable,
|
|
.assign_shl => unreachable,
|
|
.assign_shl_sat => unreachable,
|
|
.assign_shr => unreachable,
|
|
.assign_bit_and => unreachable,
|
|
.assign_bit_or => unreachable,
|
|
.assign_bit_xor => unreachable,
|
|
.assign_div => unreachable,
|
|
.assign_sub => unreachable,
|
|
.assign_sub_wrap => unreachable,
|
|
.assign_sub_sat => unreachable,
|
|
.assign_mod => unreachable,
|
|
.assign_add => unreachable,
|
|
.assign_add_wrap => unreachable,
|
|
.assign_add_sat => unreachable,
|
|
.assign_mul => unreachable,
|
|
.assign_mul_wrap => unreachable,
|
|
.assign_mul_sat => unreachable,
|
|
|
|
.shl,
|
|
.shr,
|
|
.add,
|
|
.add_wrap,
|
|
.add_sat,
|
|
.sub,
|
|
.sub_wrap,
|
|
.sub_sat,
|
|
.mul,
|
|
.mul_wrap,
|
|
.mul_sat,
|
|
.div,
|
|
.mod,
|
|
.shl_sat,
|
|
.bit_and,
|
|
.bit_or,
|
|
.bit_xor,
|
|
.bang_equal,
|
|
.equal_equal,
|
|
.greater_than,
|
|
.greater_or_equal,
|
|
.less_than,
|
|
.less_or_equal,
|
|
.array_cat,
|
|
.array_mult,
|
|
.bool_and,
|
|
.bool_or,
|
|
.bool_not,
|
|
.bit_not,
|
|
.negation_wrap,
|
|
=> try zg.addErrorTok(tree.nodeMainToken(node), "operator '{s}' is not allowed in ZON", .{tree.tokenSlice(tree.nodeMainToken(node))}),
|
|
|
|
.error_union,
|
|
.merge_error_sets,
|
|
.optional_type,
|
|
.anyframe_literal,
|
|
.anyframe_type,
|
|
.ptr_type_aligned,
|
|
.ptr_type_sentinel,
|
|
.ptr_type,
|
|
.ptr_type_bit_range,
|
|
.container_decl,
|
|
.container_decl_trailing,
|
|
.container_decl_arg,
|
|
.container_decl_arg_trailing,
|
|
.container_decl_two,
|
|
.container_decl_two_trailing,
|
|
.tagged_union,
|
|
.tagged_union_trailing,
|
|
.tagged_union_enum_tag,
|
|
.tagged_union_enum_tag_trailing,
|
|
.tagged_union_two,
|
|
.tagged_union_two_trailing,
|
|
.array_type,
|
|
.array_type_sentinel,
|
|
.error_set_decl,
|
|
.fn_proto_simple,
|
|
.fn_proto_multi,
|
|
.fn_proto_one,
|
|
.fn_proto,
|
|
=> try zg.addErrorNode(node, "types are not available in ZON", .{}),
|
|
|
|
.call_one,
|
|
.call_one_comma,
|
|
.call,
|
|
.call_comma,
|
|
.@"return",
|
|
.if_simple,
|
|
.@"if",
|
|
.while_simple,
|
|
.while_cont,
|
|
.@"while",
|
|
.for_simple,
|
|
.@"for",
|
|
.@"catch",
|
|
.@"orelse",
|
|
.@"break",
|
|
.@"continue",
|
|
.@"switch",
|
|
.switch_comma,
|
|
.@"nosuspend",
|
|
.@"suspend",
|
|
.@"resume",
|
|
.@"try",
|
|
.unreachable_literal,
|
|
=> try zg.addErrorNode(node, "control flow is not allowed in ZON", .{}),
|
|
|
|
.@"comptime" => try zg.addErrorNode(node, "keyword 'comptime' is not allowed in ZON", .{}),
|
|
.asm_simple, .@"asm", .asm_legacy => try zg.addErrorNode(node, "inline asm is not allowed in ZON", .{}),
|
|
|
|
.builtin_call_two,
|
|
.builtin_call_two_comma,
|
|
.builtin_call,
|
|
.builtin_call_comma,
|
|
=> try zg.addErrorNode(node, "builtin function calls are not allowed in ZON", .{}),
|
|
|
|
.field_access => try zg.addErrorNode(node, "field accesses are not allowed in ZON", .{}),
|
|
|
|
.slice_open,
|
|
.slice,
|
|
.slice_sentinel,
|
|
=> try zg.addErrorNode(node, "slice operator is not allowed in ZON", .{}),
|
|
|
|
.deref, .address_of => try zg.addErrorTok(tree.nodeMainToken(node), "pointers are not available in ZON", .{}),
|
|
.unwrap_optional => try zg.addErrorTok(tree.nodeMainToken(node), "optionals are not available in ZON", .{}),
|
|
.error_value => try zg.addErrorNode(node, "errors are not available in ZON", .{}),
|
|
|
|
.array_access => try zg.addErrorNode(node, "array indexing is not allowed in ZON", .{}),
|
|
|
|
.block_two,
|
|
.block_two_semicolon,
|
|
.block,
|
|
.block_semicolon,
|
|
=> {
|
|
var buffer: [2]Ast.Node.Index = undefined;
|
|
const statements = tree.blockStatements(&buffer, node).?;
|
|
if (statements.len == 0) {
|
|
try zg.addErrorNodeNotes(node, "void literals are not available in ZON", .{}, &.{
|
|
try zg.errNoteNode(node, "void union payloads can be represented by enum literals", .{}),
|
|
});
|
|
} else {
|
|
try zg.addErrorNode(node, "blocks are not allowed in ZON", .{});
|
|
}
|
|
},
|
|
|
|
.array_init_one,
|
|
.array_init_one_comma,
|
|
.array_init,
|
|
.array_init_comma,
|
|
.struct_init_one,
|
|
.struct_init_one_comma,
|
|
.struct_init,
|
|
.struct_init_comma,
|
|
=> {
|
|
var buf: [2]Ast.Node.Index = undefined;
|
|
|
|
const type_node = if (tree.fullArrayInit(&buf, node)) |full|
|
|
full.ast.type_expr.unwrap().?
|
|
else if (tree.fullStructInit(&buf, node)) |full|
|
|
full.ast.type_expr.unwrap().?
|
|
else
|
|
unreachable;
|
|
|
|
try zg.addErrorNodeNotes(type_node, "types are not available in ZON", .{}, &.{
|
|
try zg.errNoteNode(type_node, "replace the type with '.'", .{}),
|
|
});
|
|
},
|
|
|
|
.grouped_expression => {
|
|
try zg.addErrorTokNotes(tree.nodeMainToken(node), "expression grouping is not allowed in ZON", .{}, &.{
|
|
try zg.errNoteTok(tree.nodeMainToken(node), "these parentheses are always redundant", .{}),
|
|
});
|
|
return zg.expr(tree.nodeData(node).node_and_token[0], dest_node);
|
|
},
|
|
|
|
.negation => {
|
|
const child_node = tree.nodeData(node).node;
|
|
switch (tree.nodeTag(child_node)) {
|
|
.number_literal => return zg.numberLiteral(child_node, node, dest_node, .negative),
|
|
.identifier => {
|
|
const child_ident = tree.tokenSlice(tree.nodeMainToken(child_node));
|
|
if (mem.eql(u8, child_ident, "inf")) {
|
|
zg.setNode(dest_node, .{
|
|
.tag = .neg_inf,
|
|
.data = 0, // ignored
|
|
.ast_node = node,
|
|
});
|
|
return;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
try zg.addErrorTok(tree.nodeMainToken(node), "expected number or 'inf' after '-'", .{});
|
|
},
|
|
.number_literal => try zg.numberLiteral(node, node, dest_node, .positive),
|
|
.char_literal => try zg.charLiteral(node, dest_node),
|
|
|
|
.identifier => try zg.identifier(node, dest_node),
|
|
|
|
.enum_literal => {
|
|
const str_index = zg.identAsString(tree.nodeMainToken(node)) catch |err| switch (err) {
|
|
error.BadString => undefined, // doesn't matter, there's an error
|
|
error.OutOfMemory => |e| return e,
|
|
};
|
|
zg.setNode(dest_node, .{
|
|
.tag = .enum_literal,
|
|
.data = @intFromEnum(str_index),
|
|
.ast_node = node,
|
|
});
|
|
},
|
|
.string_literal, .multiline_string_literal => if (zg.strLitAsString(node)) |result| switch (result) {
|
|
.nts => |nts| zg.setNode(dest_node, .{
|
|
.tag = .string_literal_null,
|
|
.data = @intFromEnum(nts),
|
|
.ast_node = node,
|
|
}),
|
|
.slice => |slice| {
|
|
const extra_index: u32 = @intCast(zg.extra.items.len);
|
|
try zg.extra.appendSlice(zg.gpa, &.{ slice.start, slice.len });
|
|
zg.setNode(dest_node, .{
|
|
.tag = .string_literal,
|
|
.data = extra_index,
|
|
.ast_node = node,
|
|
});
|
|
},
|
|
} else |err| switch (err) {
|
|
error.BadString => {},
|
|
error.OutOfMemory => |e| return e,
|
|
},
|
|
|
|
.array_init_dot_two,
|
|
.array_init_dot_two_comma,
|
|
.array_init_dot,
|
|
.array_init_dot_comma,
|
|
=> {
|
|
var buf: [2]Ast.Node.Index = undefined;
|
|
const full = tree.fullArrayInit(&buf, node).?;
|
|
assert(full.ast.elements.len != 0); // Otherwise it would be a struct init
|
|
assert(full.ast.type_expr == .none); // The tag was `array_init_dot_*`
|
|
|
|
const first_elem: u32 = @intCast(zg.nodes.len);
|
|
try zg.nodes.resize(gpa, zg.nodes.len + full.ast.elements.len);
|
|
|
|
const extra_index: u32 = @intCast(zg.extra.items.len);
|
|
try zg.extra.appendSlice(gpa, &.{
|
|
@intCast(full.ast.elements.len),
|
|
first_elem,
|
|
});
|
|
|
|
zg.setNode(dest_node, .{
|
|
.tag = .array_literal,
|
|
.data = extra_index,
|
|
.ast_node = node,
|
|
});
|
|
|
|
for (full.ast.elements, first_elem..) |elem_node, elem_dest_node| {
|
|
try zg.expr(elem_node, @enumFromInt(elem_dest_node));
|
|
}
|
|
},
|
|
|
|
.struct_init_dot_two,
|
|
.struct_init_dot_two_comma,
|
|
.struct_init_dot,
|
|
.struct_init_dot_comma,
|
|
=> {
|
|
var buf: [2]Ast.Node.Index = undefined;
|
|
const full = tree.fullStructInit(&buf, node).?;
|
|
assert(full.ast.type_expr == .none); // The tag was `struct_init_dot_*`
|
|
|
|
if (full.ast.fields.len == 0) {
|
|
zg.setNode(dest_node, .{
|
|
.tag = .empty_literal,
|
|
.data = 0, // ignored
|
|
.ast_node = node,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const first_elem: u32 = @intCast(zg.nodes.len);
|
|
try zg.nodes.resize(gpa, zg.nodes.len + full.ast.fields.len);
|
|
|
|
const extra_index: u32 = @intCast(zg.extra.items.len);
|
|
try zg.extra.ensureUnusedCapacity(gpa, 2 + full.ast.fields.len);
|
|
zg.extra.appendSliceAssumeCapacity(&.{
|
|
@intCast(full.ast.fields.len),
|
|
first_elem,
|
|
});
|
|
const names_start = extra_index + 2;
|
|
zg.extra.appendNTimesAssumeCapacity(undefined, full.ast.fields.len);
|
|
|
|
zg.setNode(dest_node, .{
|
|
.tag = .struct_literal,
|
|
.data = extra_index,
|
|
.ast_node = node,
|
|
});
|
|
|
|
// For short initializers, track the names on the stack rather than going through gpa.
|
|
var sfba_state = std.heap.stackFallback(256, gpa);
|
|
const sfba = sfba_state.get();
|
|
var field_names: std.AutoHashMapUnmanaged(Zoir.NullTerminatedString, Ast.TokenIndex) = .empty;
|
|
defer field_names.deinit(sfba);
|
|
|
|
var reported_any_duplicate = false;
|
|
|
|
for (full.ast.fields, names_start.., first_elem..) |elem_node, extra_name_idx, elem_dest_node| {
|
|
const name_token = tree.firstToken(elem_node) - 2;
|
|
if (zg.identAsString(name_token)) |name_str| {
|
|
zg.extra.items[extra_name_idx] = @intFromEnum(name_str);
|
|
const gop = try field_names.getOrPut(sfba, name_str);
|
|
if (gop.found_existing and !reported_any_duplicate) {
|
|
reported_any_duplicate = true;
|
|
const earlier_token = gop.value_ptr.*;
|
|
try zg.addErrorTokNotes(earlier_token, "duplicate struct field name", .{}, &.{
|
|
try zg.errNoteTok(name_token, "duplicate name here", .{}),
|
|
});
|
|
}
|
|
gop.value_ptr.* = name_token;
|
|
} else |err| switch (err) {
|
|
error.BadString => {}, // there's an error, so it's fine to not populate `zg.extra`
|
|
error.OutOfMemory => |e| return e,
|
|
}
|
|
try zg.expr(elem_node, @enumFromInt(elem_dest_node));
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) error{ OutOfMemory, BadString }!u32 {
|
|
const gpa = zg.gpa;
|
|
const tree = zg.tree;
|
|
assert(tree.tokenTag(ident_token) == .identifier);
|
|
const ident_name = tree.tokenSlice(ident_token);
|
|
if (!mem.startsWith(u8, ident_name, "@")) {
|
|
const start = zg.string_bytes.items.len;
|
|
try zg.string_bytes.appendSlice(gpa, ident_name);
|
|
return @intCast(start);
|
|
}
|
|
const offset = 1;
|
|
const start: u32 = @intCast(zg.string_bytes.items.len);
|
|
const raw_string = zg.tree.tokenSlice(ident_token)[offset..];
|
|
try zg.string_bytes.ensureUnusedCapacity(gpa, raw_string.len);
|
|
const result = r: {
|
|
var aw: Writer.Allocating = .fromArrayList(gpa, &zg.string_bytes);
|
|
defer zg.string_bytes = aw.toArrayList();
|
|
break :r std.zig.string_literal.parseWrite(&aw.writer, raw_string) catch |err| switch (err) {
|
|
error.WriteFailed => return error.OutOfMemory,
|
|
};
|
|
};
|
|
switch (result) {
|
|
.success => {},
|
|
.failure => |err| {
|
|
try zg.lowerStrLitError(err, ident_token, raw_string, offset);
|
|
return error.BadString;
|
|
},
|
|
}
|
|
|
|
const slice = zg.string_bytes.items[start..];
|
|
if (mem.findScalar(u8, slice, 0) != null) {
|
|
try zg.addErrorTok(ident_token, "identifier cannot contain null bytes", .{});
|
|
return error.BadString;
|
|
} else if (slice.len == 0) {
|
|
try zg.addErrorTok(ident_token, "identifier cannot be empty", .{});
|
|
return error.BadString;
|
|
}
|
|
return start;
|
|
}
|
|
|
|
/// Estimates the size of a string node without parsing it.
|
|
pub fn strLitSizeHint(tree: Ast, node: Ast.Node.Index) usize {
|
|
switch (tree.nodeTag(node)) {
|
|
// Parsed string literals are typically around the size of the raw strings.
|
|
.string_literal => {
|
|
const token = tree.nodeMainToken(node);
|
|
const raw_string = tree.tokenSlice(token);
|
|
return raw_string.len;
|
|
},
|
|
// Multiline string literal lengths can be computed exactly.
|
|
.multiline_string_literal => {
|
|
const first_tok, const last_tok = tree.nodeData(node).token_and_token;
|
|
|
|
var size = tree.tokenSlice(first_tok)[2..].len;
|
|
for (first_tok + 1..last_tok + 1) |tok_idx| {
|
|
size += 1; // Newline
|
|
size += tree.tokenSlice(@intCast(tok_idx))[2..].len;
|
|
}
|
|
return size;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
/// Parses the given node as a string literal.
|
|
pub fn parseStrLit(
|
|
tree: Ast,
|
|
node: Ast.Node.Index,
|
|
writer: *Writer,
|
|
) Writer.Error!std.zig.string_literal.Result {
|
|
switch (tree.nodeTag(node)) {
|
|
.string_literal => {
|
|
const token = tree.nodeMainToken(node);
|
|
const raw_string = tree.tokenSlice(token);
|
|
return std.zig.string_literal.parseWrite(writer, raw_string);
|
|
},
|
|
.multiline_string_literal => {
|
|
const first_tok, const last_tok = tree.nodeData(node).token_and_token;
|
|
|
|
// First line: do not append a newline.
|
|
{
|
|
const line_bytes = tree.tokenSlice(first_tok)[2..];
|
|
try writer.writeAll(line_bytes);
|
|
}
|
|
|
|
// Following lines: each line prepends a newline.
|
|
for (first_tok + 1..last_tok + 1) |tok_idx| {
|
|
const line_bytes = tree.tokenSlice(@intCast(tok_idx))[2..];
|
|
try writer.writeByte('\n');
|
|
try writer.writeAll(line_bytes);
|
|
}
|
|
|
|
return .success;
|
|
},
|
|
// Node must represent a string
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
const StringLiteralResult = union(enum) {
|
|
nts: Zoir.NullTerminatedString,
|
|
slice: struct { start: u32, len: u32 },
|
|
};
|
|
|
|
fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) error{ OutOfMemory, BadString }!StringLiteralResult {
|
|
if (!zg.options.parse_str_lits) return .{ .slice = .{ .start = 0, .len = 0 } };
|
|
|
|
const gpa = zg.gpa;
|
|
const string_bytes = &zg.string_bytes;
|
|
const str_index: u32 = @intCast(zg.string_bytes.items.len);
|
|
const size_hint = strLitSizeHint(zg.tree, str_node);
|
|
try string_bytes.ensureUnusedCapacity(gpa, size_hint);
|
|
const result = r: {
|
|
var aw: Writer.Allocating = .fromArrayList(gpa, &zg.string_bytes);
|
|
defer zg.string_bytes = aw.toArrayList();
|
|
break :r parseStrLit(zg.tree, str_node, &aw.writer) catch |err| switch (err) {
|
|
error.WriteFailed => return error.OutOfMemory,
|
|
};
|
|
};
|
|
switch (result) {
|
|
.success => {},
|
|
.failure => |err| {
|
|
const token = zg.tree.nodeMainToken(str_node);
|
|
const raw_string = zg.tree.tokenSlice(token);
|
|
try zg.lowerStrLitError(err, token, raw_string, 0);
|
|
return error.BadString;
|
|
},
|
|
}
|
|
const key: []const u8 = string_bytes.items[str_index..];
|
|
if (std.mem.findScalar(u8, key, 0) != null) return .{ .slice = .{
|
|
.start = str_index,
|
|
.len = @intCast(key.len),
|
|
} };
|
|
const gop = try zg.string_table.getOrPutContextAdapted(
|
|
gpa,
|
|
key,
|
|
StringIndexAdapter{ .bytes = string_bytes },
|
|
StringIndexContext{ .bytes = string_bytes },
|
|
);
|
|
if (gop.found_existing) {
|
|
string_bytes.shrinkRetainingCapacity(str_index);
|
|
return .{ .nts = @enumFromInt(gop.key_ptr.*) };
|
|
}
|
|
gop.key_ptr.* = str_index;
|
|
try string_bytes.append(gpa, 0);
|
|
return .{ .nts = @enumFromInt(str_index) };
|
|
}
|
|
|
|
fn identAsString(zg: *ZonGen, ident_token: Ast.TokenIndex) !Zoir.NullTerminatedString {
|
|
const gpa = zg.gpa;
|
|
const string_bytes = &zg.string_bytes;
|
|
const str_index = try zg.appendIdentStr(ident_token);
|
|
const key: []const u8 = string_bytes.items[str_index..];
|
|
const gop = try zg.string_table.getOrPutContextAdapted(
|
|
gpa,
|
|
key,
|
|
StringIndexAdapter{ .bytes = string_bytes },
|
|
StringIndexContext{ .bytes = string_bytes },
|
|
);
|
|
if (gop.found_existing) {
|
|
string_bytes.shrinkRetainingCapacity(str_index);
|
|
return @enumFromInt(gop.key_ptr.*);
|
|
}
|
|
gop.key_ptr.* = str_index;
|
|
try string_bytes.append(gpa, 0);
|
|
return @enumFromInt(str_index);
|
|
}
|
|
|
|
fn numberLiteral(zg: *ZonGen, num_node: Ast.Node.Index, src_node: Ast.Node.Index, dest_node: Zoir.Node.Index, sign: enum { negative, positive }) !void {
|
|
const tree = zg.tree;
|
|
const num_token = tree.nodeMainToken(num_node);
|
|
const num_bytes = tree.tokenSlice(num_token);
|
|
|
|
switch (std.zig.parseNumberLiteral(num_bytes)) {
|
|
.int => |unsigned_num| {
|
|
if (unsigned_num == 0 and sign == .negative) {
|
|
try zg.addErrorTokNotes(num_token, "integer literal '-0' is ambiguous", .{}, &.{
|
|
try zg.errNoteTok(num_token, "use '0' for an integer zero", .{}),
|
|
try zg.errNoteTok(num_token, "use '-0.0' for a floating-point signed zero", .{}),
|
|
});
|
|
return;
|
|
}
|
|
const num: i65 = switch (sign) {
|
|
.positive => unsigned_num,
|
|
.negative => -@as(i65, unsigned_num),
|
|
};
|
|
if (std.math.cast(i32, num)) |x| {
|
|
zg.setNode(dest_node, .{
|
|
.tag = .int_literal_small,
|
|
.data = @bitCast(x),
|
|
.ast_node = src_node,
|
|
});
|
|
return;
|
|
}
|
|
const max_limbs = comptime std.math.big.int.calcTwosCompLimbCount(@bitSizeOf(@TypeOf(num)));
|
|
var limbs: [max_limbs]std.math.big.Limb = undefined;
|
|
var big_int: std.math.big.int.Mutable = .init(&limbs, num);
|
|
try zg.setBigIntLiteralNode(dest_node, src_node, big_int.toConst());
|
|
},
|
|
.big_int => |base| {
|
|
const gpa = zg.gpa;
|
|
const num_without_prefix = switch (base) {
|
|
.decimal => num_bytes,
|
|
.hex, .binary, .octal => num_bytes[2..],
|
|
};
|
|
var big_int: std.math.big.int.Managed = try .init(gpa);
|
|
defer big_int.deinit();
|
|
big_int.setString(@intFromEnum(base), num_without_prefix) catch |err| switch (err) {
|
|
error.InvalidCharacter => unreachable, // caught in `parseNumberLiteral`
|
|
error.InvalidBase => unreachable, // we only pass 16, 8, 2, see above
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
};
|
|
switch (sign) {
|
|
.positive => {},
|
|
.negative => big_int.negate(),
|
|
}
|
|
try zg.setBigIntLiteralNode(dest_node, src_node, big_int.toConst());
|
|
},
|
|
.float => {
|
|
const unsigned_num = std.fmt.parseFloat(f128, num_bytes) catch |err| switch (err) {
|
|
error.InvalidCharacter => unreachable, // validated by tokenizer
|
|
};
|
|
const num: f128 = switch (sign) {
|
|
.positive => unsigned_num,
|
|
.negative => -unsigned_num,
|
|
};
|
|
|
|
{
|
|
// If the value fits into an f32 without losing any precision, store it that way.
|
|
@setFloatMode(.strict);
|
|
const smaller_float: f32 = @floatCast(num);
|
|
const bigger_again: f128 = smaller_float;
|
|
if (bigger_again == num) {
|
|
zg.setNode(dest_node, .{
|
|
.tag = .float_literal_small,
|
|
.data = @bitCast(smaller_float),
|
|
.ast_node = src_node,
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
const elems: [4]u32 = @bitCast(num);
|
|
const extra_index: u32 = @intCast(zg.extra.items.len);
|
|
try zg.extra.appendSlice(zg.gpa, &elems);
|
|
zg.setNode(dest_node, .{
|
|
.tag = .float_literal,
|
|
.data = extra_index,
|
|
.ast_node = src_node,
|
|
});
|
|
},
|
|
.failure => |err| try zg.lowerNumberError(err, num_token, num_bytes),
|
|
}
|
|
}
|
|
|
|
fn setBigIntLiteralNode(zg: *ZonGen, dest_node: Zoir.Node.Index, src_node: Ast.Node.Index, val: std.math.big.int.Const) !void {
|
|
try zg.extra.ensureUnusedCapacity(zg.gpa, 2);
|
|
try zg.limbs.ensureUnusedCapacity(zg.gpa, val.limbs.len);
|
|
|
|
const limbs_idx: u32 = @intCast(zg.limbs.items.len);
|
|
zg.limbs.appendSliceAssumeCapacity(val.limbs);
|
|
|
|
const extra_idx: u32 = @intCast(zg.extra.items.len);
|
|
zg.extra.appendSliceAssumeCapacity(&.{ @intCast(val.limbs.len), limbs_idx });
|
|
|
|
zg.setNode(dest_node, .{
|
|
.tag = if (val.positive) .int_literal_pos else .int_literal_neg,
|
|
.data = extra_idx,
|
|
.ast_node = src_node,
|
|
});
|
|
}
|
|
|
|
fn charLiteral(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) !void {
|
|
const tree = zg.tree;
|
|
assert(tree.nodeTag(node) == .char_literal);
|
|
const main_token = tree.nodeMainToken(node);
|
|
const slice = tree.tokenSlice(main_token);
|
|
switch (std.zig.parseCharLiteral(slice)) {
|
|
.success => |codepoint| zg.setNode(dest_node, .{
|
|
.tag = .char_literal,
|
|
.data = codepoint,
|
|
.ast_node = node,
|
|
}),
|
|
.failure => |err| try zg.lowerStrLitError(err, main_token, slice, 0),
|
|
}
|
|
}
|
|
|
|
fn identifier(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) !void {
|
|
const tree = zg.tree;
|
|
assert(tree.nodeTag(node) == .identifier);
|
|
const main_token = tree.nodeMainToken(node);
|
|
const ident = tree.tokenSlice(main_token);
|
|
|
|
const tag: Zoir.Node.Repr.Tag = t: {
|
|
if (mem.eql(u8, ident, "true")) break :t .true;
|
|
if (mem.eql(u8, ident, "false")) break :t .false;
|
|
if (mem.eql(u8, ident, "null")) break :t .null;
|
|
if (mem.eql(u8, ident, "inf")) break :t .pos_inf;
|
|
if (mem.eql(u8, ident, "nan")) break :t .nan;
|
|
try zg.addErrorNodeNotes(node, "invalid expression", .{}, &.{
|
|
try zg.errNoteNode(node, "ZON allows identifiers 'true', 'false', 'null', 'inf', and 'nan'", .{}),
|
|
try zg.errNoteNode(node, "precede identifier with '.' for an enum literal", .{}),
|
|
});
|
|
return;
|
|
};
|
|
|
|
zg.setNode(dest_node, .{
|
|
.tag = tag,
|
|
.data = 0, // ignored
|
|
.ast_node = node,
|
|
});
|
|
}
|
|
|
|
fn setNode(zg: *ZonGen, dest: Zoir.Node.Index, repr: Zoir.Node.Repr) void {
|
|
zg.nodes.set(@intFromEnum(dest), repr);
|
|
}
|
|
|
|
fn lowerStrLitError(
|
|
zg: *ZonGen,
|
|
err: std.zig.string_literal.Error,
|
|
token: Ast.TokenIndex,
|
|
raw_string: []const u8,
|
|
offset: u32,
|
|
) Allocator.Error!void {
|
|
return ZonGen.addErrorTokOff(zg, token, @intCast(offset + err.offset()), "{f}", .{err.fmt(raw_string)});
|
|
}
|
|
|
|
fn lowerNumberError(zg: *ZonGen, err: std.zig.number_literal.Error, token: Ast.TokenIndex, bytes: []const u8) Allocator.Error!void {
|
|
const is_float = std.mem.findScalar(u8, bytes, '.') != null;
|
|
switch (err) {
|
|
.leading_zero => if (is_float) {
|
|
try zg.addErrorTok(token, "number '{s}' has leading zero", .{bytes});
|
|
} else {
|
|
try zg.addErrorTokNotes(token, "number '{s}' has leading zero", .{bytes}, &.{
|
|
try zg.errNoteTok(token, "use '0o' prefix for octal literals", .{}),
|
|
});
|
|
},
|
|
.digit_after_base => try zg.addErrorTok(token, "expected a digit after base prefix", .{}),
|
|
.upper_case_base => |i| try zg.addErrorTokOff(token, @intCast(i), "base prefix must be lowercase", .{}),
|
|
.invalid_float_base => |i| try zg.addErrorTokOff(token, @intCast(i), "invalid base for float literal", .{}),
|
|
.repeated_underscore => |i| try zg.addErrorTokOff(token, @intCast(i), "repeated digit separator", .{}),
|
|
.invalid_underscore_after_special => |i| try zg.addErrorTokOff(token, @intCast(i), "expected digit before digit separator", .{}),
|
|
.invalid_digit => |info| try zg.addErrorTokOff(token, @intCast(info.i), "invalid digit '{c}' for {s} base", .{ bytes[info.i], @tagName(info.base) }),
|
|
.invalid_digit_exponent => |i| try zg.addErrorTokOff(token, @intCast(i), "invalid digit '{c}' in exponent", .{bytes[i]}),
|
|
.duplicate_exponent => |i| try zg.addErrorTokOff(token, @intCast(i), "duplicate exponent", .{}),
|
|
.exponent_after_underscore => |i| try zg.addErrorTokOff(token, @intCast(i), "expected digit before exponent", .{}),
|
|
.special_after_underscore => |i| try zg.addErrorTokOff(token, @intCast(i), "expected digit before '{c}'", .{bytes[i]}),
|
|
.trailing_special => |i| try zg.addErrorTokOff(token, @intCast(i), "expected digit after '{c}'", .{bytes[i - 1]}),
|
|
.trailing_underscore => |i| try zg.addErrorTokOff(token, @intCast(i), "trailing digit separator", .{}),
|
|
.duplicate_period => unreachable, // Validated by tokenizer
|
|
.invalid_character => unreachable, // Validated by tokenizer
|
|
.invalid_exponent_sign => |i| {
|
|
assert(bytes.len >= 2 and bytes[0] == '0' and bytes[1] == 'x'); // Validated by tokenizer
|
|
try zg.addErrorTokOff(token, @intCast(i), "sign '{c}' cannot follow digit '{c}' in hex base", .{ bytes[i], bytes[i - 1] });
|
|
},
|
|
.period_after_exponent => |i| try zg.addErrorTokOff(token, @intCast(i), "unexpected period after exponent", .{}),
|
|
}
|
|
}
|
|
|
|
fn errNoteNode(zg: *ZonGen, node: Ast.Node.Index, comptime format: []const u8, args: anytype) Allocator.Error!Zoir.CompileError.Note {
|
|
const message_idx: u32 = @intCast(zg.string_bytes.items.len);
|
|
try zg.string_bytes.print(zg.gpa, format ++ "\x00", args);
|
|
return .{
|
|
.msg = @enumFromInt(message_idx),
|
|
.token = .none,
|
|
.node_or_offset = @intFromEnum(node),
|
|
};
|
|
}
|
|
|
|
fn errNoteTok(zg: *ZonGen, tok: Ast.TokenIndex, comptime format: []const u8, args: anytype) Allocator.Error!Zoir.CompileError.Note {
|
|
const message_idx: u32 = @intCast(zg.string_bytes.items.len);
|
|
try zg.string_bytes.print(zg.gpa, format ++ "\x00", args);
|
|
return .{
|
|
.msg = @enumFromInt(message_idx),
|
|
.token = .fromToken(tok),
|
|
.node_or_offset = 0,
|
|
};
|
|
}
|
|
|
|
fn addErrorNode(zg: *ZonGen, node: Ast.Node.Index, comptime format: []const u8, args: anytype) Allocator.Error!void {
|
|
return zg.addErrorInner(.none, @intFromEnum(node), format, args, &.{});
|
|
}
|
|
fn addErrorTok(zg: *ZonGen, tok: Ast.TokenIndex, comptime format: []const u8, args: anytype) Allocator.Error!void {
|
|
return zg.addErrorInner(.fromToken(tok), 0, format, args, &.{});
|
|
}
|
|
fn addErrorNodeNotes(zg: *ZonGen, node: Ast.Node.Index, comptime format: []const u8, args: anytype, notes: []const Zoir.CompileError.Note) Allocator.Error!void {
|
|
return zg.addErrorInner(.none, @intFromEnum(node), format, args, notes);
|
|
}
|
|
fn addErrorTokNotes(zg: *ZonGen, tok: Ast.TokenIndex, comptime format: []const u8, args: anytype, notes: []const Zoir.CompileError.Note) Allocator.Error!void {
|
|
return zg.addErrorInner(.fromToken(tok), 0, format, args, notes);
|
|
}
|
|
fn addErrorTokOff(zg: *ZonGen, tok: Ast.TokenIndex, offset: u32, comptime format: []const u8, args: anytype) Allocator.Error!void {
|
|
return zg.addErrorInner(.fromToken(tok), offset, format, args, &.{});
|
|
}
|
|
fn addErrorTokNotesOff(zg: *ZonGen, tok: Ast.TokenIndex, offset: u32, comptime format: []const u8, args: anytype, notes: []const Zoir.CompileError.Note) Allocator.Error!void {
|
|
return zg.addErrorInner(.fromToken(tok), offset, format, args, notes);
|
|
}
|
|
|
|
fn addErrorInner(
|
|
zg: *ZonGen,
|
|
token: Ast.OptionalTokenIndex,
|
|
node_or_offset: u32,
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
notes: []const Zoir.CompileError.Note,
|
|
) Allocator.Error!void {
|
|
const gpa = zg.gpa;
|
|
|
|
const first_note: u32 = @intCast(zg.error_notes.items.len);
|
|
try zg.error_notes.appendSlice(gpa, notes);
|
|
|
|
const message_idx: u32 = @intCast(zg.string_bytes.items.len);
|
|
try zg.string_bytes.print(gpa, format ++ "\x00", args);
|
|
|
|
try zg.compile_errors.append(gpa, .{
|
|
.msg = @enumFromInt(message_idx),
|
|
.token = token,
|
|
.node_or_offset = node_or_offset,
|
|
.first_note = first_note,
|
|
.note_count = @intCast(notes.len),
|
|
});
|
|
}
|
|
|
|
fn lowerAstErrors(zg: *ZonGen) Allocator.Error!void {
|
|
const gpa = zg.gpa;
|
|
const tree = zg.tree;
|
|
assert(tree.errors.len > 0);
|
|
|
|
var msg: Writer.Allocating = .init(gpa);
|
|
defer msg.deinit();
|
|
const msg_bw = &msg.writer;
|
|
|
|
var notes: std.ArrayList(Zoir.CompileError.Note) = .empty;
|
|
defer notes.deinit(gpa);
|
|
|
|
var cur_err = tree.errors[0];
|
|
for (tree.errors[1..]) |err| {
|
|
if (err.is_note) {
|
|
tree.renderError(err, msg_bw) catch return error.OutOfMemory;
|
|
try notes.append(gpa, try zg.errNoteTok(err.token, "{s}", .{msg.written()}));
|
|
} else {
|
|
// Flush error
|
|
tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory;
|
|
const extra_offset = tree.errorOffset(cur_err);
|
|
try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.written()}, notes.items);
|
|
notes.clearRetainingCapacity();
|
|
cur_err = err;
|
|
|
|
// TODO: `Parse` currently does not have good error recovery
|
|
// mechanisms, so the remaining errors could be bogus. As such,
|
|
// we'll ignore all remaining errors for now. We should improve
|
|
// `Parse` so that we can report all the errors.
|
|
return;
|
|
}
|
|
msg.clearRetainingCapacity();
|
|
}
|
|
|
|
// Flush error
|
|
const extra_offset = tree.errorOffset(cur_err);
|
|
tree.renderError(cur_err, msg_bw) catch return error.OutOfMemory;
|
|
try zg.addErrorTokNotesOff(cur_err.token, extra_offset, "{s}", .{msg.written()}, notes.items);
|
|
}
|