diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-01-25 20:15:24 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-01-25 20:15:24 +0900 |
| commit | 8b8bc79d647285a170aa928ff31a0989c9ef6e33 (patch) | |
| tree | 14d172520dbd36179ec315010813528cdae40b5e /src/jv/ops.zig | |
| parent | 7fb93cc98fc7738e160bd6bc896cbafe7a1aadcc (diff) | |
| download | zgjq-8b8bc79d647285a170aa928ff31a0989c9ef6e33.tar.gz zgjq-8b8bc79d647285a170aa928ff31a0989c9ef6e33.tar.zst zgjq-8b8bc79d647285a170aa928ff31a0989c9ef6e33.zip | |
refactor: move json value operations to separate file
Diffstat (limited to 'src/jv/ops.zig')
| -rw-r--r-- | src/jv/ops.zig | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/src/jv/ops.zig b/src/jv/ops.zig new file mode 100644 index 0000000..3c702c8 --- /dev/null +++ b/src/jv/ops.zig @@ -0,0 +1,188 @@ +const std = @import("std"); +const Value = @import("./value.zig").Value; +const Array = @import("./value.zig").Array; +const Object = @import("./value.zig").Object; + +pub const OpsError = error{ + InvalidType, + Unimplemented, +}; + +pub fn index(base: Value, key: Value) OpsError!Value { + return switch (base) { + .array => |arr| blk: { + const idx: usize = @intCast(switch (key) { + .integer => |i| i, + else => return error.InvalidType, + }); + break :blk if (idx < arr.items.len) arr.items[idx] else .null; + }, + .object => |obj| blk: { + const k = switch (key) { + .string => |s| s, + else => return error.InvalidType, + }; + break :blk obj.get(k) orelse .null; + }, + .null => .null, + else => error.InvalidType, + }; +} + +pub const CompareOp = enum { eq, ne, lt, gt, le, ge }; + +pub fn compare(lhs: Value, rhs: Value, op: CompareOp) OpsError!bool { + const lhs_tag = std.meta.activeTag(lhs); + const rhs_tag = std.meta.activeTag(rhs); + + if (lhs_tag != rhs_tag) { + const lhs_is_number = lhs_tag == .integer or lhs_tag == .float; + const rhs_is_number = rhs_tag == .integer or rhs_tag == .float; + if (lhs_is_number and rhs_is_number) { + return compareNumbers(lhs, rhs, op); + } + return error.InvalidType; + } + + return switch (lhs) { + .null => switch (op) { + .eq => true, + .ne => false, + .lt, .gt, .le, .ge => error.Unimplemented, + }, + .bool => |lhs_bool| { + const rhs_bool = rhs.bool; + return switch (op) { + .eq => lhs_bool == rhs_bool, + .ne => lhs_bool != rhs_bool, + .lt, .gt, .le, .ge => error.Unimplemented, + }; + }, + .integer, .float => compareNumbers(lhs, rhs, op), + .string => |lhs_str| { + const rhs_str = rhs.string; + const order = std.mem.order(u8, lhs_str, rhs_str); + return switch (op) { + .eq => order == .eq, + .ne => order != .eq, + .lt => order == .lt, + .gt => order == .gt, + .le => order == .lt or order == .eq, + .ge => order == .gt or order == .eq, + }; + }, + .array => switch (op) { + .eq, .ne => error.Unimplemented, + .lt, .gt, .le, .ge => error.Unimplemented, + }, + .object => switch (op) { + .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) { + .integer => |i| @floatFromInt(i), + .float => |f| f, + else => unreachable, + }; + const rhs_f: f64 = switch (rhs) { + .integer => |i| @floatFromInt(i), + .float => |f| f, + else => unreachable, + }; + return switch (op) { + .eq => lhs_f == rhs_f, + .ne => lhs_f != rhs_f, + .lt => lhs_f < rhs_f, + .gt => lhs_f > rhs_f, + .le => lhs_f <= rhs_f, + .ge => lhs_f >= rhs_f, + }; +} + +test "index array" { + var arr = Array.init(std.testing.allocator); + defer arr.deinit(); + try arr.append(.{ .integer = 10 }); + try arr.append(.{ .integer = 20 }); + try arr.append(.{ .integer = 30 }); + + const base = Value{ .array = arr }; + + try std.testing.expectEqual(Value{ .integer = 10 }, try index(base, .{ .integer = 0 })); + try std.testing.expectEqual(Value{ .integer = 20 }, try index(base, .{ .integer = 1 })); + try std.testing.expectEqual(Value{ .integer = 30 }, try index(base, .{ .integer = 2 })); + try std.testing.expectEqual(Value.null, try index(base, .{ .integer = 3 })); +} + +test "index object" { + var obj = Object.init(std.testing.allocator); + defer obj.deinit(); + try obj.put("foo", .{ .integer = 1 }); + try obj.put("bar", .{ .integer = 2 }); + + const base = Value{ .object = obj }; + + try std.testing.expectEqual(Value{ .integer = 1 }, try index(base, .{ .string = "foo" })); + try std.testing.expectEqual(Value{ .integer = 2 }, try index(base, .{ .string = "bar" })); + try std.testing.expectEqual(Value.null, try index(base, .{ .string = "baz" })); +} + +test "index null" { + try std.testing.expectEqual(Value.null, try index(.null, .{ .integer = 0 })); + try std.testing.expectEqual(Value.null, try index(.null, .{ .string = "foo" })); +} + +test "index invalid type" { + try std.testing.expectError(error.InvalidType, index(.{ .integer = 42 }, .{ .integer = 0 })); + try std.testing.expectError(error.InvalidType, index(.{ .string = "foo" }, .{ .integer = 0 })); +} + +test "compare integers" { + try std.testing.expect(try compare(.{ .integer = 1 }, .{ .integer = 1 }, .eq)); + try std.testing.expect(!try compare(.{ .integer = 1 }, .{ .integer = 2 }, .eq)); + try std.testing.expect(try compare(.{ .integer = 1 }, .{ .integer = 2 }, .ne)); + try std.testing.expect(try compare(.{ .integer = 1 }, .{ .integer = 2 }, .lt)); + try std.testing.expect(try compare(.{ .integer = 2 }, .{ .integer = 1 }, .gt)); + try std.testing.expect(try compare(.{ .integer = 1 }, .{ .integer = 1 }, .le)); + try std.testing.expect(try compare(.{ .integer = 1 }, .{ .integer = 1 }, .ge)); +} + +test "compare floats" { + try std.testing.expect(try compare(.{ .float = 1.5 }, .{ .float = 1.5 }, .eq)); + try std.testing.expect(try compare(.{ .float = 1.5 }, .{ .float = 2.5 }, .lt)); + try std.testing.expect(try compare(.{ .float = 2.5 }, .{ .float = 1.5 }, .gt)); +} + +test "compare mixed numbers" { + try std.testing.expect(try compare(.{ .integer = 2 }, .{ .float = 2.0 }, .eq)); + try std.testing.expect(try compare(.{ .float = 1.5 }, .{ .integer = 2 }, .lt)); + try std.testing.expect(try compare(.{ .integer = 3 }, .{ .float = 2.5 }, .gt)); +} + +test "compare strings" { + try std.testing.expect(try compare(.{ .string = "abc" }, .{ .string = "abc" }, .eq)); + try std.testing.expect(try compare(.{ .string = "abc" }, .{ .string = "abd" }, .ne)); + try std.testing.expect(try compare(.{ .string = "abc" }, .{ .string = "abd" }, .lt)); + try std.testing.expect(try compare(.{ .string = "abd" }, .{ .string = "abc" }, .gt)); +} + +test "compare booleans" { + try std.testing.expect(try compare(.{ .bool = true }, .{ .bool = true }, .eq)); + try std.testing.expect(try compare(.{ .bool = false }, .{ .bool = false }, .eq)); + try std.testing.expect(try compare(.{ .bool = true }, .{ .bool = false }, .ne)); +} + +test "compare null" { + try std.testing.expect(try compare(.null, .null, .eq)); + try std.testing.expect(!try compare(.null, .null, .ne)); +} + +test "compare different types" { + try std.testing.expectError(error.InvalidType, compare(.{ .integer = 1 }, .{ .string = "1" }, .eq)); + try std.testing.expectError(error.InvalidType, compare(.null, .{ .integer = 0 }, .eq)); +} |
