diff options
| -rw-r--r-- | src/jq/execute.zig | 122 | ||||
| -rw-r--r-- | src/jq/parse.zig | 2 | ||||
| -rw-r--r-- | src/jq/saveable_stack.zig | 4 | ||||
| -rw-r--r-- | src/jv/ops.zig | 61 | ||||
| -rw-r--r-- | src/jv/parse.zig | 32 | ||||
| -rw-r--r-- | src/jv/rc.zig | 40 | ||||
| -rw-r--r-- | src/jv/stringify.zig | 2 | ||||
| -rw-r--r-- | src/jv/value.zig | 229 | ||||
| -rw-r--r-- | src/root.zig | 2 |
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); |
