aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-01-31 15:31:15 +0900
committernsfisis <nsfisis@gmail.com>2026-01-31 15:47:23 +0900
commit617ddc62aa4d3153850362526069b85bfaf5e59e (patch)
tree6940c577bbecdbf7856ef3c37ab43b256d33da59
parent6be43138338fbe4623c1cd62cf71138873af3a7a (diff)
downloadzgjq-617ddc62aa4d3153850362526069b85bfaf5e59e.tar.gz
zgjq-617ddc62aa4d3153850362526069b85bfaf5e59e.tar.zst
zgjq-617ddc62aa4d3153850362526069b85bfaf5e59e.zip
use reference counting to manage JSON value lifetime
-rw-r--r--src/jq/execute.zig122
-rw-r--r--src/jq/parse.zig2
-rw-r--r--src/jq/saveable_stack.zig4
-rw-r--r--src/jv/ops.zig61
-rw-r--r--src/jv/parse.zig32
-rw-r--r--src/jv/rc.zig40
-rw-r--r--src/jv/stringify.zig2
-rw-r--r--src/jv/value.zig229
-rw-r--r--src/root.zig2
9 files changed, 330 insertions, 164 deletions
diff --git a/src/jq/execute.zig b/src/jq/execute.zig
index 5749b0b..3096be2 100644
--- a/src/jq/execute.zig
+++ b/src/jq/execute.zig
@@ -34,52 +34,58 @@ const ValueStack = struct {
}
pub fn pop(self: *Self) jv.Value {
+ // Values beyond the savepoint boundary belong to a previous segment
+ // that may be restored later. We must clone them because restore()
+ // expects those values to be still available.
+ if (self.stack.isBeyondSavepointBoundary()) {
+ return self.stack.pop().clone();
+ }
return self.stack.pop();
}
pub fn popInteger(self: *Self) ExecuteError!i64 {
const value = self.pop();
- return switch (value.kind()) {
- .integer => value.integer(),
+ return switch (value) {
+ .integer => |i| i,
else => error.InvalidType,
};
}
pub fn popNumber(self: *Self) ExecuteError!f64 {
const value = self.pop();
- return switch (value.kind()) {
- .integer => @floatFromInt(value.integer()),
- .float => value.float(),
+ return switch (value) {
+ .integer => |i| @floatFromInt(i),
+ .float => |f| f,
else => error.InvalidType,
};
}
pub fn popString(self: *Self) ExecuteError![]const u8 {
const value = self.pop();
- return switch (value.kind()) {
- .string => value.string(),
+ return switch (value) {
+ .string => |s| s,
else => error.InvalidType,
};
}
pub fn popArray(self: *Self) ExecuteError!jv.Array {
const value = self.pop();
- return switch (value.kind()) {
- .array => value.array(),
+ return switch (value) {
+ .array => |a| a,
else => error.InvalidType,
};
}
pub fn popObject(self: *Self) ExecuteError!jv.Object {
const value = self.pop();
- return switch (value.kind()) {
- .object => value.object(),
+ return switch (value) {
+ .object => |o| o,
else => error.InvalidType,
};
}
pub fn dup(self: *Self) !void {
- const top = self.stack.top().*;
+ const top = self.stack.top().*.clone();
try self.push(top);
}
@@ -96,13 +102,35 @@ const ValueStack = struct {
try self.stack.save();
}
- pub fn restore(self: *Self) void {
+ pub fn restore(self: *Self, allocator: std.mem.Allocator) void {
+ self.discardAllValuesAboveSavepoint(allocator);
self.stack.restore();
}
pub fn ensureSize(self: *Self, n: usize) bool {
return self.stack.ensureSize(n);
}
+
+ // Discard all values pushed above the current savepoint.
+ fn discardAllValuesAboveSavepoint(self: *Self, allocator: std.mem.Allocator) void {
+ if (self.stack.savepoints.items.len == 0) return;
+ const sp = self.stack.savepoints.items[self.stack.savepoints.items.len - 1];
+
+ var seg_idx = self.stack.active_segment_index;
+ while (seg_idx > sp.segment_index) : (seg_idx -= 1) {
+ const seg = &self.stack.segments.items[seg_idx];
+ for (seg.data.items) |item| {
+ item.deinit(allocator);
+ }
+ }
+
+ const seg = &self.stack.segments.items[sp.segment_index];
+ if (seg.data.items.len > sp.offset) {
+ for (seg.data.items[sp.offset..]) |item| {
+ item.deinit(allocator);
+ }
+ }
+ }
};
pub const Runtime = struct {
@@ -122,7 +150,7 @@ pub const Runtime = struct {
try constants.append(allocator, jv.Value.null);
try constants.append(allocator, jv.Value.false);
try constants.append(allocator, jv.Value.true);
- try constants.append(allocator, jv.Value.initArray(jv.Array.init(allocator)));
+ try constants.append(allocator, jv.Value.initArray(try jv.Array.init(allocator)));
return .{
.allocator = allocator,
@@ -136,12 +164,15 @@ pub const Runtime = struct {
}
pub fn deinit(self: *Self) void {
- for (self.variables.items) |*value| {
+ for (self.variables.items) |value| {
value.deinit(self.allocator);
}
self.variables.deinit(self.allocator);
- for (self.constants.items) |*value| {
- value.deinit(self.allocator);
+ for (self.constants.items) |value| {
+ switch (value) {
+ .string => |s| self.allocator.free(s),
+ else => value.deinit(self.allocator),
+ }
}
self.constants.deinit(self.allocator);
self.allocator.free(self.instrs);
@@ -171,7 +202,7 @@ pub const Runtime = struct {
}
pub fn start(self: *Self, input: jv.Value) !void {
- try self.values.push(input);
+ try self.values.push(input.clone());
}
pub fn next(self: *Self) !?jv.Value {
@@ -217,7 +248,7 @@ pub const Runtime = struct {
.pop => {
std.debug.assert(self.values.ensureSize(1));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
},
.subexp_begin => try self.values.dup(),
.subexp_end => try self.values.swap(),
@@ -226,7 +257,9 @@ pub const Runtime = struct {
const base = self.values.pop();
const key = self.values.pop();
- const result = try jv.ops.index(base, key);
+ const result = (try jv.ops.index(base, key)).clone();
+ base.deinit(self.allocator);
+ key.deinit(self.allocator);
try self.values.push(result);
},
.index_opt => {
@@ -234,13 +267,16 @@ pub const Runtime = struct {
const base = self.values.pop();
const key = self.values.pop();
- const result = jv.ops.index(base, key) catch jv.Value.null;
+ const idx_result: jv.Value = jv.ops.index(base, key) catch .null;
+ const result = idx_result.clone();
+ base.deinit(self.allocator);
+ key.deinit(self.allocator);
try self.values.push(result);
},
.add => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = try self.values.popInteger();
const rhs = try self.values.popInteger();
const result = lhs + rhs;
@@ -249,7 +285,7 @@ pub const Runtime = struct {
.sub => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = try self.values.popInteger();
const rhs = try self.values.popInteger();
const result = lhs - rhs;
@@ -258,7 +294,7 @@ pub const Runtime = struct {
.mul => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = try self.values.popInteger();
const rhs = try self.values.popInteger();
const result = lhs * rhs;
@@ -267,7 +303,7 @@ pub const Runtime = struct {
.div => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = try self.values.popInteger();
const rhs = try self.values.popInteger();
const result = @divTrunc(lhs, rhs);
@@ -276,7 +312,7 @@ pub const Runtime = struct {
.mod => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = try self.values.popInteger();
const rhs = try self.values.popInteger();
const result = @mod(lhs, rhs);
@@ -285,7 +321,7 @@ pub const Runtime = struct {
.eq => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = self.values.pop();
const rhs = self.values.pop();
const result = try jv.ops.compare(lhs, rhs, .eq);
@@ -294,7 +330,7 @@ pub const Runtime = struct {
.ne => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = self.values.pop();
const rhs = self.values.pop();
const result = try jv.ops.compare(lhs, rhs, .ne);
@@ -303,7 +339,7 @@ pub const Runtime = struct {
.lt => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = self.values.pop();
const rhs = self.values.pop();
const result = try jv.ops.compare(lhs, rhs, .lt);
@@ -312,7 +348,7 @@ pub const Runtime = struct {
.gt => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = self.values.pop();
const rhs = self.values.pop();
const result = try jv.ops.compare(lhs, rhs, .gt);
@@ -321,7 +357,7 @@ pub const Runtime = struct {
.le => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = self.values.pop();
const rhs = self.values.pop();
const result = try jv.ops.compare(lhs, rhs, .le);
@@ -330,7 +366,7 @@ pub const Runtime = struct {
.ge => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = self.values.pop();
const rhs = self.values.pop();
const result = try jv.ops.compare(lhs, rhs, .ge);
@@ -339,19 +375,25 @@ pub const Runtime = struct {
.alt => {
std.debug.assert(self.values.ensureSize(3));
- _ = self.values.pop();
+ self.values.pop().deinit(self.allocator);
const lhs = self.values.pop();
const rhs = self.values.pop();
- try self.values.push(if (jv.ops.isFalsy(lhs)) rhs else lhs);
+ if (jv.ops.isFalsy(lhs)) {
+ lhs.deinit(self.allocator);
+ try self.values.push(rhs);
+ } else {
+ rhs.deinit(self.allocator);
+ try self.values.push(lhs);
+ }
},
.@"const" => |idx| {
std.debug.assert(self.values.ensureSize(1));
- _ = self.values.pop();
- try self.values.push(self.constants.items[@intFromEnum(idx)]);
+ self.values.pop().deinit(self.allocator);
+ try self.values.push(self.constants.items[@intFromEnum(idx)].clone());
},
.load => |idx| {
- try self.values.push(self.variables.items[@intFromEnum(idx)]);
+ try self.values.push(self.variables.items[@intFromEnum(idx)].clone());
},
.store => |idx| {
std.debug.assert(self.values.ensureSize(1));
@@ -360,12 +402,14 @@ pub const Runtime = struct {
while (self.variables.items.len <= @intFromEnum(idx)) {
try self.variables.append(self.allocator, jv.Value.null);
}
+ self.variables.items[@intFromEnum(idx)].deinit(self.allocator);
self.variables.items[@intFromEnum(idx)] = self.values.pop();
},
.append => |idx| {
std.debug.assert(self.values.ensureSize(1));
- try self.variables.items[@intFromEnum(idx)].arrayAppend(self.values.pop());
+ const var_ptr = &self.variables.items[@intFromEnum(idx)];
+ try var_ptr.arrayAppend(self.allocator, self.values.pop());
},
}
}
@@ -381,7 +425,7 @@ pub const Runtime = struct {
fn restore_stack(self: *Self) bool {
if (self.forks.pop()) |target_pc| {
self.pc = target_pc;
- self.values.restore();
+ self.values.restore(self.allocator);
return true;
}
return false;
diff --git a/src/jq/parse.zig b/src/jq/parse.zig
index a56b7eb..6abd7c2 100644
--- a/src/jq/parse.zig
+++ b/src/jq/parse.zig
@@ -372,7 +372,7 @@ const Parser = struct {
.brace_left => {
_ = try self.tokens.next();
_ = try self.tokens.expect(.brace_right);
- try self.constants.append(self.allocator, jv.Value.initObject(jv.Object.init(self.allocator)));
+ try self.constants.append(self.allocator, jv.Value.initObject(try jv.Object.init(self.allocator)));
const idx: ConstIndex = @enumFromInt(self.constants.items.len - 1);
const object_node = try self.compile_allocator.create(Ast);
object_node.* = .{ .literal = idx };
diff --git a/src/jq/saveable_stack.zig b/src/jq/saveable_stack.zig
index a3085d6..f3a0b40 100644
--- a/src/jq/saveable_stack.zig
+++ b/src/jq/saveable_stack.zig
@@ -101,6 +101,10 @@ pub fn SaveableStack(comptime T: type) type {
return seg.previous_position.offset == 0;
}
+ pub fn isBeyondSavepointBoundary(self: *Self) bool {
+ return self.activeSegment().len() == 0;
+ }
+
pub fn pop(self: *Self) T {
std.debug.assert(!self.isEmpty());
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;
+const Rc = @import("./rc.zig").Rc;
-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 const ValueKind = enum {
+ null,
+ bool,
+ integer,
+ float,
+ string,
+ array,
+ object,
+};
- 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,
- };
- }
+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 kind(self: Value) ValueKind {
+ return self;
+ }
+
+ pub fn clone(self: Value) Value {
+ return switch (self) {
+ .array => |a| .{ .array = a.clone() },
+ .object => |o| .{ .object = o.clone() },
+ else => self,
+ };
}
- pub fn deinit(self: *Value, allocator: Allocator) void {
- switch (self._internal) {
- .string => |s| allocator.free(s),
- .array => |*a| a.deinit(),
- .object => |*o| o.deinit(),
+ 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 boolean(self: Value) bool {
- return self._internal.bool;
+ pub fn arrayAppend(self: *Value, allocator: std.mem.Allocator, item: Value) !void {
+ try self.array.append(allocator, item);
}
- pub fn integer(self: Value) i64 {
- return self._internal.integer;
+ 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 fn float(self: Value) f64 {
- return self._internal.float;
- }
+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);
+ }
}
};
diff --git a/src/root.zig b/src/root.zig
index e61ed1f..e2152d3 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -12,6 +12,7 @@ pub fn run(allocator: std.mem.Allocator, input: []const u8, query: []const u8) !
try runtime.compileFromSlice(query);
try runtime.start(json);
const result = try runtime.next() orelse return error.NoResult;
+ defer result.deinit(allocator);
const output = try jv.stringify(allocator, result);
return output;
}
@@ -34,6 +35,7 @@ fn testRunMultiple(expected: []const []const u8, input: []const u8, query: []con
for (expected) |ex| {
const result_value = try runtime.next() orelse return error.NoResult;
+ defer result_value.deinit(allocator);
const result = try jv.stringify(allocator, result_value);
defer allocator.free(result);
try std.testing.expectEqualStrings(ex, result);