Compare commits

..

1 commit

Author SHA1 Message Date
9e0e5ff8ea Add EnumFlags tests 2026-08-25 12:07:43 +00:00
12 changed files with 296 additions and 1632 deletions

View file

@ -18,7 +18,9 @@ pub fn build(b: *std.Build) void {
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
run_cmd.addPassthruArgs();
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);

12
flake.lock generated
View file

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1791914995,
"narHash": "sha256-Zf/xfQEAasnwnmEfHqW/4hjDE/s44yn2hD4RuVIZRjA=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b7a79d3fea1e214f6d668d521cecec0c01e26544",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1793110635,
"narHash": "sha256-dhoW6VgFWlaodSY5jrBn+CF2FLmaUvDVgM7+C7T2+NM=",
"lastModified": 1781577229,
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "31ecbaca1bdefa45dffc409f0250bd8039ea0010",
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
"type": "github"
},
"original": {

View file

@ -33,14 +33,12 @@ pub const ClassAccessFlags = EnumFlags(enum(u16) {
module = 0x8000,
});
pub const ParseError = std.Io.Reader.Error || std.mem.Allocator.Error || error{
pub const ParseError = std.Io.Reader.Error || std.mem.Allocator.Error || error {
InvalidMagicNumber,
InvalidTag,
InvalidOp,
InvalidArrayType,
};
pub const ResolveError = error{
pub const ResolveError = error {
InvalidConstantPoolIndex,
InvalidConstantType,
};
@ -121,12 +119,8 @@ fn parseFields(input: *std.Io.Reader, constant_pool: ConstantPool, allocator: st
const fields = try allocator.alloc(FieldInfo, fields_count);
errdefer allocator.free(fields);
var parsed: usize = 0;
errdefer for (0..parsed) |i| fields[i].deinit(allocator);
for (0..fields_count) |i| {
fields[i] = try .parse(input, constant_pool, allocator);
parsed += 1;
fields[i] = try FieldInfo.parse(input, constant_pool, allocator);
}
return fields;
@ -137,12 +131,8 @@ fn parseMethods(input: *std.Io.Reader, constant_pool: ConstantPool, allocator: s
const methods = try allocator.alloc(MethodInfo, methods_count);
errdefer allocator.free(methods);
var parsed: usize = 0;
errdefer for (0..parsed) |i| methods[i].deinit(allocator);
for (0..methods_count) |i| {
methods[i] = try .parse(input, constant_pool, allocator);
parsed += 1;
}
return methods;
@ -153,12 +143,8 @@ pub fn parseAttributes(input: *std.Io.Reader, constant_pool: ConstantPool, alloc
const attributes = try allocator.alloc(AttributeInfo, attributes_count);
errdefer allocator.free(attributes);
var parsed: usize = 0;
errdefer for (0..parsed) |i| attributes[i].deinit(allocator);
for (0..attributes_count) |i| {
attributes[i] = try .parse(input, constant_pool, allocator);
parsed += 1;
attributes[i] = try AttributeInfo.parse(input, constant_pool, allocator);
}
return attributes;
@ -166,34 +152,58 @@ pub fn parseAttributes(input: *std.Io.Reader, constant_pool: ConstantPool, alloc
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
self.constant_pool.deinit(allocator);
self.freeInterfaces(allocator);
self.freeFields(allocator);
self.freeMethods(allocator);
self.freeAttributes(allocator);
}
fn freeInterfaces(self: *Self, allocator: std.mem.Allocator) void {
allocator.free(self.interfaces);
}
fn freeFields(self: *Self, allocator: std.mem.Allocator) void {
for (self.fields) |*field_info| {
field_info.deinit(allocator);
}
allocator.free(self.fields);
}
fn freeMethods(self: *Self, allocator: std.mem.Allocator) void {
for (self.methods) |*method_info| {
method_info.deinit(allocator);
}
allocator.free(self.methods);
}
fn freeAttributes(self: *Self, allocator: std.mem.Allocator) void {
for (self.attributes) |*attr| {
attr.deinit(allocator);
}
allocator.free(self.attributes);
}
pub fn jsonStringify(self: *const Self, jws: *std.json.Stringify) !void {
try jws.beginObject();
inline for (std.meta.fields(Self)) |field| {
if (comptime std.mem.eql(u8, field.name, "allocator")) continue;
try jws.objectField(field.name);
try jws.write(@field(self, field.name));
}
try jws.endObject();
}
pub fn format(self: *const Self, w: *std.Io.Writer) std.Io.Writer.Error!void {
const depth = 2;
var indent: u8 = 0;
try w.print("magic: 0x{X}\n", .{self.magic});
try w.print("magic: 0x{X}\n", .{ self.magic });
try w.print("version: {d}.{d}\n", .{ self.major_version, self.minor_version });
try w.print("flags: {f}\n", .{self.access_flags});
try w.print("flags: {f}\n", .{ self.access_flags });
const this_class = self.thisClass() catch return error.WriteFailed;
_ = try w.write("this class:\n");
@ -212,7 +222,7 @@ pub fn format(self: *const Self, w: *std.Io.Writer) std.Io.Writer.Error!void {
const info = self.constant_pool.getTag(interface, .class) catch return error.WriteFailed;
try w.splatByteAll(' ', depth * indent);
try w.print("{d}:\n", .{i});
try w.print("{d}:\n", .{ i });
try info.indentedFormat(w, depth, indent + 1, self.constant_pool);
}
@ -222,7 +232,7 @@ pub fn format(self: *const Self, w: *std.Io.Writer) std.Io.Writer.Error!void {
defer indent -= 1;
try w.splatByteAll(' ', depth * indent);
try w.print("{d}:\n", .{i});
try w.print("{d}:\n", .{ i });
try field.indentedFormat(w, depth, indent + 1, self.constant_pool);
}
@ -232,7 +242,7 @@ pub fn format(self: *const Self, w: *std.Io.Writer) std.Io.Writer.Error!void {
defer indent -= 1;
try w.splatByteAll(' ', depth * indent);
try w.print("{d}:\n", .{i});
try w.print("{d}:\n", .{ i });
try method.indentedFormat(w, depth, indent + 1, self.constant_pool);
}
@ -242,7 +252,7 @@ pub fn format(self: *const Self, w: *std.Io.Writer) std.Io.Writer.Error!void {
defer indent -= 1;
try w.splatByteAll(' ', depth * indent);
try w.print("{d}:\n", .{i});
try w.print("{d}:\n", .{ i });
try attribute.indentedFormat(w, depth, indent + 1, self.constant_pool);
}
}
@ -254,3 +264,4 @@ pub fn thisClass(self: *const Self) ResolveError!ConstantPool.Info {
pub fn superClass(self: *const Self) ResolveError!?ConstantPool.Info {
return self.constant_pool.getTagOptional(self.super_class, .class);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -355,23 +355,23 @@ pub const Info = union(Info.Tag) {
pub fn format(self: *const Info, w: *std.Io.Writer) std.Io.Writer.Error!void {
switch (self.*) {
.class => |class| try w.print("#{d}", .{class.name_index}),
.class => |class| try w.print("#{d}", .{ class.name_index }),
.field_ref => |field_ref| try w.print("#{d}.#{d}", .{ field_ref.class_index, field_ref.name_and_type_index }),
.method_ref => |method_ref| try w.print("#{d}.#{d}", .{ method_ref.class_index, method_ref.name_and_type_index }),
.interface_method_ref => |interface_method_ref| try w.print("#{d}.#{d}", .{ interface_method_ref.class_index, interface_method_ref.name_and_type_index }),
.string => |string| try w.print("#{d}", .{string.string_index}),
.integer => |integer| try w.print("{d}", .{integer.bytes}),
.float => |float| try w.print("{d}", .{float.float()}),
.long => |long| try w.print("{d}", .{long.long()}),
.double => |double| try w.print("{d}", .{double.double()}),
.string => |string| try w.print("#{d}", .{ string.string_index }),
.integer => |integer| try w.print("{d}", .{ integer.bytes }),
.float => |float| try w.print("{d}", .{ float.float() }),
.long => |long| try w.print("{d}", .{ long.long() }),
.double => |double| try w.print("{d}", .{ double.double() }),
.name_and_type => |name_and_type| try w.print("#{d}:#{d}", .{ name_and_type.name_index, name_and_type.descriptor_index }),
.utf8 => |utf8| try w.print("\"{s}\"", .{utf8.bytes}),
.utf8 => |utf8| try w.print("\"{s}\"", .{ utf8.bytes }),
.method_handle => |method_handle| try w.print("{t} #{d}", .{ method_handle.reference_kind, method_handle.reference_index }),
.method_type => |method_type| try w.print("#{d}", .{method_type.descriptor_index}),
.method_type => |method_type| try w.print("#{d}", .{ method_type.descriptor_index }),
.dynamic => |dynamic| try w.print("#{d} #{d}", .{ dynamic.bootstrap_method_attr_index, dynamic.name_and_type_index }),
.invoke_dynamic => |invoke_dynamic| try w.print("#{d} #{d}", .{ invoke_dynamic.bootstrap_method_attr_index, invoke_dynamic.name_and_type_index }),
.module => |module| try w.print("#{d}", .{module.name_index}),
.package => |package| try w.print("#{d}", .{package.name_index}),
.module => |module| try w.print("#{d}", .{ module.name_index }),
.package => |package| try w.print("#{d}", .{ package.name_index }),
}
}
@ -383,7 +383,7 @@ pub const Info = union(Info.Tag) {
constant_pool: Self,
) std.Io.Writer.Error!void {
try w.splatByteAll(' ', depth * indent);
try w.print("constant_value_type: {t}\n", .{self.*});
try w.print("constant_value_type: {t}\n", .{ self.* });
switch (self.*) {
.class => |*class| {
@ -409,19 +409,19 @@ pub const Info = union(Info.Tag) {
},
.integer => |*integer| {
try w.splatByteAll(' ', depth * indent);
try w.print("integer: {d}\n", .{integer.bytes});
try w.print("integer: {d}\n", .{ integer.bytes });
},
.float => |*float| {
try w.splatByteAll(' ', depth * indent);
try w.print("float: {d}\n", .{float.float()});
try w.print("float: {d}\n", .{ float.float() });
},
.long => |*long| {
try w.splatByteAll(' ', depth * indent);
try w.print("long: {d}\n", .{long.long()});
try w.print("long: {d}\n", .{ long.long() });
},
.double => |*double| {
try w.splatByteAll(' ', depth * indent);
try w.print("double: {d}\n", .{double.double()});
try w.print("double: {d}\n", .{ double.double() });
},
.name_and_type => |*name_and_type| {
try formatInfo(w, depth, indent, "name", name_and_type, .name, constant_pool);
@ -430,11 +430,11 @@ pub const Info = union(Info.Tag) {
},
.utf8 => |*utf8| {
try w.splatByteAll(' ', depth * indent);
try w.print("bytes: \"{s}\"\n", .{utf8.bytes});
try w.print("bytes: \"{s}\"\n", .{ utf8.bytes });
},
.method_handle => |*method_handle| {
try w.splatByteAll(' ', depth * indent);
try w.print("kind: {t}\n", .{method_handle.reference_kind});
try w.print("kind: {t}\n", .{ method_handle.reference_kind });
try formatInfo(w, depth, indent, "descriptor", method_handle, .reference, constant_pool);
},
@ -466,18 +466,8 @@ pub fn parse(input: *std.Io.Reader, allocator: std.mem.Allocator) ParseError!Sel
const constant_pool = try allocator.alloc(?Info, constant_pool_size - 1);
errdefer allocator.free(constant_pool);
var parsed: usize = 0;
errdefer {
for (0..parsed) |i| {
if (constant_pool[i]) |*info| {
info.deinit(allocator);
}
}
}
var i: usize = 0;
while (i < constant_pool_size - 1) {
defer parsed = i;
defer i += 1;
const info: Info = try .parse(input, allocator);

View file

@ -54,7 +54,7 @@ pub fn indentedFormat(
constant_pool: ConstantPool,
) std.Io.Writer.Error!void {
try w.splatByteAll(' ', depth * indent);
try w.print("flags: {f}\n", .{self.access_flags});
try w.print("flags: {f}\n", .{ self.access_flags });
try w.splatByteAll(' ', depth * indent);
_ = try w.write("name:\n");
@ -74,7 +74,7 @@ pub fn indentedFormat(
defer attr_indent -= 1;
try w.splatByteAll(' ', depth * attr_indent);
try w.print("{d}:\n", .{j});
try w.print("{d}:\n", .{ j });
try attribute.indentedFormat(w, depth, attr_indent + 1, constant_pool);
}
@ -87,3 +87,4 @@ pub fn name(self: *const Self, constant_pool: ConstantPool) ResolveError!Constan
pub fn descriptor(self: *const Self, constant_pool: ConstantPool) ResolveError!ConstantPool.Info {
return constant_pool.getTag(self.descriptor_index, .utf8);
}

View file

@ -57,7 +57,7 @@ pub fn indentedFormat(
constant_pool: ConstantPool,
) std.Io.Writer.Error!void {
try w.splatByteAll(' ', depth * indent);
try w.print("flags: {f}\n", .{self.access_flags});
try w.print("flags: {f}\n", .{ self.access_flags });
try w.splatByteAll(' ', depth * indent);
_ = try w.write("name:\n");
@ -77,7 +77,7 @@ pub fn indentedFormat(
defer attr_indent -= 1;
try w.splatByteAll(' ', depth * attr_indent);
try w.print("{d}:\n", .{j});
try w.print("{d}:\n", .{ j });
try attribute.indentedFormat(w, depth, attr_indent + 1, constant_pool);
}

View file

@ -8,7 +8,13 @@ pub fn EnumFlags(comptime E: type) type {
mask: BackingInt,
pub const empty: Self = .{ .data = 0 };
pub const empty: Self = .{ .mask = 0 };
test empty {
const flags: EnumFlags(Enum) = .empty;
try std.testing.expectEqual(0b0000_0000, flags.mask);
}
pub fn from(mask: BackingInt) Self {
return .{
@ -16,34 +22,102 @@ pub fn EnumFlags(comptime E: type) type {
};
}
test from {
const flags: EnumFlags(Enum) = .from(0b0000_0001);
try std.testing.expectEqual(0b0000_0001, flags.mask);
}
pub fn contains(self: Self, flag: E) bool {
return (self.mask & @backingInt(flag)) != 0;
return (self.mask & @intFromEnum(flag)) != 0;
}
pub fn insert(self: *Self, flag: E) void {
self.mask |= @backingInt(flag);
self.mask |= @intFromEnum(flag);
}
pub fn remove(self: *Self, flag: E) void {
self.mask &= ~@backingInt(flag);
self.mask &= ~@intFromEnum(flag);
}
pub fn toggle(self: *Self, flag: E) void {
self.mask ^= @backingInt(flag);
self.mask ^= @intFromEnum(flag);
}
pub fn format(self: *const Self, w: *std.Io.Writer) std.Io.Writer.Error!void {
var index: usize = 0;
inline for (@typeInfo(E).@"enum".field_names) |field| {
const key = @field(E, field);
inline for (std.meta.fields(E)) |field| {
const key = @field(E, field.name);
if (self.contains(key)) {
if (index > 0) {
_ = try w.write(" | ");
}
try w.print("{s}", .{field});
try w.print("{s}", .{ field.name });
index += 1;
}
}
}
};
}
const Enum = enum(u8) {
a = 0b0000_0001,
b = 0b0000_0010,
c = 0b0000_0100,
};
test {
std.testing.refAllDecls(EnumFlags(Enum));
}
test "EnumFlags operations" {
var flags: EnumFlags(Enum) = .from(0b0000_0001);
try std.testing.expect(flags.contains(Enum.a));
flags.remove(Enum.a);
try std.testing.expect(!flags.contains(Enum.a));
flags.remove(Enum.a); // Removing twice should not enable the flag
try std.testing.expect(!flags.contains(Enum.a));
flags.insert(Enum.b);
try std.testing.expectEqual(0b0000_0010, flags.mask);
try std.testing.expect(flags.contains(Enum.b));
try std.testing.expect(!flags.contains(Enum.a));
flags.toggle(Enum.a);
try std.testing.expectEqual(0b0000_0011, flags.mask);
try std.testing.expect(flags.contains(Enum.a));
try std.testing.expect(flags.contains(Enum.b));
try std.testing.expect(!flags.contains(Enum.c));
}
fn testFormat(expected: []const u8, flags: EnumFlags(Enum)) !void {
var out_buf: [1024]u8 = undefined;
var out: std.Io.Writer = .fixed(&out_buf);
try flags.format(&out);
try std.testing.expectEqualStrings(expected, out.buffered());
}
test "EnumFlags format" {
var flags: EnumFlags(Enum) = .empty;
flags.toggle(Enum.a);
flags.toggle(Enum.b);
flags.toggle(Enum.c);
try testFormat("a | b | c", flags);
flags.toggle(Enum.b);
try testFormat("a | c", flags);
flags.remove(Enum.c);
try testFormat("a", flags);
flags.insert(Enum.b);
try testFormat("a | b", flags);
}

View file

@ -19,17 +19,17 @@ pub fn main(init: std.process.Init) !void {
std.debug.assert(args.skip());
const file_path = if (args.next()) |fp| fp else {
try stderr.writeAll("No file argument provided.\n");
std.process.exit(1);
return;
};
const file = std.Io.Dir.cwd().openFile(io, file_path, .{}) catch |err| switch (err) {
error.FileNotFound => {
try stderr.writeAll("File not found.\n");
std.process.exit(1);
return;
},
error.IsDir => {
try stderr.writeAll("Path is a directory.\n");
std.process.exit(1);
return;
},
else => return err,
};
@ -42,17 +42,17 @@ pub fn main(init: std.process.Init) !void {
var class = Class.parse(reader, allocator) catch |err| switch (err) {
error.InvalidMagicNumber => {
try stderr.writeAll("Invalid magic number.\n");
std.process.exit(1);
return;
},
error.InvalidTag => {
try stderr.writeAll("Unrecognized tag type.\n");
std.process.exit(1);
return;
},
else => return err,
};
defer class.deinit(allocator);
try stdout.print("{f}", .{class});
try stdout.print("{f}", .{ class });
try stdout.flush();
}

View file

@ -17,52 +17,6 @@ public class Main implements Runnable, Supplier<Integer>, Interface {
} finally {
System.err.println("Finally block");
}
var a = 8;
a <<= 3;
final var b = a >> 1;
switch (b) {
case 1:
break;
case 32:
System.out.println("Good");
break;
default:
System.out.println("Number " + a);
break;
}
final var o = getObject();
switch (o) {
case Integer i:
switch (i) {
case 1:
System.out.println("One");
break;
case 2:
System.out.println("Two");
break;
case 3:
System.out.println("Three");
break;
default:
break;
}
break;
default:
System.out.println("Something else");
break;
}
inc(5);
}
private static Object getObject() {
return 3;
}
private static void inc(int i) {
i += 1000;
}
@Override

Binary file not shown.