From 617ddc62aa4d3153850362526069b85bfaf5e59e Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 31 Jan 2026 15:31:15 +0900 Subject: use reference counting to manage JSON value lifetime --- src/jv/ops.zig | 61 +++++++------ src/jv/parse.zig | 32 ++++++- src/jv/rc.zig | 40 +++++++++ src/jv/stringify.zig | 2 +- src/jv/value.zig | 235 +++++++++++++++++++++++++++++++-------------------- 5 files changed, 243 insertions(+), 127 deletions(-) create mode 100644 src/jv/rc.zig (limited to 'src/jv') diff --git a/src/jv/ops.zig b/src/jv/ops.zig index d29c9cc..8d5f8fc 100644 --- a/src/jv/ops.zig +++ b/src/jv/ops.zig @@ -9,14 +9,14 @@ pub const OpsError = error{ }; pub fn index(base: Value, key: Value) OpsError!Value { - return switch (base.kind()) { - .array => blk: { - if (key.kind() != .integer) return error.InvalidType; - break :blk base.arrayGet(@intCast(key.integer())); + return switch (base) { + .array => |a| switch (key) { + .integer => |i| a.get(@intCast(i)), + else => error.InvalidType, }, - .object => blk: { - if (key.kind() != .string) return error.InvalidType; - break :blk base.objectGet(key.string()) orelse Value.null; + .object => |o| switch (key) { + .string => |s| o.get(s) orelse Value.null, + else => error.InvalidType, }, .null => Value.null, else => error.InvalidType, @@ -38,15 +38,14 @@ pub fn compare(lhs: Value, rhs: Value, op: CompareOp) OpsError!bool { return error.InvalidType; } - return switch (lhs_tag) { + return switch (lhs) { .null => switch (op) { .eq => true, .ne => false, .lt, .gt, .le, .ge => error.Unimplemented, }, - .bool => { - const lhs_bool = lhs.boolean(); - const rhs_bool = rhs.boolean(); + .bool => |lhs_bool| { + const rhs_bool = rhs.bool; return switch (op) { .eq => lhs_bool == rhs_bool, .ne => lhs_bool != rhs_bool, @@ -54,9 +53,8 @@ pub fn compare(lhs: Value, rhs: Value, op: CompareOp) OpsError!bool { }; }, .integer, .float => compareNumbers(lhs, rhs, op), - .string => { - const lhs_str = lhs.string(); - const rhs_str = rhs.string(); + .string => |lhs_str| { + const rhs_str = rhs.string; const order = std.mem.order(u8, lhs_str, rhs_str); return switch (op) { .eq => order == .eq, @@ -75,19 +73,18 @@ pub fn compare(lhs: Value, rhs: Value, op: CompareOp) OpsError!bool { .eq, .ne => error.Unimplemented, .lt, .gt, .le, .ge => error.Unimplemented, }, - .number_string => error.Unimplemented, }; } fn compareNumbers(lhs: Value, rhs: Value, op: CompareOp) bool { - const lhs_f: f64 = switch (lhs.kind()) { - .integer => @floatFromInt(lhs.integer()), - .float => lhs.float(), + const lhs_f: f64 = switch (lhs) { + .integer => |i| @floatFromInt(i), + .float => |f| f, else => unreachable, }; - const rhs_f: f64 = switch (rhs.kind()) { - .integer => @floatFromInt(rhs.integer()), - .float => rhs.float(), + const rhs_f: f64 = switch (rhs) { + .integer => |i| @floatFromInt(i), + .float => |f| f, else => unreachable, }; return switch (op) { @@ -101,11 +98,11 @@ fn compareNumbers(lhs: Value, rhs: Value, op: CompareOp) bool { } test "index array" { - var arr = Array.init(std.testing.allocator); - defer arr.deinit(); - try arr.append(Value.initInteger(10)); - try arr.append(Value.initInteger(20)); - try arr.append(Value.initInteger(30)); + var arr = try Array.init(std.testing.allocator); + defer arr.deinit(std.testing.allocator); + try arr.append(std.testing.allocator, Value.initInteger(10)); + try arr.append(std.testing.allocator, Value.initInteger(20)); + try arr.append(std.testing.allocator, Value.initInteger(30)); const base = Value.initArray(arr); @@ -116,10 +113,10 @@ test "index array" { } test "index object" { - var obj = Object.init(std.testing.allocator); - defer obj.deinit(); - try obj.set("foo", Value.initInteger(1)); - try obj.set("bar", Value.initInteger(2)); + var obj = try Object.init(std.testing.allocator); + defer obj.deinit(std.testing.allocator); + try obj.set(std.testing.allocator, "foo", Value.initInteger(1)); + try obj.set(std.testing.allocator, "bar", Value.initInteger(2)); const base = Value.initObject(obj); @@ -184,9 +181,9 @@ test "compare different types" { } pub fn isFalsy(value: Value) bool { - return switch (value.kind()) { + return switch (value) { .null => true, - .bool => !value.boolean(), + .bool => |b| !b, else => false, }; } diff --git a/src/jv/parse.zig b/src/jv/parse.zig index dc3fcef..3d90be5 100644 --- a/src/jv/parse.zig +++ b/src/jv/parse.zig @@ -1,5 +1,7 @@ const std = @import("std"); const Value = @import("./value.zig").Value; +const Array = @import("./value.zig").Array; +const Object = @import("./value.zig").Object; pub const Parsed = struct { value: Value, @@ -7,6 +9,7 @@ pub const Parsed = struct { allocator: std.mem.Allocator, pub fn deinit(self: *const Parsed) void { + self.value.deinit(self.allocator); const arena = self.arena; const allocator = self.allocator; arena.deinit(); @@ -16,9 +19,36 @@ pub const Parsed = struct { pub fn parse(allocator: std.mem.Allocator, input: []const u8) !Parsed { const internal = try std.json.parseFromSlice(std.json.Value, allocator, input, .{}); + const value = try convertValue(allocator, internal.value); return .{ - .value = .{ ._internal = internal.value }, + .value = value, .arena = internal.arena, .allocator = allocator, }; } + +fn convertValue(allocator: std.mem.Allocator, v: std.json.Value) !Value { + return switch (v) { + .null => Value.null, + .bool => |b| Value.initBool(b), + .integer => |i| Value.initInteger(i), + .float => |f| Value.initFloat(f), + .string => |s| Value.initString(s), + .array => |a| blk: { + var arr = try Array.init(allocator); + for (a.items) |item| { + try arr.append(allocator, try convertValue(allocator, item)); + } + break :blk Value.initArray(arr); + }, + .object => |o| blk: { + var obj = try Object.init(allocator); + var it = o.iterator(); + while (it.next()) |entry| { + try obj.set(allocator, entry.key_ptr.*, try convertValue(allocator, entry.value_ptr.*)); + } + break :blk Value.initObject(obj); + }, + .number_string => unreachable, + }; +} diff --git a/src/jv/rc.zig b/src/jv/rc.zig new file mode 100644 index 0000000..62a4feb --- /dev/null +++ b/src/jv/rc.zig @@ -0,0 +1,40 @@ +const std = @import("std"); + +pub fn Rc(comptime T: type) type { + const Cell = struct { + value: T, + ref_count: usize, + }; + + return struct { + const Self = @This(); + + cell: *Cell, + + pub fn init(allocator: std.mem.Allocator, value: T) std.mem.Allocator.Error!Self { + const cell = try allocator.create(Cell); + cell.* = .{ .value = value, .ref_count = 1 }; + return .{ .cell = cell }; + } + + pub fn release(self: Self, allocator: std.mem.Allocator) void { + self.cell.ref_count -= 1; + if (self.cell.ref_count == 0) { + allocator.destroy(self.cell); + } + } + + pub fn retain(self: Self) Self { + self.cell.ref_count += 1; + return .{ .cell = self.cell }; + } + + pub fn isUnique(self: Self) bool { + return self.cell.ref_count == 1; + } + + pub fn get(self: Self) *T { + return &self.cell.value; + } + }; +} diff --git a/src/jv/stringify.zig b/src/jv/stringify.zig index 0fc40a7..b553c6b 100644 --- a/src/jv/stringify.zig +++ b/src/jv/stringify.zig @@ -2,5 +2,5 @@ const std = @import("std"); const Value = @import("./value.zig").Value; pub fn stringify(allocator: std.mem.Allocator, value: Value) ![]u8 { - return try std.json.Stringify.valueAlloc(allocator, value._internal, .{ .whitespace = .indent_2 }); + return try std.json.Stringify.valueAlloc(allocator, value, .{ .whitespace = .indent_2 }); } diff --git a/src/jv/value.zig b/src/jv/value.zig index ffdec44..eb36c99 100644 --- a/src/jv/value.zig +++ b/src/jv/value.zig @@ -1,159 +1,208 @@ const std = @import("std"); -const Allocator = std.mem.Allocator; - -pub const Value = struct { - const _Internal = std.json.Value; - _internal: _Internal, - - pub const Kind = enum { null, bool, integer, float, string, array, object, number_string }; - - pub fn kind(self: Value) Kind { - return switch (self._internal) { - .null => .null, - .bool => .bool, - .integer => .integer, - .float => .float, - .string => .string, - .array => .array, - .object => .object, - .number_string => .number_string, - }; - } +const Rc = @import("./rc.zig").Rc; + +pub const ValueKind = enum { + null, + bool, + integer, + float, + string, + array, + object, +}; + +pub const Value = union(ValueKind) { + null: void, + bool: bool, + integer: i64, + float: f64, + string: []const u8, + array: Array, + object: Object, - pub const @"null": Value = .{ ._internal = .null }; - pub const @"true": Value = .{ ._internal = .{ .bool = true } }; - pub const @"false": Value = .{ ._internal = .{ .bool = false } }; + pub const @"true": Value = .{ .bool = true }; + pub const @"false": Value = .{ .bool = false }; pub fn initBool(b: bool) Value { - return .{ ._internal = .{ .bool = b } }; + return .{ .bool = b }; } pub fn initInteger(i: i64) Value { - return .{ ._internal = .{ .integer = i } }; + return .{ .integer = i }; } pub fn initFloat(f: f64) Value { - return .{ ._internal = .{ .float = f } }; + return .{ .float = f }; } pub fn initString(s: []const u8) Value { - return .{ ._internal = .{ .string = s } }; + return .{ .string = s }; } pub fn initArray(arr: Array) Value { - return .{ ._internal = .{ .array = arr._internal } }; + return .{ .array = arr }; } pub fn initObject(obj: Object) Value { - return .{ ._internal = .{ .object = obj._internal } }; + return .{ .object = obj }; } - pub fn deinit(self: *Value, allocator: Allocator) void { - switch (self._internal) { - .string => |s| allocator.free(s), - .array => |*a| a.deinit(), - .object => |*o| o.deinit(), - else => {}, - } + pub fn kind(self: Value) ValueKind { + return self; } - pub fn boolean(self: Value) bool { - return self._internal.bool; + pub fn clone(self: Value) Value { + return switch (self) { + .array => |a| .{ .array = a.clone() }, + .object => |o| .{ .object = o.clone() }, + else => self, + }; } - pub fn integer(self: Value) i64 { - return self._internal.integer; + pub fn deinit(self: Value, allocator: std.mem.Allocator) void { + switch (self) { + .array => |a| a.deinit(allocator), + .object => |o| o.deinit(allocator), + else => {}, + } } - pub fn float(self: Value) f64 { - return self._internal.float; + pub fn arrayAppend(self: *Value, allocator: std.mem.Allocator, item: Value) !void { + try self.array.append(allocator, item); + } + + pub fn jsonStringify(self: Value, jws: anytype) !void { + switch (self) { + .null => try jws.write(null), + .bool => |b| try jws.write(b), + .integer => |i| try jws.write(i), + .float => |f| try jws.write(f), + .string => |s| try jws.write(s), + .array => |a| { + try jws.beginArray(); + for (0..a.len()) |i| { + try a.get(i).jsonStringify(jws); + } + try jws.endArray(); + }, + .object => |o| { + try jws.beginObject(); + var it = o.iterator(); + while (it.next()) |entry| { + try jws.objectField(entry.key_ptr.*); + try entry.value_ptr.jsonStringify(jws); + } + try jws.endObject(); + }, + } } +}; + +pub const Array = struct { + const Inner = std.ArrayList(Value); + + rc: Rc(Inner), - pub fn string(self: Value) []const u8 { - return self._internal.string; + pub fn init(allocator: std.mem.Allocator) !Array { + return .{ .rc = try Rc(Inner).init(allocator, .{}) }; } - pub fn array(self: Value) Array { - return .{ ._internal = self._internal.array }; + pub fn clone(self: Array) Array { + return .{ .rc = self.rc.retain() }; } - pub fn object(self: Value) Object { - return .{ ._internal = self._internal.object }; + pub fn deinit(self: Array, allocator: std.mem.Allocator) void { + if (self.rc.isUnique()) { + for (self.rc.get().items) |item| { + item.deinit(allocator); + } + self.rc.get().deinit(allocator); + } + self.rc.release(allocator); } - pub fn arrayGet(self: Value, idx: usize) Value { - const items = self._internal.array.items; + pub fn get(self: Array, idx: usize) Value { + const items = self.rc.get().items; if (idx < items.len) { - return .{ ._internal = items[idx] }; + return items[idx]; } return Value.null; } - pub fn arrayLen(self: Value) usize { - return self._internal.array.items.len; + pub fn len(self: Array) usize { + return self.rc.get().items.len; } - pub fn arrayAppend(self: *Value, item: Value) !void { - try self._internal.array.append(item._internal); + pub fn append(self: *Array, allocator: std.mem.Allocator, value: Value) !void { + try self.ensureUnique(allocator); + try self.rc.get().append(allocator, value); } - pub fn objectGet(self: Value, key: []const u8) ?Value { - if (self._internal.object.get(key)) |v| { - return .{ ._internal = v }; + fn ensureUnique(self: *Array, allocator: std.mem.Allocator) !void { + if (!self.rc.isUnique()) { + const old_items = self.rc.get().items; + var new_inner: Inner = .{}; + try new_inner.ensureTotalCapacity(allocator, old_items.len); + for (old_items) |item| { + new_inner.appendAssumeCapacity(item.clone()); + } + self.rc.release(allocator); + self.rc = try Rc(Inner).init(allocator, new_inner); } - return null; - } - - pub fn objectSet(self: *Value, key: []const u8, val: Value) !void { - try self._internal.object.put(key, val._internal); } }; -pub const Array = struct { - const _Internal = std.json.Array; - _internal: _Internal, +pub const Object = struct { + const Inner = std.StringArrayHashMapUnmanaged(Value); - pub fn init(allocator: Allocator) Array { - return .{ ._internal = _Internal.init(allocator) }; - } + rc: Rc(Inner), - pub fn deinit(self: *Array) void { - self._internal.deinit(); + pub fn init(allocator: std.mem.Allocator) !Object { + return .{ .rc = try Rc(Inner).init(allocator, .{}) }; } - pub fn get(self: Array, idx: usize) Value { - return .{ ._internal = self._internal.items[idx] }; + pub fn clone(self: Object) Object { + return .{ .rc = self.rc.retain() }; } - pub fn len(self: Array) usize { - return self._internal.items.len; + pub fn deinit(self: Object, allocator: std.mem.Allocator) void { + if (self.rc.isUnique()) { + for (self.rc.get().values()) |v| { + v.deinit(allocator); + } + self.rc.get().deinit(allocator); + } + self.rc.release(allocator); } - pub fn append(self: *Array, value: Value) !void { - try self._internal.append(value._internal); + pub fn get(self: Object, key: []const u8) ?Value { + return self.rc.get().get(key); } -}; - -pub const Object = struct { - const _Internal = std.json.ObjectMap; - _internal: _Internal, - pub fn init(allocator: Allocator) Object { - return .{ ._internal = _Internal.init(allocator) }; + pub fn set(self: *Object, allocator: std.mem.Allocator, key: []const u8, value: Value) !void { + try self.ensureUnique(allocator); + try self.rc.get().put(allocator, key, value); } - pub fn deinit(self: *Object) void { - self._internal.deinit(); + pub fn count(self: Object) usize { + return self.rc.get().count(); } - pub fn get(self: Object, key: []const u8) ?Value { - if (self._internal.get(key)) |v| { - return .{ ._internal = v }; - } - return null; + pub fn iterator(self: Object) Inner.Iterator { + return self.rc.get().iterator(); } - pub fn set(self: *Object, key: []const u8, value: Value) !void { - try self._internal.put(key, value._internal); + fn ensureUnique(self: *Object, allocator: std.mem.Allocator) !void { + if (!self.rc.isUnique()) { + const old = self.rc.get(); + var new_inner: Inner = .{}; + try new_inner.ensureTotalCapacity(allocator, old.count()); + var it = old.iterator(); + while (it.next()) |entry| { + new_inner.putAssumeCapacity(entry.key_ptr.*, entry.value_ptr.clone()); + } + self.rc.release(allocator); + self.rc = try Rc(Inner).init(allocator, new_inner); + } } }; -- cgit v1.3-1-g0d28