Compare commits

...

20 Commits

Author SHA1 Message Date
Mason Remaley
c4ae53b6ec
Merge 54950c9c4b into f845fa04a0 2024-11-21 11:49:50 +09:00
Alex Rønne Petersen
f845fa04a0 std.debug: Gracefully handle process_vm_readv() EPERM in MemoryAccessor.read().
Closes #21815.
2024-11-20 23:07:46 +01:00
Mason Remaley
54950c9c4b Fixes parsing integers as floats 2024-11-13 15:15:41 -08:00
Mason Remaley
0f2fde90af Parses structs to known result types 2024-11-12 20:39:31 -08:00
Mason Remaley
d7ba90b8d6 Parses void union fields from enum literals 2024-11-12 19:26:26 -08:00
Mason Remaley
78735d83a0 Fixes bug in explicit union tag parsing 2024-11-11 16:57:27 -08:00
Mason Remaley
ea92351baa Parses unions to explicit return types
Coercing enum literals to unions isn't yet implemented, and some of the
union tests are still skipped due to a bug with explicit tags I still
need to fix
2024-11-11 16:49:08 -08:00
Mason Remaley
30d6f24581 Parses strings to known result type 2024-11-06 15:28:59 -08:00
Mason Remaley
d3f1bb9ab1 Parses tuples to known result types 2024-11-06 14:29:24 -08:00
Mason Remaley
978a693666 WIP tuples: crash on string literals right now 2024-11-06 14:13:58 -08:00
Mason Remaley
24dc5c5126 Parses enums to known result type 2024-11-06 13:11:57 -08:00
Mason Remaley
7da51184d2 Parses arrays to known result type 2024-11-06 01:47:08 -08:00
Mason Remaley
a59ccc08c1 Pulls out separate types into their own functions to make easier to jump around in the code 2024-11-05 20:12:43 -08:00
Mason Remaley
77f6422740 Parses integers to known result types. Not yet tested as those tests use structs. 2024-11-05 19:40:23 -08:00
Mason Remaley
0451d38064 Starts moving optionals to known result type parsing
Need to do ints next
2024-11-05 13:34:39 -08:00
Mason Remaley
b30045c900 Starts moving parsing into known result type 2024-11-05 12:38:41 -08:00
Mason Remaley
e99d07a9ad Gets strings working again 2024-11-04 19:18:42 -08:00
Mason Remaley
3bff320d08 Gets some import zon behavior tests working, skips failing tests for now 2024-11-04 17:50:56 -08:00
Mason Remaley
d2bbcf9e71 Rebases and gets runtime zon import tests passing, comptime import needs to be reworked anyway 2024-11-04 16:54:55 -08:00
Mason Remaley
0cbc043644 Squashes all zon work into one commit for easier rebase
* Gets all tests passing after rebase
* Resolves a bunch of TODOs
* WIP commit while I build release version of main
* Errors on embedded nulls in identifiers
* Parsing identifiers properly without allocating
* Properly frees parsed idents
* Fixes integer negation logic
* Uses existing big int parser
* Cleans up runtime int parsing
* Some investigation into float issue
* More float investigation
* Adds missing test
* Resolves TODOs
* Fixes some error tokens
* Cleans up remaining parsing todos
* Removes TODOs
* Excludes tests format formatting
* Formats
* Parses directly as desired float type
* Moves zon lowering into own file

Records result type for imports, does not yet use

Fixes print zir for imports, better encoding of instruction

Uses the result type to add indirection where necessary

Does not yet remove & from syntax

Removes & from comptime ZON syntax

Yet to be removed from runtime syntax

Fixes after rebase

Allow coercion to slices, not to any other pointer type, adds tests

No longer allows `&` in the runtime parser

Also fixes merge

Fixes bug where you could parse structs as tuples and vice versa

Also cleans up error handling

Reworks runtime error handling

Explains usage of unreachable

Fixes CI failure, and exposes notes in API

Shows notes on integer literal errors when parsing ZON

Fixes free of undefined value if struct with allocated fields is missing a field

Skips tests failing due to C backend issue

Notes why tests are skipped, renames to make easier to use test filter
2024-11-04 14:03:36 -08:00
101 changed files with 7379 additions and 244 deletions

View File

@ -426,7 +426,7 @@ pub fn build(b: *std.Build) !void {
const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index];
const fmt_include_paths = &.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" };
const fmt_exclude_paths = &.{"test/cases"};
const fmt_exclude_paths = &.{ "test/cases", "test/behavior/zon" };
const do_fmt = b.addFmt(.{
.paths = fmt_include_paths,
.exclude_paths = fmt_exclude_paths,

View File

@ -48,7 +48,8 @@ fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool {
switch (linux.E.init(bytes_read)) {
.SUCCESS => return bytes_read == buf.len,
.FAULT => return false,
.INVAL, .PERM, .SRCH => unreachable, // own pid is always valid
.INVAL, .SRCH => unreachable, // own pid is always valid
.PERM => {}, // Known to happen in containers.
.NOMEM => {},
.NOSYS => {}, // QEMU is known not to implement this syscall.
else => unreachable, // unexpected

View File

@ -1579,7 +1579,8 @@ test parseInt {
try std.testing.expectEqual(@as(i5, -16), try std.fmt.parseInt(i5, "-10", 16));
}
fn parseIntWithSign(
/// Like `parseIntWithGenericCharacter`, but with a sign argument.
pub fn parseIntWithSign(
comptime Result: type,
comptime Character: type,
buf: []const Character,

View File

@ -44,6 +44,7 @@ pub const Thread = @import("Thread.zig");
pub const Treap = @import("treap.zig").Treap;
pub const Tz = tz.Tz;
pub const Uri = @import("Uri.zig");
pub const zon = @import("zon.zig");
pub const array_hash_map = @import("array_hash_map.zig");
pub const atomic = @import("atomic.zig");

View File

@ -7,12 +7,13 @@
/// Reference to externally-owned data.
source: [:0]const u8,
mode: Mode,
tokens: TokenList.Slice,
/// The root AST node is assumed to be index 0. Since there can be no
/// references to the root node, this means 0 is available to indicate null.
nodes: NodeList.Slice,
extra_data: []Node.Index,
mode: Mode = .zig,
errors: []const Error,

View File

@ -130,6 +130,8 @@ fn appendRefsAssumeCapacity(astgen: *AstGen, refs: []const Zir.Inst.Ref) void {
}
pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
assert(tree.mode == .zig);
var arena = std.heap.ArenaAllocator.init(gpa);
defer arena.deinit();
@ -8789,36 +8791,22 @@ fn numberLiteral(gz: *GenZir, ri: ResultInfo, node: Ast.Node.Index, source_node:
}
}
fn failWithNumberError(astgen: *AstGen, err: std.zig.number_literal.Error, token: Ast.TokenIndex, bytes: []const u8) InnerError {
const is_float = std.mem.indexOfScalar(u8, bytes, '.') != null;
switch (err) {
.leading_zero => if (is_float) {
return astgen.failTok(token, "number '{s}' has leading zero", .{bytes});
} else {
return astgen.failTokNotes(token, "number '{s}' has leading zero", .{bytes}, &.{
try astgen.errNoteTok(token, "use '0o' prefix for octal literals", .{}),
});
},
.digit_after_base => return astgen.failTok(token, "expected a digit after base prefix", .{}),
.upper_case_base => |i| return astgen.failOff(token, @intCast(i), "base prefix must be lowercase", .{}),
.invalid_float_base => |i| return astgen.failOff(token, @intCast(i), "invalid base for float literal", .{}),
.repeated_underscore => |i| return astgen.failOff(token, @intCast(i), "repeated digit separator", .{}),
.invalid_underscore_after_special => |i| return astgen.failOff(token, @intCast(i), "expected digit before digit separator", .{}),
.invalid_digit => |info| return astgen.failOff(token, @intCast(info.i), "invalid digit '{c}' for {s} base", .{ bytes[info.i], @tagName(info.base) }),
.invalid_digit_exponent => |i| return astgen.failOff(token, @intCast(i), "invalid digit '{c}' in exponent", .{bytes[i]}),
.duplicate_exponent => |i| return astgen.failOff(token, @intCast(i), "duplicate exponent", .{}),
.exponent_after_underscore => |i| return astgen.failOff(token, @intCast(i), "expected digit before exponent", .{}),
.special_after_underscore => |i| return astgen.failOff(token, @intCast(i), "expected digit before '{c}'", .{bytes[i]}),
.trailing_special => |i| return astgen.failOff(token, @intCast(i), "expected digit after '{c}'", .{bytes[i - 1]}),
.trailing_underscore => |i| return astgen.failOff(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
return astgen.failOff(token, @intCast(i), "sign '{c}' cannot follow digit '{c}' in hex base", .{ bytes[i], bytes[i - 1] });
},
.period_after_exponent => |i| return astgen.failOff(token, @intCast(i), "unexpected period after exponent", .{}),
}
fn failWithNumberError(
astgen: *AstGen,
err: std.zig.number_literal.Error,
token: Ast.TokenIndex,
bytes: []const u8,
) InnerError {
const note = err.noteWithSource(bytes);
const notes: []const u32 = if (note) |n| &.{try astgen.errNoteTok(token, "{s}", .{n})} else &.{};
try astgen.appendErrorTokNotesOff(
token,
@as(u32, @intCast(err.offset())),
"{}",
.{err.fmtWithSource(bytes)},
notes,
);
return error.AnalysisFail;
}
fn asmExpr(
@ -9313,7 +9301,18 @@ fn builtinCall(
} else if (str.len == 0) {
return astgen.failTok(str_lit_token, "import path cannot be empty", .{});
}
const result = try gz.addStrTok(.import, str.index, str_lit_token);
const res_ty = try ri.rl.resultType(gz, node) orelse .none;
const payload_index = try addExtra(gz.astgen, Zir.Inst.Import{
.res_ty = res_ty,
.path = str.index,
});
const result = try gz.add(.{
.tag = .import,
.data = .{ .pl_tok = .{
.src_tok = gz.tokenIndexToRelative(str_lit_token),
.payload_index = payload_index,
} },
});
const gop = try astgen.imports.getOrPut(astgen.gpa, str.index);
if (!gop.found_existing) {
gop.value_ptr.* = str_lit_token;
@ -11399,85 +11398,20 @@ fn parseStrLit(
}
}
fn failWithStrLitError(astgen: *AstGen, err: std.zig.string_literal.Error, token: Ast.TokenIndex, bytes: []const u8, offset: u32) InnerError {
fn failWithStrLitError(
astgen: *AstGen,
err: std.zig.string_literal.Error,
token: Ast.TokenIndex,
bytes: []const u8,
offset: u32,
) InnerError {
const raw_string = bytes[offset..];
switch (err) {
.invalid_escape_character => |bad_index| {
return astgen.failOff(
token,
offset + @as(u32, @intCast(bad_index)),
"invalid escape character: '{c}'",
.{raw_string[bad_index]},
offset + @as(u32, @intCast(err.offset())),
"{}",
.{err.fmtWithSource(raw_string)},
);
},
.expected_hex_digit => |bad_index| {
return astgen.failOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected hex digit, found '{c}'",
.{raw_string[bad_index]},
);
},
.empty_unicode_escape_sequence => |bad_index| {
return astgen.failOff(
token,
offset + @as(u32, @intCast(bad_index)),
"empty unicode escape sequence",
.{},
);
},
.expected_hex_digit_or_rbrace => |bad_index| {
return astgen.failOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected hex digit or '}}', found '{c}'",
.{raw_string[bad_index]},
);
},
.invalid_unicode_codepoint => |bad_index| {
return astgen.failOff(
token,
offset + @as(u32, @intCast(bad_index)),
"unicode escape does not correspond to a valid unicode scalar value",
.{},
);
},
.expected_lbrace => |bad_index| {
return astgen.failOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected '{{', found '{c}",
.{raw_string[bad_index]},
);
},
.expected_rbrace => |bad_index| {
return astgen.failOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected '}}', found '{c}",
.{raw_string[bad_index]},
);
},
.expected_single_quote => |bad_index| {
return astgen.failOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected single quote ('), found '{c}",
.{raw_string[bad_index]},
);
},
.invalid_character => |bad_index| {
return astgen.failOff(
token,
offset + @as(u32, @intCast(bad_index)),
"invalid byte in string or character literal: '{c}'",
.{raw_string[bad_index]},
);
},
.empty_char_literal => {
return astgen.failOff(token, offset, "empty character literal", .{});
},
}
}
fn failNode(
@ -11595,7 +11529,7 @@ fn appendErrorTokNotesOff(
comptime format: []const u8,
args: anytype,
notes: []const u32,
) !void {
) Allocator.Error!void {
@branchHint(.cold);
const gpa = astgen.gpa;
const string_bytes = &astgen.string_bytes;
@ -11791,32 +11725,17 @@ fn strLitAsString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !IndexSlice {
}
fn strLitNodeAsString(astgen: *AstGen, node: Ast.Node.Index) !IndexSlice {
const tree = astgen.tree;
const node_datas = tree.nodes.items(.data);
const start = node_datas[node].lhs;
const end = node_datas[node].rhs;
const gpa = astgen.gpa;
const data = astgen.tree.nodes.items(.data);
const string_bytes = &astgen.string_bytes;
const str_index = string_bytes.items.len;
// First line: do not append a newline.
var tok_i = start;
{
const slice = tree.tokenSlice(tok_i);
const line_bytes = slice[2..];
try string_bytes.appendSlice(gpa, line_bytes);
tok_i += 1;
}
// Following lines: each line prepends a newline.
while (tok_i <= end) : (tok_i += 1) {
const slice = tree.tokenSlice(tok_i);
const line_bytes = slice[2..];
try string_bytes.ensureUnusedCapacity(gpa, line_bytes.len + 1);
string_bytes.appendAssumeCapacity('\n');
string_bytes.appendSliceAssumeCapacity(line_bytes);
var parser = std.zig.string_literal.multilineParser(string_bytes.writer(gpa));
var tok_i = data[node].lhs;
while (tok_i <= data[node].rhs) : (tok_i += 1) {
try parser.line(astgen.tree.tokenSlice(tok_i));
}
const len = string_bytes.items.len - str_index;
try string_bytes.append(gpa, 0);
return IndexSlice{

View File

@ -1667,7 +1667,7 @@ pub const Inst = struct {
.func = .pl_node,
.func_inferred = .pl_node,
.func_fancy = .pl_node,
.import = .str_tok,
.import = .pl_tok,
.int = .int,
.int_big = .str,
.float = .float,
@ -3572,6 +3572,13 @@ pub const Inst = struct {
/// If `.none`, restore unconditionally.
operand: Ref,
};
pub const Import = struct {
/// The result type of the import, or `.none` if none was available.
res_ty: Ref,
/// The import path.
path: NullTerminatedString,
};
};
pub const SpecialProng = enum { none, @"else", under };

View File

@ -58,8 +58,83 @@ pub const Error = union(enum) {
invalid_exponent_sign: usize,
/// Period comes directly after exponent.
period_after_exponent: usize,
pub fn fmtWithSource(self: Error, bytes: []const u8) std.fmt.Formatter(formatErrorWithSource) {
return .{ .data = .{ .err = self, .bytes = bytes } };
}
pub fn noteWithSource(self: Error, bytes: []const u8) ?[]const u8 {
if (self == .leading_zero) {
const is_float = std.mem.indexOfScalar(u8, bytes, '.') != null;
if (!is_float) return "use '0o' prefix for octal literals";
}
return null;
}
pub fn offset(self: Error) usize {
return switch (self) {
.leading_zero => 0,
.digit_after_base => 0,
.upper_case_base => |i| i,
.invalid_float_base => |i| i,
.repeated_underscore => |i| i,
.invalid_underscore_after_special => |i| i,
.invalid_digit => |e| e.i,
.invalid_digit_exponent => |i| i,
.duplicate_period => 0,
.duplicate_exponent => |i| i,
.exponent_after_underscore => |i| i,
.special_after_underscore => |i| i,
.trailing_special => |i| i,
.trailing_underscore => |i| i,
.invalid_character => |i| i,
.invalid_exponent_sign => |i| i,
.period_after_exponent => |i| i,
};
}
};
const FormatWithSource = struct {
bytes: []const u8,
err: Error,
};
fn formatErrorWithSource(
self: FormatWithSource,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = fmt;
switch (self.err) {
.leading_zero => try writer.print("number '{s}' has leading zero", .{self.bytes}),
.digit_after_base => try writer.writeAll("expected a digit after base prefix"),
.upper_case_base => try writer.writeAll("base prefix must be lowercase"),
.invalid_float_base => try writer.writeAll("invalid base for float literal"),
.repeated_underscore => try writer.writeAll("repeated digit separator"),
.invalid_underscore_after_special => try writer.writeAll("expected digit before digit separator"),
.invalid_digit => |info| try writer.print("invalid digit '{c}' for {s} base", .{ self.bytes[info.i], @tagName(info.base) }),
.invalid_digit_exponent => |i| try writer.print("invalid digit '{c}' in exponent", .{self.bytes[i]}),
.duplicate_exponent => try writer.writeAll("duplicate exponent"),
.exponent_after_underscore => try writer.writeAll("expected digit before exponent"),
.special_after_underscore => |i| try writer.print("expected digit before '{c}'", .{self.bytes[i]}),
.trailing_special => |i| try writer.print("expected digit after '{c}'", .{self.bytes[i - 1]}),
.trailing_underscore => try writer.writeAll("trailing digit separator"),
.duplicate_period => try writer.writeAll("duplicate period"),
.invalid_character => try writer.writeAll("invalid character"),
.invalid_exponent_sign => |i| {
const hex = self.bytes.len >= 2 and self.bytes[0] == '0' and self.bytes[1] == 'x';
if (hex) {
try writer.print("sign '{c}' cannot follow digit '{c}' in hex base", .{ self.bytes[i], self.bytes[i - 1] });
} else {
try writer.print("sign '{c}' cannot follow digit '{c}' in current base", .{ self.bytes[i], self.bytes[i - 1] });
}
},
.period_after_exponent => try writer.writeAll("unexpected period after exponent"),
}
}
/// Parse Zig number literal accepted by fmt.parseInt, fmt.parseFloat and big_int.setString.
/// Valid for any input.
pub fn parseNumberLiteral(bytes: []const u8) Result {

View File

@ -38,8 +38,74 @@ pub const Error = union(enum) {
invalid_character: usize,
/// `''`. Not returned for string literals.
empty_char_literal,
pub fn fmtWithSource(self: Error, raw_string: []const u8) std.fmt.Formatter(formatErrorWithSource) {
return .{ .data = .{ .err = self, .raw_string = raw_string } };
}
pub fn offset(self: Error) usize {
return switch (self) {
.invalid_escape_character => |i| i,
.expected_hex_digit => |i| i,
.empty_unicode_escape_sequence => |i| i,
.expected_hex_digit_or_rbrace => |i| i,
.invalid_unicode_codepoint => |i| i,
.expected_lbrace => |i| i,
.expected_rbrace => |i| i,
.expected_single_quote => |i| i,
.invalid_character => |i| i,
.empty_char_literal => 0,
};
}
};
const FormatWithSource = struct {
raw_string: []const u8,
err: Error,
};
fn formatErrorWithSource(
self: FormatWithSource,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = fmt;
switch (self.err) {
.invalid_escape_character => |bad_index| {
try writer.print("invalid escape character: '{c}'", .{self.raw_string[bad_index]});
},
.expected_hex_digit => |bad_index| {
try writer.print("expected hex digit, found '{c}'", .{self.raw_string[bad_index]});
},
.empty_unicode_escape_sequence => {
try writer.writeAll("empty unicode escape sequence");
},
.expected_hex_digit_or_rbrace => |bad_index| {
try writer.print("expected hex digit or '}}', found '{c}'", .{self.raw_string[bad_index]});
},
.invalid_unicode_codepoint => {
try writer.writeAll("unicode escape does not correspond to a valid unicode scalar value");
},
.expected_lbrace => |bad_index| {
try writer.print("expected '{{', found '{c}", .{self.raw_string[bad_index]});
},
.expected_rbrace => |bad_index| {
try writer.print("expected '}}', found '{c}", .{self.raw_string[bad_index]});
},
.expected_single_quote => |bad_index| {
try writer.print("expected single quote ('), found '{c}", .{self.raw_string[bad_index]});
},
.invalid_character => |bad_index| {
try writer.print("invalid byte in string or character literal: '{c}'", .{self.raw_string[bad_index]});
},
.empty_char_literal => {
try writer.print("empty character literal", .{});
},
}
}
/// Asserts the slice starts and ends with single-quotes.
/// Returns an error if there is not exactly one UTF-8 codepoint in between.
pub fn parseCharLiteral(slice: []const u8) ParsedCharLiteral {
@ -247,7 +313,7 @@ test parseCharLiteral {
/// Parses `bytes` as a Zig string literal and writes the result to the std.io.Writer type.
/// Asserts `bytes` has '"' at beginning and end.
pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result {
pub fn parseWrite(writer: anytype, bytes: []const u8) !Result {
assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"');
var index: usize = 1;
@ -312,3 +378,141 @@ test parseAlloc {
try expect(eql(u8, "foo", try parseAlloc(alloc, "\"f\x6f\x6f\"")));
try expect(eql(u8, "f💯", try parseAlloc(alloc, "\"f\u{1f4af}\"")));
}
/// Parses one line at a time of a multiline Zig string literal to a std.io.Writer type. Does not append a null terminator.
pub fn MultilineParser(comptime Writer: type) type {
return struct {
writer: Writer,
first_line: bool,
pub fn init(writer: Writer) @This() {
return .{
.writer = writer,
.first_line = true,
};
}
/// Parse one line of a multiline string, writing the result to the writer prepending a newline if necessary.
///
/// Asserts bytes begins with "\\". The line may be terminated with '\n' or "\r\n", but may not contain any interior newlines.
/// contain any interior newlines.
pub fn line(self: *@This(), bytes: []const u8) Writer.Error!void {
assert(bytes.len >= 2 and bytes[0] == '\\' and bytes[1] == '\\');
if (self.first_line) {
self.first_line = false;
} else {
try self.writer.writeByte('\n');
}
try self.writer.writeAll(bytes[2..]);
}
};
}
pub fn multilineParser(writer: anytype) MultilineParser(@TypeOf(writer)) {
return MultilineParser(@TypeOf(writer)).init(writer);
}
test "parse multiline" {
// Varying newlines
{
{
var parsed = std.ArrayList(u8).init(std.testing.allocator);
defer parsed.deinit();
const writer = parsed.writer();
var parser = multilineParser(writer);
try parser.line("\\\\foo");
try std.testing.expectEqualStrings("foo", parsed.items);
try parser.line("\\\\bar");
try std.testing.expectEqualStrings("foo\nbar", parsed.items);
}
{
var parsed = std.ArrayList(u8).init(std.testing.allocator);
defer parsed.deinit();
const writer = parsed.writer();
var parser = multilineParser(writer);
try parser.line("\\\\foo");
try std.testing.expectEqualStrings("foo", parsed.items);
try parser.line("\\\\bar\n");
try std.testing.expectEqualStrings("foo\nbar", parsed.items);
}
{
var parsed = std.ArrayList(u8).init(std.testing.allocator);
defer parsed.deinit();
const writer = parsed.writer();
var parser = multilineParser(writer);
try parser.line("\\\\foo");
try std.testing.expectEqualStrings("foo", parsed.items);
try parser.line("\\\\bar\r\n");
try std.testing.expectEqualStrings("foo\nbar", parsed.items);
}
{
var parsed = std.ArrayList(u8).init(std.testing.allocator);
defer parsed.deinit();
const writer = parsed.writer();
var parser = multilineParser(writer);
try parser.line("\\\\foo\n");
try std.testing.expectEqualStrings("foo", parsed.items);
try parser.line("\\\\bar");
try std.testing.expectEqualStrings("foo\nbar", parsed.items);
}
{
var parsed = std.ArrayList(u8).init(std.testing.allocator);
defer parsed.deinit();
const writer = parsed.writer();
var parser = multilineParser(writer);
try parser.line("\\\\foo\r\n");
try std.testing.expectEqualStrings("foo", parsed.items);
try parser.line("\\\\bar");
try std.testing.expectEqualStrings("foo\nbar", parsed.items);
}
}
// Empty lines
{
{
var parsed = std.ArrayList(u8).init(std.testing.allocator);
defer parsed.deinit();
const writer = parsed.writer();
var parser = multilineParser(writer);
try parser.line("\\\\");
try std.testing.expectEqualStrings("", parsed.items);
try parser.line("\\\\");
try std.testing.expectEqualStrings("\n", parsed.items);
try parser.line("\\\\foo");
try std.testing.expectEqualStrings("\n\nfoo", parsed.items);
try parser.line("\\\\bar");
try std.testing.expectEqualStrings("\n\nfoo\nbar", parsed.items);
}
{
var parsed = std.ArrayList(u8).init(std.testing.allocator);
defer parsed.deinit();
const writer = parsed.writer();
var parser = multilineParser(writer);
try parser.line("\\\\foo");
try std.testing.expectEqualStrings("foo", parsed.items);
try parser.line("\\\\");
try std.testing.expectEqualStrings("foo\n", parsed.items);
try parser.line("\\\\bar");
try std.testing.expectEqualStrings("foo\n\nbar", parsed.items);
try parser.line("\\\\");
try std.testing.expectEqualStrings("foo\n\nbar\n", parsed.items);
}
}
// No escapes
{
var parsed = std.ArrayList(u8).init(std.testing.allocator);
defer parsed.deinit();
const writer = parsed.writer();
var parser = multilineParser(writer);
try parser.line("\\\\no \\n escape");
try std.testing.expectEqualStrings("no \\n escape", parsed.items);
try parser.line("\\\\still no \\n escape");
try std.testing.expectEqualStrings("no \\n escape\nstill no \\n escape", parsed.items);
}
}

97
lib/std/zon.zig Normal file
View File

@ -0,0 +1,97 @@
//! ZON serialization and deserialization.
//!
//! # ZON
//! ZON, or Zig Object Notation, is a subset* of Zig used for data storage. ZON contains no type
//! names.
//!
//! Supported Zig primitives:
//! * boolean literals
//! * number literals (including `nan` and `inf`)
//! * character literals
//! * enum literals
//! * `null` and `void` literals
//! * string literals
//! * multiline string literals
//!
//! Supported Zig containers:
//! * anonymous struct literals
//! * anonymous tuple literals
//! * slices
//! * notated as a reference to a tuple literal
//! * this syntax will likely be removed in the future, at which point ZON will not distinguish
//! between slices and tuples
//!
//! Here is an example ZON object:
//! ```zon
//! .{
//! .a = 1.5,
//! .b = "hello, world!",
//! .c = .{ true, false },
//! .d = &.{ 1, 2, 3 },
//! }
//! ```
//!
//! Individual primitives are also valid ZON, for example:
//! ```zon
//! "This string is a valid ZON object."
//! ```
//!
//! \* ZON is not currently a true subset of Zig, because it supports `nan` and
//! `inf` literals, which Zig does not.
//!
//! # Deserialization
//!
//! The simplest way to deserialize ZON at runtime is `parseFromSlice`. (For reading ZON at
//! comptime, you can use `@import`.)
//!
//! If you need lower level control, or more detailed diagnostics, you can generate the AST yourself
//! with `std.zig.Ast.parse` and then deserialize it with:
//! * `parseFromAst`
//! * `parseFromAstNoAlloc`
//!
//! If you'd like to deserialize just part of an AST, you can use:
//! * `parseFromAstNode`
//! * `parseFromAstNodeNoAlloc`
//!
//! If you need lower level control than provided by this module, you can operate directly on the
//! results of `std.zig.Ast.parse`.
//!
//!
//! # Serialization
//!
//! The simplest way to serialize to ZON is to call `stringify`.
//!
//! If you need to serialize recursive types, the following functions are also provided:
//! * `stringifyMaxDepth`
//! * `stringifyArbitraryDepth`
//!
//! If you need more control over the serialization process, for example to control which fields are
//! serialized, configure fields individually, or to stringify ZON values that do not exist in
//! memory, you can use `Stringifier`.
//!
//! Note that serializing floats with more than 64 bits may result in a loss of precision
//! (see https://github.com/ziglang/zig/issues/1181).
pub const ParseOptions = @import("zon/parse.zig").ParseOptions;
pub const ParseStatus = @import("zon/parse.zig").ParseStatus;
pub const parseFromSlice = @import("zon/parse.zig").parseFromSlice;
pub const parseFromAst = @import("zon/parse.zig").parseFromAst;
pub const parseFromAstNoAlloc = @import("zon/parse.zig").parseFromAstNoAlloc;
pub const parseFromAstNode = @import("zon/parse.zig").parseFromAstNode;
pub const parseFromAstNodeNoAlloc = @import("zon/parse.zig").parseFromAstNodeNoAlloc;
pub const parseFree = @import("zon/parse.zig").parseFree;
pub const StringifierOptions = @import("zon/stringify.zig").StringifierOptions;
pub const StringifyValueOptions = @import("zon/stringify.zig").StringifyValueOptions;
pub const StringifyOptions = @import("zon/stringify.zig").StringifyOptions;
pub const StringifyContainerOptions = @import("zon/stringify.zig").StringifyContainerOptions;
pub const Stringifier = @import("zon/stringify.zig").Stringifier;
pub const stringify = @import("zon/stringify.zig").stringify;
pub const stringifyMaxDepth = @import("zon/stringify.zig").stringifyMaxDepth;
pub const stringifyArbitraryDepth = @import("zon/stringify.zig").stringifyArbitraryDepth;
pub const stringifier = @import("zon/stringify.zig").stringifier;
test {
_ = @import("zon/parse.zig");
_ = @import("zon/stringify.zig");
}

2843
lib/std/zon/parse.zig Normal file

File diff suppressed because it is too large Load Diff

1920
lib/std/zon/stringify.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1018,6 +1018,11 @@ pub const Inst = struct {
pub fn toType(ref: Ref) Type {
return Type.fromInterned(ref.toInterned().?);
}
pub fn toTypeAllowNone(ref: Ref) ?Type {
if (ref == .none) return null;
return ref.toType();
}
};
/// All instructions have an 8-byte payload, which is contained within

View File

@ -2192,8 +2192,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
try comp.astgen_work_queue.ensureUnusedCapacity(zcu.import_table.count());
for (zcu.import_table.values()) |file_index| {
if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue;
const file = zcu.fileByIndex(file_index);
if (file.mode == .zig) {
comp.astgen_work_queue.writeItemAssumeCapacity(file_index);
}
}
if (comp.file_system_inputs) |fsi| {
for (zcu.import_table.values()) |file_index| {
const file = zcu.fileByIndex(file_index);
@ -4150,6 +4153,7 @@ fn workerAstGenFile(
wg: *WaitGroup,
src: Zcu.AstGenSrc,
) void {
assert(file.mode == .zig);
const child_prog_node = prog_node.start(file.sub_file_path, 0);
defer child_prog_node.end();
@ -4202,7 +4206,7 @@ fn workerAstGenFile(
const imported_path_digest = pt.zcu.filePathDigest(res.file_index);
break :blk .{ res, imported_path_digest };
};
if (import_result.is_new) {
if (import_result.is_new and import_result.file.mode == .zig) {
log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{
file.sub_file_path, import_path, import_result.file.sub_file_path,
});

View File

@ -14,6 +14,7 @@ pub const Digest = [Hash.digest_length]u8;
pub const multihash_len = 1 + 1 + Hash.digest_length;
pub const multihash_hex_digest_len = 2 * multihash_len;
pub const MultiHashHexDigest = [multihash_hex_digest_len]u8;
const AstGen = std.zig.AstGen;
pub const Dependency = struct {
location: Location,
@ -456,7 +457,6 @@ const Parse = struct {
return duped;
}
/// TODO: try to DRY this with AstGen.parseStrLit
fn parseStrLit(
p: *Parse,
token: Ast.TokenIndex,
@ -470,95 +470,13 @@ const Parse = struct {
buf.* = buf_managed.moveToUnmanaged();
switch (try result) {
.success => {},
.failure => |err| try p.appendStrLitError(err, token, bytes, offset),
}
}
/// TODO: try to DRY this with AstGen.failWithStrLitError
fn appendStrLitError(
p: *Parse,
err: std.zig.string_literal.Error,
token: Ast.TokenIndex,
bytes: []const u8,
offset: u32,
) Allocator.Error!void {
const raw_string = bytes[offset..];
switch (err) {
.invalid_escape_character => |bad_index| {
try p.appendErrorOff(
.failure => |err| try appendErrorOff(
p,
token,
offset + @as(u32, @intCast(bad_index)),
"invalid escape character: '{c}'",
.{raw_string[bad_index]},
);
},
.expected_hex_digit => |bad_index| {
try p.appendErrorOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected hex digit, found '{c}'",
.{raw_string[bad_index]},
);
},
.empty_unicode_escape_sequence => |bad_index| {
try p.appendErrorOff(
token,
offset + @as(u32, @intCast(bad_index)),
"empty unicode escape sequence",
.{},
);
},
.expected_hex_digit_or_rbrace => |bad_index| {
try p.appendErrorOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected hex digit or '}}', found '{c}'",
.{raw_string[bad_index]},
);
},
.invalid_unicode_codepoint => |bad_index| {
try p.appendErrorOff(
token,
offset + @as(u32, @intCast(bad_index)),
"unicode escape does not correspond to a valid unicode scalar value",
.{},
);
},
.expected_lbrace => |bad_index| {
try p.appendErrorOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected '{{', found '{c}",
.{raw_string[bad_index]},
);
},
.expected_rbrace => |bad_index| {
try p.appendErrorOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected '}}', found '{c}",
.{raw_string[bad_index]},
);
},
.expected_single_quote => |bad_index| {
try p.appendErrorOff(
token,
offset + @as(u32, @intCast(bad_index)),
"expected single quote ('), found '{c}",
.{raw_string[bad_index]},
);
},
.invalid_character => |bad_index| {
try p.appendErrorOff(
token,
offset + @as(u32, @intCast(bad_index)),
"invalid byte in string or character literal: '{c}'",
.{raw_string[bad_index]},
);
},
.empty_char_literal => {
try p.appendErrorOff(token, offset, "empty character literal", .{});
},
offset + @as(u32, @intCast(err.offset())),
"{}",
err.fmtWithSource(raw_string),
),
}
}

View File

@ -478,6 +478,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
.status = .never_loaded,
.prev_status = .never_loaded,
.mod = new,
.mode = .zig,
};
break :b new;
};

View File

@ -192,6 +192,7 @@ const Alignment = InternPool.Alignment;
const AnalUnit = InternPool.AnalUnit;
const ComptimeAllocIndex = InternPool.ComptimeAllocIndex;
const Cache = std.Build.Cache;
const zon = @import("zon.zig");
pub const default_branch_quota = 1000;
pub const default_reference_trace_len = 2;
@ -14475,9 +14476,10 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
const extra = sema.code.extraData(Zir.Inst.Import, inst_data.payload_index).data;
const operand_src = block.tokenOffset(inst_data.src_tok);
const operand = inst_data.get(sema.code);
const operand = sema.code.nullTerminatedString(extra.path);
const result = pt.importFile(block.getFileScope(zcu), operand) catch |err| switch (err) {
error.ImportOutsideModulePath => {
@ -14494,12 +14496,40 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ operand, @errorName(err) });
},
};
switch (result.file.mode) {
.zig => {
try sema.declareDependency(.{ .file = result.file_index });
try pt.ensureFileAnalyzed(result.file_index);
const ty = zcu.fileRootType(result.file_index);
try sema.declareDependency(.{ .interned = ty });
try sema.addTypeReferenceEntry(operand_src, ty);
return Air.internedToRef(ty);
},
.zon => {
_ = result.file.getTree(zcu.gpa) catch |err| {
// TODO: these errors are file system errors; make sure an update() will
// retry this and not cache the file system error, which may be transient.
return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ result.file.sub_file_path, @errorName(err) });
};
if (extra.res_ty == .none) {
return sema.fail(block, operand_src, "import ZON must have a known result type", .{});
}
const res_ty_inst = try sema.resolveInst(extra.res_ty);
const res_ty = try sema.analyzeAsType(block, operand_src, res_ty_inst);
if (res_ty.isGenericPoison()) {
return sema.fail(block, operand_src, "import ZON must have a known result type", .{});
}
const interned = try zon.lower(
sema,
result.file,
result.file_index,
res_ty,
);
return Air.internedToRef(interned);
},
}
}
fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {

View File

@ -453,6 +453,9 @@ pub const File = struct {
/// successful, this field is unloaded.
prev_zir: ?*Zir = null,
/// Whether the file is Zig or ZON. This filed is always populated.
mode: Ast.Mode,
pub const Status = enum {
never_loaded,
retryable_failure,
@ -472,6 +475,17 @@ pub const File = struct {
root: *Package.Module,
};
pub fn modeFromPath(path: []const u8) Ast.Mode {
if (std.mem.endsWith(u8, path, ".zon")) {
return .zon;
} else if (std.mem.endsWith(u8, path, ".zig")) {
return .zig;
} else {
// `Module.importFile` rejects all other extensions
unreachable;
}
}
pub fn unload(file: *File, gpa: Allocator) void {
file.unloadTree(gpa);
file.unloadSource(gpa);
@ -546,7 +560,7 @@ pub const File = struct {
if (file.tree_loaded) return &file.tree;
const source = try file.getSource(gpa);
file.tree = try Ast.parse(gpa, source.bytes, .zig);
file.tree = try Ast.parse(gpa, source.bytes, file.mode);
file.tree_loaded = true;
return &file.tree;
}
@ -663,6 +677,7 @@ pub const File = struct {
pub const Index = InternPool.FileIndex;
};
/// Represents the contents of a file loaded with `@embedFile`.
pub const EmbedFile = struct {
/// Relative to the owning module's root directory.
sub_file_path: InternPool.NullTerminatedString,
@ -2087,6 +2102,12 @@ pub const LazySrcLoc = struct {
break :inst .{ info.file, info.inst };
};
const file = zcu.fileByIndex(file_index);
// If we're relative to .main_struct_inst, we know the ast node is the root and don't need to resolve the ZIR,
// which may not exist e.g. in the case of errors in ZON files.
if (zir_inst == .main_struct_inst) return .{ file, 0 };
// Otherwise, make sure ZIR is loaded.
assert(file.zir_loaded);
const zir = file.zir;

View File

@ -1066,6 +1066,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
const zcu = pt.zcu;
const gpa = zcu.gpa;
const file = zcu.fileByIndex(file_index);
assert(file.mode == .zig);
assert(zcu.fileRootType(file_index) == .none);
if (file.status != .success_zir) {
@ -1463,6 +1464,7 @@ pub fn importPkg(pt: Zcu.PerThread, mod: *Module) !Zcu.ImportFileResult {
.status = .never_loaded,
.prev_status = .never_loaded,
.mod = mod,
.mode = Zcu.File.modeFromPath(sub_file_path),
};
try new_file.addReference(zcu, .{ .root = mod });
@ -1493,7 +1495,9 @@ pub fn importFile(
if (mod.deps.get(import_string)) |pkg| {
return pt.importPkg(pkg);
}
if (!std.mem.endsWith(u8, import_string, ".zig")) {
if (!std.mem.endsWith(u8, import_string, ".zig") and
!std.mem.endsWith(u8, import_string, ".zon"))
{
return error.ModuleNotFound;
}
const gpa = zcu.gpa;
@ -1574,6 +1578,7 @@ pub fn importFile(
.status = .never_loaded,
.prev_status = .never_loaded,
.mod = mod,
.mode = Zcu.File.modeFromPath(sub_file_path),
};
return .{

View File

@ -6037,6 +6037,7 @@ fn cmdAstCheck(
.tree = undefined,
.zir = undefined,
.mod = undefined,
.mode = .zig,
};
if (zig_source_file) |file_name| {
var f = fs.cwd().openFile(file_name, .{}) catch |err| {
@ -6361,6 +6362,7 @@ fn cmdDumpZir(
.tree = undefined,
.zir = try Zcu.loadZirCache(gpa, f),
.mod = undefined,
.mode = .zig,
};
defer file.zir.deinit(gpa);
@ -6433,6 +6435,7 @@ fn cmdChangelist(
.tree = undefined,
.zir = undefined,
.mod = undefined,
.mode = Zcu.File.modeFromPath(old_source_file),
};
file.mod = try Package.Module.createLimited(arena, .{

View File

@ -488,7 +488,6 @@ const Writer = struct {
.enum_literal,
.decl_ref,
.decl_val,
.import,
.ret_err_value,
.ret_err_value_code,
.param_anytype,
@ -515,6 +514,8 @@ const Writer = struct {
.declaration => try self.writeDeclaration(stream, inst),
.extended => try self.writeExtended(stream, inst),
.import => try self.writeImport(stream, inst),
}
}
@ -2979,4 +2980,13 @@ const Writer = struct {
try stream.writeByte('\n');
}
}
fn writeImport(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
const extra = self.code.extraData(Zir.Inst.Import, inst_data.payload_index).data;
try self.writeInstRef(stream, extra.res_ty);
const import_path = self.code.nullTerminatedString(extra.path);
try stream.print(", \"{}\") ", .{std.zig.fmtEscapes(import_path)});
try self.writeSrcTok(stream, inst_data.src_tok);
}
};

1292
src/zon.zig Normal file

File diff suppressed because it is too large Load Diff

322
test/behavior/zon.zig Normal file
View File

@ -0,0 +1,322 @@
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualDeep = std.testing.expectEqualDeep;
const expectEqualSlices = std.testing.expectEqualSlices;
const expectEqualStrings = std.testing.expectEqualStrings;
test "void" {
try expectEqual({}, @as(void, @import("zon/void.zon")));
}
test "bool" {
try expectEqual(true, @as(bool, @import("zon/true.zon")));
try expectEqual(false, @as(bool, @import("zon/false.zon")));
}
test "optional" {
const some: ?u32 = @import("zon/some.zon");
const none: ?u32 = @import("zon/none.zon");
const @"null": @TypeOf(null) = @import("zon/none.zon");
try expectEqual(@as(u32, 10), some);
try expectEqual(@as(?u32, null), none);
try expectEqual(null, @"null");
}
test "union" {
// No tag
{
const Union = union {
x: f32,
y: bool,
z: void,
};
const union1: Union = @import("zon/union1.zon");
const union2: Union = @import("zon/union2.zon");
const union3: Union = @import("zon/union3.zon");
const union4: Union = @import("zon/union4.zon");
try expectEqual(union1.x, 1.5);
try expectEqual(union2.y, true);
try expectEqual(union3.z, {});
try expectEqual(union4.z, {});
}
// Inferred tag
{
const Union = union(enum) {
x: f32,
y: bool,
z: void,
};
const union1: Union = @import("zon/union1.zon");
const union2: Union = @import("zon/union2.zon");
const union3: Union = @import("zon/union3.zon");
const union4: Union = @import("zon/union4.zon");
try expectEqual(union1.x, 1.5);
try expectEqual(union2.y, true);
try expectEqual(union3.z, {});
try expectEqual(union4.z, {});
}
// Explicit tag
{
const Tag = enum(i128) {
x = -1,
y = 2,
z = 1,
};
const Union = union(Tag) {
x: f32,
y: bool,
z: void,
};
const union1: Union = @import("zon/union1.zon");
const union2: Union = @import("zon/union2.zon");
const union3: Union = @import("zon/union3.zon");
const union4: Union = @import("zon/union4.zon");
try expectEqual(union1.x, 1.5);
try expectEqual(union2.y, true);
try expectEqual(union3.z, {});
try expectEqual(union4.z, {});
}
}
test "struct" {
const Vec0 = struct {};
const Vec1 = struct { x: f32 };
const Vec2 = struct { x: f32, y: f32 };
const Escaped = struct { @"0": f32, foo: f32 };
try expectEqual(Vec0{}, @as(Vec0, @import("zon/vec0.zon")));
try expectEqual(Vec1{ .x = 1.5 }, @as(Vec1, @import("zon/vec1.zon")));
try expectEqual(Vec2{ .x = 1.5, .y = 2 }, @as(Vec2, @import("zon/vec2.zon")));
try expectEqual(Escaped{ .@"0" = 1.5, .foo = 2 }, @as(Escaped, @import("zon/escaped_struct.zon")));
}
test "struct default fields" {
const Vec3 = struct {
x: f32,
y: f32,
z: f32 = 123.4,
};
try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, @as(Vec3, @import("zon/vec2.zon")));
const ascribed: Vec3 = @import("zon/vec2.zon");
try expectEqual(Vec3{ .x = 1.5, .y = 2.0, .z = 123.4 }, ascribed);
}
test "struct enum field" {
const Struct = struct {
x: enum { x, y, z },
};
try expectEqual(Struct{ .x = .z }, @as(Struct, @import("zon/enum_field.zon")));
}
test "tuple" {
const Tuple = struct { f32, bool, []const u8, u16 };
try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon")));
}
test "char" {
try expectEqual(@as(u8, 'a'), @as(u8, @import("zon/a.zon")));
try expectEqual(@as(u8, 'z'), @as(u8, @import("zon/z.zon")));
try expectEqual(@as(i8, -'a'), @as(i8, @import("zon/a_neg.zon")));
}
test "arrays" {
try expectEqual([0]u8{}, @as([0]u8, @import("zon/vec0.zon")));
try expectEqual([0:1]u8{}, @as([0:1]u8, @import("zon/vec0.zon")));
try expectEqual(1, @as([0:1]u8, @import("zon/vec0.zon"))[0]);
try expectEqual([4]u8{ 'a', 'b', 'c', 'd' }, @as([4]u8, @import("zon/array.zon")));
try expectEqual([4:2]u8{ 'a', 'b', 'c', 'd' }, @as([4:2]u8, @import("zon/array.zon")));
try expectEqual(2, @as([4:2]u8, @import("zon/array.zon"))[4]);
}
test "slices, arrays, tuples" {
return error.SkipZigTest;
// {
// const expected_slice: []const u8 = &.{};
// const found_slice: []const u8 = @import("zon/slice-empty.zon");
// try expectEqualSlices(u8, expected_slice, found_slice);
// const expected_array: [0]u8 = .{};
// const found_array: [0]u8 = @import("zon/slice-empty.zon");
// try expectEqual(expected_array, found_array);
// const T = struct {};
// const expected_tuple: T = .{};
// const found_tuple: T = @import("zon/slice-empty.zon");
// try expectEqual(expected_tuple, found_tuple);
// }
// {
// const expected_slice: []const u8 = &.{1};
// const found_slice: []const u8 = @import("zon/slice-1.zon");
// try expectEqualSlices(u8, expected_slice, found_slice);
// const expected_array: [1]u8 = .{1};
// const found_array: [1]u8 = @import("zon/slice-1.zon");
// try expectEqual(expected_array, found_array);
// const T = struct { u8 };
// const expected_tuple: T = .{1};
// const found_tuple: T = @import("zon/slice-1.zon");
// try expectEqual(expected_tuple, found_tuple);
// }
// {
// const expected_slice: []const u8 = &.{ 'a', 'b', 'c' };
// const found_slice: []const u8 = @import("zon/slice-abc.zon");
// try expectEqualSlices(u8, expected_slice, found_slice);
// const expected_array: [3]u8 = .{ 'a', 'b', 'c' };
// const found_array: [3]u8 = @import("zon/slice-abc.zon");
// try expectEqual(expected_array, found_array);
// const T = struct { u8, u8, u8 };
// const expected_tuple: T = .{ 'a', 'b', 'c' };
// const found_tuple: T = @import("zon/slice-abc.zon");
// try expectEqual(expected_tuple, found_tuple);
// }
}
test "string literals" {
try expectEqualSlices(u8, "abc", @import("zon/abc.zon"));
try expectEqualSlices(u8, "ab\\c", @import("zon/abc-escaped.zon"));
const zero_terminated: [:0]const u8 = @import("zon/abc.zon");
try expectEqualDeep(zero_terminated, "abc");
try expectEqual(0, zero_terminated[zero_terminated.len]);
try expectEqualStrings(
\\Hello, world!
\\This is a multiline string!
\\ There are no escapes, we can, for example, include \n in the string
, @import("zon/multiline_string.zon"));
try expectEqualStrings("a\nb\x00c", @import("zon/string_embedded_null.zon"));
}
test "enum literals" {
const Enum = enum {
foo,
bar,
baz,
@"0\na",
};
try expectEqual(Enum.foo, @as(Enum, @import("zon/foo.zon")));
try expectEqual(.foo, @as(@TypeOf(.foo), @import("zon/foo.zon")));
try expectEqual(Enum.@"0\na", @as(Enum, @import("zon/escaped_enum.zon")));
}
test "int" {
const expected = .{
// Test various numbers and types
@as(u8, 10),
@as(i16, 24),
@as(i14, -4),
@as(i32, -123),
// Test limits
@as(i8, 127),
@as(i8, -128),
// Test characters
@as(u8, 'a'),
@as(u8, 'z'),
// Test big integers
@as(u65, 36893488147419103231),
@as(u65, 36893488147419103231),
@as(i128, -18446744073709551615), // Only a big int due to negation
@as(i128, -9223372036854775809), // Only a big int due to negation
// Test big integer limits
@as(i66, 36893488147419103231),
@as(i66, -36893488147419103232),
// Test parsing whole number floats as integers
@as(i8, -1),
@as(i8, 123),
// Test non-decimal integers
@as(i16, 0xff),
@as(i16, -0xff),
@as(i16, 0o77),
@as(i16, -0o77),
@as(i16, 0b11),
@as(i16, -0b11),
// Test non-decimal big integers
@as(u65, 0x1ffffffffffffffff),
@as(i66, 0x1ffffffffffffffff),
@as(i66, -0x1ffffffffffffffff),
@as(u65, 0x1ffffffffffffffff),
@as(i66, 0x1ffffffffffffffff),
@as(i66, -0x1ffffffffffffffff),
@as(u65, 0x1ffffffffffffffff),
@as(i66, 0x1ffffffffffffffff),
@as(i66, -0x1ffffffffffffffff),
};
const actual: @TypeOf(expected) = @import("zon/ints.zon");
try expectEqual(expected, actual);
}
test "floats" {
const expected = .{
// Test decimals
@as(f16, 0.5),
@as(f32, 123.456),
@as(f64, -123.456),
@as(f128, 42.5),
// Test whole numbers with and without decimals
@as(f16, 5.0),
@as(f16, 5.0),
@as(f32, -102),
@as(f32, -102),
// Test characters and negated characters
@as(f32, 'a'),
@as(f32, 'z'),
@as(f32, -'z'),
// Test big integers
@as(f32, 36893488147419103231),
@as(f32, -36893488147419103231),
@as(f128, 0x1ffffffffffffffff),
@as(f32, 0x1ffffffffffffffff),
// Exponents, underscores
@as(f32, 123.0E+77),
// Hexadecimal
@as(f32, 0x103.70p-5),
@as(f32, -0x103.70),
@as(f32, 0x1234_5678.9ABC_CDEFp-10),
};
const actual: @TypeOf(expected) = @import("zon/floats.zon");
try expectEqual(actual, expected);
}
test "inf and nan" {
// comptime float
{
const actual: struct { comptime_float, comptime_float, comptime_float, comptime_float } = @import("zon/inf_and_nan.zon");
try expect(std.math.isNan(actual[0]));
try expect(std.math.isNan(actual[1]));
try expect(std.math.isPositiveInf(@as(f128, @floatCast(actual[2]))));
try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[3]))));
}
// f32
{
const actual: struct { f32, f32, f32, f32 } = @import("zon/inf_and_nan.zon");
try expect(std.math.isNan(actual[0]));
try expect(std.math.isNan(actual[1]));
try expect(std.math.isPositiveInf(actual[2]));
try expect(std.math.isNegativeInf(actual[3]));
}
}

1
test/behavior/zon/a.zon Normal file
View File

@ -0,0 +1 @@
'a'

View File

@ -0,0 +1 @@
-'a'

View File

@ -0,0 +1 @@
"ab\\c"

View File

@ -0,0 +1 @@
"abc"

View File

@ -0,0 +1 @@
.{ 'a', 'b', 'c', 'd' }

View File

@ -0,0 +1 @@
.{ .x = .z }

View File

@ -0,0 +1 @@
.@"0\na"

View File

@ -0,0 +1,2 @@
// zig fmt: off
.{ .@"0" = 1.5, .@"foo" = 2 }

View File

@ -0,0 +1,4 @@
// Comment
false // Another comment
// Yet another comment

View File

@ -0,0 +1,26 @@
.{
0.5,
123.456,
-123.456,
42.5,
5.0,
5,
-102.0,
-102,
'a',
'z',
-'z',
36893488147419103231,
-36893488147419103231,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
12_3.0E+77,
0x103.70p-5,
-0x103.70,
0x1234_5678.9ABC_CDEFp-10,
}

View File

@ -0,0 +1 @@
.foo

View File

@ -0,0 +1,6 @@
.{
nan,
-nan,
inf,
-inf,
}

View File

@ -0,0 +1,40 @@
.{
10,
24,
-4,
-123,
127,
-128,
'a',
'z',
36893488147419103231,
368934_881_474191032_31,
-18446744073709551615,
-9223372036854775809,
36893488147419103231,
-36893488147419103232,
-1.0,
123.0,
0xff,
-0xff,
0o77,
-0o77,
0b11,
-0b11,
0x1ffffffffffffffff,
0x1ffffffffffffffff,
-0x1ffffffffffffffff,
0o3777777777777777777777,
0o3777777777777777777777,
-0o3777777777777777777777,
0b11111111111111111111111111111111111111111111111111111111111111111,
0b11111111111111111111111111111111111111111111111111111111111111111,
-0b11111111111111111111111111111111111111111111111111111111111111111,
}

View File

@ -0,0 +1,4 @@
// zig fmt: off
\\Hello, world!
\\This is a multiline string!
\\ There are no escapes, we can, for example, include \n in the string

View File

@ -0,0 +1 @@
null

View File

@ -0,0 +1 @@
.{ 1 }

View File

@ -0,0 +1 @@
.{'a', 'b', 'c'}

View File

@ -0,0 +1 @@
.{}

View File

@ -0,0 +1 @@
10

View File

@ -0,0 +1 @@
"a\nb\x00c"

View File

@ -0,0 +1 @@
true

View File

@ -0,0 +1 @@
.{ 1.2, true, "hello", 3 }

View File

@ -0,0 +1 @@
.{ .x = 1.5 }

View File

@ -0,0 +1 @@
.{ .y = true }

View File

@ -0,0 +1 @@
.z

View File

@ -0,0 +1 @@
.{ .z = {} }

View File

@ -0,0 +1 @@
.{}

View File

@ -0,0 +1 @@
.{ .x = 1.5 }

View File

@ -0,0 +1 @@
.{ .x = 1.5, .y = 2 }

View File

@ -0,0 +1 @@
{}

1
test/behavior/zon/z.zon Normal file
View File

@ -0,0 +1 @@
'z'

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i32 = @import("zon/addr_slice.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/addr_slice.zon
//
// addr_slice.zon:2:14: error: invalid ZON value

View File

@ -0,0 +1,13 @@
pub fn main() void {
const f: [4]u8 = @import("zon/array.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/array.zon
//
// 2:22: error: expected type '[4]u8', found 'struct{comptime comptime_int = 97, comptime comptime_int = 98, comptime comptime_int = 99}'
// note: destination has length 4
// note: source has length 3

View File

@ -0,0 +1,12 @@
pub fn main() void {
_ = @import(
"bogus-does-not-exist.zon",
);
}
// error
// backend=stage2
// target=native
// output_mode=Exe
//
// :3:9: error: unable to open 'bogus-does-not-exist.zon': FileNotFound

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: *struct { u8, u8, u8 } = @import("zon/array.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/array.zon
//
// found 'struct{comptime comptime_int = 97, comptime comptime_int = 98, comptime comptime_int = 99}'

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i32 = @import("zon/double_negation_float.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/double_negation_float.zon
//
// double_negation_float.zon:1:2: error: invalid ZON value

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i32 = @import("zon/double_negation_int.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/double_negation_int.zon
//
// double_negation_int.zon:1:2: error: invalid ZON value

View File

@ -0,0 +1,12 @@
const std = @import("std");
pub fn main() void {
const f = @import("zon/enum_embedded_null.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/enum_embedded_null.zon
//
// enum_embedded_null.zon:2:6: error: identifier cannot contain null bytes

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f = @import("zon/invalid_character.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/invalid_character.zon
//
// invalid_character.zon:1:3: error: invalid escape character: 'a'

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f = @import("zon/invalid_number.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/invalid_number.zon
//
// invalid_number.zon:1:19: error: invalid digit 'a' for decimal base

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f = @import("zon/invalid_string.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/invalid_string.zon
//
// invalid_string.zon:1:5: error: invalid escape character: 'a'

View File

@ -0,0 +1,12 @@
pub fn main() void {
const f = @import("zon/leading_zero_in_integer.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/leading_zero_in_integer.zon
//
// leading_zero_in_integer.zon:1:1: error: number '0012' has leading zero
// leading_zero_in_integer.zon:1:1: note: use '0o' prefix for octal literals

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i8 = @import("zon/negative_zero.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/negative_zero.zon
//
// negative_zero.zon:1:1: error: integer literal '-0' is ambiguous

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: f32 = @import("zon/negative_zero.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/negative_zero.zon
//
// negative_zero.zon:1:1: error: integer literal '-0' is ambiguous

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i66 = @import("zon/large_number.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/large_number.zon
//
// 2:20: error: type 'i66' cannot represent integer value '36893488147419103232'

View File

@ -0,0 +1,12 @@
const std = @import("std");
pub fn main() void {
const f = @import("zon/struct_dup_field.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/struct_dup_field.zon
//
// struct_dup_field.zon:3:6: error: duplicate field

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: bool = @import("zon/syntax_error.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/syntax_error.zon
//
// syntax_error.zon:3:13: error: expected ',' after initializer

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i32 = @import("zon/type_decl.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/type_decl.zon
//
// type_decl.zon:2:12: error: invalid ZON value

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i32 = @import("zon/type_expr_array.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/type_expr_array.zon
//
// type_expr_array.zon:1:1: error: type expressions not allowed in ZON

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i32 = @import("zon/type_expr_fn.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/type_expr_fn.zon
//
// type_expr_fn.zon:1:1: error: type expressions not allowed in ZON

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i32 = @import("zon/type_expr_struct.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/type_expr_struct.zon
//
// type_expr_struct.zon:1:1: error: type expressions not allowed in ZON

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i32 = @import("zon/type_expr_tuple.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/type_expr_tuple.zon
//
// type_expr_tuple.zon:1:1: error: type expressions not allowed in ZON

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: bool = @import("zon/struct.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/struct.zon
//
// 2:21: error: expected type 'bool', found 'struct{comptime boolean: bool = true, comptime number: comptime_int = 123}'

View File

@ -0,0 +1,12 @@
pub fn main() void {
const f: i8 = @import("zon/unescaped_newline.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/unescaped_newline.zon
//
// unescaped_newline.zon:1:1: error: expected expression, found 'invalid bytes'
// unescaped_newline.zon:1:3: note: invalid byte: '\n'

View File

@ -0,0 +1,11 @@
pub fn main() void {
const f: i32 = @import("zon/unknown_ident.zon");
_ = f;
}
// error
// backend=stage2
// output_mode=Exe
// imports=zon/unknown_ident.zon
//
// unknown_ident.zon:2:14: error: use of unknown identifier 'truefalse'

View File

@ -0,0 +1,3 @@
.{
.value = &.{ 1, 2, 3 },
}

View File

@ -0,0 +1 @@
.{ 'a', 'b', 'c' }

View File

@ -0,0 +1,3 @@
[LocalizedFileNames]
invalid_zon_2.zig=@invalid_zon_2.zig,0
invalid_zon_1.zig=@invalid_zon_1.zig,0

View File

@ -0,0 +1 @@
--1.0

View File

@ -0,0 +1 @@
--1

View File

@ -0,0 +1,4 @@
.{
.@"\x00",
10,
}

View File

@ -0,0 +1 @@
'\a'

View File

@ -0,0 +1 @@
368934881474191032a32

View File

@ -0,0 +1 @@
"\"\a\""

View File

@ -0,0 +1 @@
36893488147419103232

View File

@ -0,0 +1 @@
0012

View File

@ -0,0 +1 @@
-0

View File

@ -0,0 +1,4 @@
.{
.boolean = true,
.number = 123,
}

View File

@ -0,0 +1,4 @@
.{
.name = 10,
.name = 20,
}

View File

@ -0,0 +1,4 @@
.{
.boolean = true
.number = 123,
}

View File

@ -0,0 +1,3 @@
.{
.foo = struct {},
}

View File

@ -0,0 +1 @@
[3]i32{1, 2, 3}

View File

@ -0,0 +1 @@
fn foo() void {}

View File

@ -0,0 +1 @@
Vec2{ .x = 1.0, .y = 2.0 }

View File

@ -0,0 +1 @@
Vec2{1.0, 2.0}

View File

@ -0,0 +1,2 @@
"a
b"

View File

@ -0,0 +1,3 @@
.{
.value = truefalse,
}

Some files were not shown because too many files have changed in this diff Show More