feature. See also
. The project being documented here (as the example) is the Zig library itself.
parse.Parser
const Parser = struct
File
Code
const Parser = struct {
gpa: Allocator,
ast: Ast,
zoir: Zoir,
diag: ?*Diagnostics,
options: Options,
const ParseExprError = error{ ParseZon, OutOfMemory };
fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprError!T {
return self.parseExprInner(T, node) catch |err| switch (err) {
error.WrongType => return self.failExpectedType(T, node),
else => |e| return e,
};
}
const ParseExprInnerError = error{ ParseZon, OutOfMemory, WrongType };
fn parseExprInner(
self: *@This(),
T: type,
node: Zoir.Node.Index,
) ParseExprInnerError!T {
if (T == Zoir.Node.Index) {
return node;
}
switch (@typeInfo(T)) {
.optional => |optional| if (node.get(self.zoir) == .null) {
return null;
} else {
return try self.parseExprInner(optional.child, node);
},
.bool => return self.parseBool(node),
.int => return self.parseInt(T, node),
.float => return self.parseFloat(T, node),
.@"enum" => return self.parseEnumLiteral(T, node),
.pointer => |pointer| switch (pointer.size) {
.one => {
const result = try self.gpa.create(pointer.child);
errdefer self.gpa.destroy(result);
result.* = try self.parseExprInner(pointer.child, node);
return result;
},
.slice => return self.parseSlicePointer(T, node),
else => comptime unreachable,
},
.array => return self.parseArray(T, node),
.vector => |vector| {
const A = [vector.len]vector.child;
return try self.parseArray(A, node);
},
.@"struct" => |@"struct"| if (@"struct".is_tuple)
return self.parseTuple(T, node)
else
return self.parseStruct(T, node),
.@"union" => return self.parseUnion(T, node),
else => comptime unreachable,
}
}
fn failExpectedType(
self: @This(),
T: type,
node: Zoir.Node.Index,
) error{ ParseZon, OutOfMemory } {
@branchHint(.cold);
return self.failExpectedTypeInner(T, false, node);
}
fn failExpectedTypeInner(
self: @This(),
T: type,
opt: bool,
node: Zoir.Node.Index,
) error{ ParseZon, OutOfMemory } {
_ = valid_types;
switch (@typeInfo(T)) {
.@"struct" => |@"struct"| if (@"struct".is_tuple) {
if (opt) {
return self.failNode(node, "expected optional tuple");
} else {
return self.failNode(node, "expected tuple");
}
} else {
if (opt) {
return self.failNode(node, "expected optional struct");
} else {
return self.failNode(node, "expected struct");
}
},
.@"union" => if (opt) {
return self.failNode(node, "expected optional union");
} else {
return self.failNode(node, "expected union");
},
.array => if (opt) {
return self.failNode(node, "expected optional array");
} else {
return self.failNode(node, "expected array");
},
.pointer => |pointer| switch (pointer.size) {
.one => return self.failExpectedTypeInner(pointer.child, opt, node),
.slice => {
if (pointer.child == u8 and
pointer.attrs.@"const" and
(pointer.sentinel() == null or pointer.sentinel() == 0) and
(pointer.attrs.@"align" == null or pointer.attrs.@"align" == 1))
{
if (opt) {
return self.failNode(node, "expected optional string");
} else {
return self.failNode(node, "expected string");
}
} else {
if (opt) {
return self.failNode(node, "expected optional array");
} else {
return self.failNode(node, "expected array");
}
}
},
else => comptime unreachable,
},
.vector, .bool, .int, .float => if (opt) {
return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(?T)});
} else {
return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)});
},
.@"enum" => if (opt) {
return self.failNode(node, "expected optional enum literal");
} else {
return self.failNode(node, "expected enum literal");
},
.optional => |optional| {
return self.failExpectedTypeInner(optional.child, true, node);
},
else => comptime unreachable,
}
}
fn parseBool(self: @This(), node: Zoir.Node.Index) !bool {
switch (node.get(self.zoir)) {
.true => return true,
.false => return false,
else => return error.WrongType,
}
}
fn parseInt(self: @This(), T: type, node: Zoir.Node.Index) !T {
switch (node.get(self.zoir)) {
.int_literal => |int| switch (int) {
.small => |val| return std.math.cast(T, val) orelse
self.failCannotRepresent(T, node),
.big => |val| return val.toInt(T) catch
self.failCannotRepresent(T, node),
},
.float_literal => |val| return intFromFloatExact(T, val) orelse
self.failCannotRepresent(T, node),
.char_literal => |val| return std.math.cast(T, val) orelse
self.failCannotRepresent(T, node),
else => return error.WrongType,
}
}
fn parseFloat(self: @This(), T: type, node: Zoir.Node.Index) !T {
switch (node.get(self.zoir)) {
.int_literal => |int| switch (int) {
.small => |val| return @floatFromInt(val),
.big => |val| return val.toFloat(T, .nearest_even)[0],
},
.float_literal => |val| return @floatCast(val),
.pos_inf => return std.math.inf(T),
.neg_inf => return -std.math.inf(T),
.nan => return std.math.nan(T),
.char_literal => |val| return @floatFromInt(val),
else => return error.WrongType,
}
}
fn parseEnumLiteral(self: @This(), T: type, node: Zoir.Node.Index) !T {
switch (node.get(self.zoir)) {
.enum_literal => |field_name| {
const enum_info = @typeInfo(T).@"enum";
comptime var kvs_list: [enum_info.field_names.len]struct { []const u8, T } = undefined;
inline for (enum_info.field_names, enum_info.field_values, 0..) |enum_field_name, enum_field_value, i| {
kvs_list[i] = .{ enum_field_name, @fromBackingInt(@intCast(enum_field_value)) };
}
const enum_tags = std.StaticStringMap(T).initComptime(kvs_list);
const field_name_str = field_name.get(self.zoir);
return enum_tags.get(field_name_str) orelse
self.failUnexpected(T, "enum literal", node, null, field_name_str);
},
else => return error.WrongType,
}
}
fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprInnerError!T {
switch (node.get(self.zoir)) {
.string_literal => return self.parseString(T, node),
.array_literal => |nodes| return self.parseSlice(T, nodes),
.empty_literal => return self.parseSlice(T, .{ .start = node, .len = 0 }),
else => return error.WrongType,
}
}
fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) ParseExprInnerError!T {
const ast_node = node.getAstNode(self.zoir);
const pointer = @typeInfo(T).pointer;
var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node);
if (pointer.sentinel() != null) size_hint += 1;
var aw: std.Io.Writer.Allocating = .init(self.gpa);
try aw.ensureUnusedCapacity(size_hint);
defer aw.deinit();
const result = ZonGen.parseStrLit(self.ast, ast_node, &aw.writer) catch return error.OutOfMemory;
switch (result) {
.success => {},
.failure => |err| {
const token = self.ast.nodeMainToken(ast_node);
const raw_string = self.ast.tokenSlice(token);
return self.failTokenFmt(token, @intCast(err.offset()), "{f}", .{err.fmt(raw_string)});
},
}
if (pointer.child != u8 or
pointer.size != .slice or
!pointer.attrs.@"const" or
(pointer.sentinel() != null and pointer.sentinel() != 0) or
(pointer.attrs.@"align" != null and pointer.attrs.@"align" != 1))
{
return error.WrongType;
}
if (pointer.sentinel() != null) {
return aw.toOwnedSliceSentinel(0);
} else {
return aw.toOwnedSlice();
}
}
fn parseSlice(self: *@This(), T: type, nodes: Zoir.Node.Index.Range) !T {
const pointer = @typeInfo(T).pointer;
switch (pointer.size) {
.slice => {},
.one, .many, .c => comptime unreachable,
}
const slice = try self.gpa.allocWithOptions(
pointer.child,
nodes.len,
.fromByteUnitsOptional(pointer.attrs.@"align"),
pointer.sentinel(),
);
errdefer self.gpa.free(slice);
for (slice, 0..) |*elem, i| {
errdefer if (self.options.free_on_error) {
for (slice[0..i]) |item| {
free(self.gpa, item);
}
};
elem.* = try self.parseExpr(pointer.child, nodes.at(@intCast(i)));
}
return slice;
}
fn parseArray(self: *@This(), T: type, node: Zoir.Node.Index) !T {
const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) {
.array_literal => |nodes| nodes,
.empty_literal => .{ .start = node, .len = 0 },
else => return error.WrongType,
};
const array_info = @typeInfo(T).array;
if (nodes.len < array_info.len) {
return self.failNodeFmt(
node,
"expected {} array elements; found {}",
.{ array_info.len, nodes.len },
);
} else if (nodes.len > array_info.len) {
return self.failNodeFmt(
nodes.at(array_info.len),
"index {} outside of array of length {}",
.{ array_info.len, array_info.len },
);
}
var result: T = undefined;
for (&result, 0..) |*elem, i| {
errdefer if (self.options.free_on_error) {
for (result[0..i]) |item| {
free(self.gpa, item);
}
};
elem.* = try self.parseExpr(array_info.child, nodes.at(@intCast(i)));
}
if (array_info.sentinel()) |s| result[result.len] = s;
return result;
}
fn parseStruct(self: *@This(), T: type, node: Zoir.Node.Index) !T {
const repr = node.get(self.zoir);
const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) {
.struct_literal => |nodes| nodes,
.empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } },
else => return error.WrongType,
};
const info = @typeInfo(T).@"struct";
// The special value `comptime_field` indicates that this is actually a comptime field.
const comptime_field = std.math.maxInt(usize);
const field_indices: std.StaticStringMap(usize) = comptime b: {
var kvs_list: [info.field_names.len]struct { []const u8, usize } = undefined;
for (&kvs_list, info.field_names, info.field_attrs, 0..) |*kv, field_name, field_attrs, i| {
kv.* = .{ field_name, if (field_attrs.@"comptime") comptime_field else i };
}
break :b .initComptime(kvs_list);
};
var result: T = undefined;
var field_found: [info.field_names.len]bool = @splat(false);
var initialized: usize = 0;
errdefer if (self.options.free_on_error and info.field_names.len > 0) {
for (fields.names[0..initialized]) |name_runtime| {
switch (field_indices.get(name_runtime.get(self.zoir)) orelse continue) {
inline 0...(info.field_names.len - 1) => |name_index| {
const name = info.field_names[name_index];
free(self.gpa, @field(result, name));
},
else => unreachable,
}
}
};
for (0..fields.names.len) |i| {
const name = fields.names[i].get(self.zoir);
const field_index = field_indices.get(name) orelse {
if (self.options.ignore_unknown_fields) continue;
return self.failUnexpected(T, "field", node, i, name);
};
if (field_index == comptime_field) {
return self.failComptimeField(node, i);
}
// the type checker (it can't be since we made it into an iteration of this loop.)
if (field_found.len == 0) unreachable;
field_found[field_index] = true;
switch (field_index) {
inline 0...(info.field_names.len - 1) => |j| {
if (info.field_attrs[j].@"comptime") unreachable;
@field(result, info.field_names[j]) = try self.parseExpr(
info.field_types[j],
fields.vals.at(@intCast(i)),
);
},
else => unreachable,
}
initialized += 1;
}
inline for (field_found, 0..) |found, i| {
if (!found) {
const field_attrs = info.field_attrs[i];
if (field_attrs.defaultValue(info.field_types[i])) |default| {
@field(result, info.field_names[i]) = default;
} else {
return self.failNodeFmt(
node,
"missing required field {s}",
.{info.field_names[i]},
);
}
}
}
return result;
}
fn parseTuple(self: *@This(), T: type, node: Zoir.Node.Index) !T {
const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) {
.array_literal => |nodes| nodes,
.empty_literal => .{ .start = node, .len = 0 },
else => return error.WrongType,
};
var result: T = undefined;
const info = @typeInfo(T).@"struct";
if (nodes.len > info.field_names.len) {
return self.failNodeFmt(
nodes.at(info.field_names.len),
"index {} outside of tuple length {}",
.{ info.field_names.len, info.field_names.len },
);
}
inline for (0..info.field_names.len) |i| {
if (i >= nodes.len) {
if (info.field_attrs[i].defaultValue(info.field_types[i])) |default| {
@field(result, info.field_names[i]) = default;
} else {
return self.failNodeFmt(node, "missing tuple field with index {}", .{i});
}
} else {
errdefer if (self.options.free_on_error) {
inline for (0..i) |j| {
if (j >= i) break;
free(self.gpa, result[j]);
}
};
if (info.field_attrs[i].@"comptime") {
return self.failComptimeField(node, i);
} else {
result[i] = try self.parseExpr(info.field_types[i], nodes.at(i));
}
}
}
return result;
}
fn parseUnion(self: *@This(), T: type, node: Zoir.Node.Index) !T {
const @"union" = @typeInfo(T).@"union";
if (@"union".field_names.len == 0) comptime unreachable;
const field_indices = b: {
comptime var kvs_list: [@"union".field_names.len]struct { []const u8, usize } = undefined;
inline for (@"union".field_names, 0..) |field_name, i| {
kvs_list[i] = .{ field_name, i };
}
break :b std.StaticStringMap(usize).initComptime(kvs_list);
};
switch (node.get(self.zoir)) {
.enum_literal => |field_name| {
if (@"union".tag_type == null) {
return error.WrongType;
}
// the order of the enum and the order of the union might not match!
const field_index = b: {
const field_name_str = field_name.get(self.zoir);
break :b field_indices.get(field_name_str) orelse
return self.failUnexpected(T, "field", node, null, field_name_str);
};
switch (field_index) {
inline 0...@"union".field_names.len - 1 => |i| {
if (@"union".field_types[i] != void)
return self.failNode(node, "expected union");
return @unionInit(T, @"union".field_names[i], {});
},
else => unreachable,
}
},
.struct_literal => |struct_fields| {
if (struct_fields.names.len != 1) {
return error.WrongType;
}
const field_name = struct_fields.names[0];
const field_name_str = field_name.get(self.zoir);
const field_val = struct_fields.vals.at(0);
const field_index = field_indices.get(field_name_str) orelse
return self.failUnexpected(T, "field", node, 0, field_name_str);
switch (field_index) {
inline 0...@"union".field_names.len - 1 => |i| {
if (@"union".field_types[i] == void) {
return self.failNode(field_val, "expected type 'void'");
} else {
const value = try self.parseExpr(@"union".field_types[i], field_val);
return @unionInit(T, @"union".field_names[i], value);
}
},
else => unreachable,
}
},
else => return error.WrongType,
}
}
fn failTokenFmt(
self: @This(),
token: Ast.TokenIndex,
offset: u32,
comptime fmt: []const u8,
args: anytype,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
return self.failTokenFmtNote(token, offset, fmt, args, null);
}
fn failTokenFmtNote(
self: @This(),
token: Ast.TokenIndex,
offset: u32,
comptime fmt: []const u8,
args: anytype,
note: ?Error.TypeCheckFailure.Note,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
comptime assert(args.len > 0);
if (self.diag) |s| s.type_check = .{
.token = token,
.offset = offset,
.message = std.fmt.allocPrint(self.gpa, fmt, args) catch |err| {
if (note) |n| n.deinit(self.gpa);
return err;
},
.owned = true,
.note = note,
};
return error.ParseZon;
}
fn failNodeFmt(
self: @This(),
node: Zoir.Node.Index,
comptime fmt: []const u8,
args: anytype,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
const token = self.ast.nodeMainToken(node.getAstNode(self.zoir));
return self.failTokenFmt(token, 0, fmt, args);
}
fn failToken(
self: @This(),
failure: Error.TypeCheckFailure,
) error{ParseZon} {
@branchHint(.cold);
if (self.diag) |s| s.type_check = failure;
return error.ParseZon;
}
fn failNode(
self: @This(),
node: Zoir.Node.Index,
message: []const u8,
) error{ParseZon} {
@branchHint(.cold);
const token = self.ast.nodeMainToken(node.getAstNode(self.zoir));
return self.failToken(.{
.token = token,
.offset = 0,
.message = message,
.owned = false,
.note = null,
});
}
fn failCannotRepresent(
self: @This(),
T: type,
node: Zoir.Node.Index,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)});
}
fn failUnexpected(
self: @This(),
T: type,
item_kind: []const u8,
node: Zoir.Node.Index,
field: ?usize,
name: []const u8,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
if (self.diag == null) return error.ParseZon;
const gpa = self.gpa;
const token = if (field) |f| b: {
var buf: [2]Ast.Node.Index = undefined;
const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?;
const field_node = struct_init.ast.fields[f];
break :b self.ast.firstToken(field_node) - 2;
} else self.ast.nodeMainToken(node.getAstNode(self.zoir));
switch (@typeInfo(T)) {
inline .@"struct", .@"union", .@"enum" => |info| {
const note: Error.TypeCheckFailure.Note = if (info.field_names.len == 0) b: {
break :b .{
.token = token,
.offset = 0,
.msg = "none expected",
.owned = false,
};
} else b: {
const msg = "supported: ";
var buf: std.ArrayList(u8) = try .initCapacity(gpa, 64);
defer buf.deinit(gpa);
try buf.appendSlice(gpa, msg);
inline for (info.field_names, 0..) |field_name, i| {
if (i != 0) try buf.appendSlice(gpa, ", ");
try buf.print(gpa, "'{f}'", .{std.zig.fmtIdFlags(field_name, .{
.allow_primitive = true,
.allow_underscore = true,
})});
}
break :b .{
.token = token,
.offset = 0,
.msg = try buf.toOwnedSlice(gpa),
.owned = true,
};
};
return self.failTokenFmtNote(
token,
0,
"unexpected {s} '{s}'",
.{ item_kind, name },
note,
);
},
else => comptime unreachable,
}
}
// the value matched, but doing so doesn't seem to support any real use cases
// so isn't worth the complexity at the moment.
fn failComptimeField(
self: @This(),
node: Zoir.Node.Index,
field: usize,
) error{ OutOfMemory, ParseZon } {
@branchHint(.cold);
if (self.diag == null) return error.ParseZon;
const ast_node = node.getAstNode(self.zoir);
var buf: [2]Ast.Node.Index = undefined;
const token = if (self.ast.fullStructInit(&buf, ast_node)) |struct_init| b: {
const field_node = struct_init.ast.fields[field];
break :b self.ast.firstToken(field_node);
} else b: {
const array_init = self.ast.fullArrayInit(&buf, ast_node).?;
const value_node = array_init.ast.elements[field];
break :b self.ast.firstToken(value_node);
};
return self.failToken(.{
.token = token,
.offset = 0,
.message = "cannot initialize comptime field",
.owned = false,
.note = null,
});
}
}