aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-01-25 16:23:13 +0900
committernsfisis <nsfisis@gmail.com>2026-01-25 16:23:13 +0900
commit20ff09460371b07d7e9683757657a5a3ead005a8 (patch)
tree327089640d923a4bf44d340d584cf3378f654728 /src
parent7e5382e6eac497f6be55a7d947a8c6e6c2ecb390 (diff)
downloadzgjq-20ff09460371b07d7e9683757657a5a3ead005a8.tar.gz
zgjq-20ff09460371b07d7e9683757657a5a3ead005a8.tar.zst
zgjq-20ff09460371b07d7e9683757657a5a3ead005a8.zip
implement comparison operators
Diffstat (limited to 'src')
-rw-r--r--src/jq/compile.zig18
-rw-r--r--src/jq/execute.zig129
-rw-r--r--src/root.zig52
3 files changed, 199 insertions, 0 deletions
diff --git a/src/jq/compile.zig b/src/jq/compile.zig
index bb3a832..ec6ef63 100644
--- a/src/jq/compile.zig
+++ b/src/jq/compile.zig
@@ -16,6 +16,12 @@ pub const Opcode = enum {
mul,
div,
mod,
+ eq,
+ ne,
+ lt,
+ gt,
+ le,
+ ge,
object_key,
literal,
};
@@ -35,6 +41,12 @@ pub const Instr = union(Opcode) {
mul,
div,
mod,
+ eq,
+ ne,
+ lt,
+ gt,
+ le,
+ ge,
object_key: []const u8,
literal: *jv.Value,
@@ -83,6 +95,12 @@ fn compileExpr(allocator: std.mem.Allocator, compile_allocator: std.mem.Allocato
.mul => .mul,
.div => .div,
.mod => .mod,
+ .eq => .eq,
+ .ne => .ne,
+ .lt => .lt,
+ .gt => .gt,
+ .le => .le,
+ .ge => .ge,
else => return error.Unimplemented,
};
try instrs.append(allocator, op_instr);
diff --git a/src/jq/execute.zig b/src/jq/execute.zig
index 3c83d23..380e5be 100644
--- a/src/jq/execute.zig
+++ b/src/jq/execute.zig
@@ -234,6 +234,60 @@ pub const Runtime = struct {
const result = @mod(lhs, rhs);
try self.values.push(.{ .integer = result });
},
+ .eq => {
+ std.debug.assert(self.values.ensureSize(3));
+
+ _ = self.values.pop();
+ const lhs = self.values.pop();
+ const rhs = self.values.pop();
+ const result = try compareValues(lhs, rhs, .eq);
+ try self.values.push(.{ .bool = result });
+ },
+ .ne => {
+ std.debug.assert(self.values.ensureSize(3));
+
+ _ = self.values.pop();
+ const lhs = self.values.pop();
+ const rhs = self.values.pop();
+ const result = try compareValues(lhs, rhs, .ne);
+ try self.values.push(.{ .bool = result });
+ },
+ .lt => {
+ std.debug.assert(self.values.ensureSize(3));
+
+ _ = self.values.pop();
+ const lhs = self.values.pop();
+ const rhs = self.values.pop();
+ const result = try compareValues(lhs, rhs, .lt);
+ try self.values.push(.{ .bool = result });
+ },
+ .gt => {
+ std.debug.assert(self.values.ensureSize(3));
+
+ _ = self.values.pop();
+ const lhs = self.values.pop();
+ const rhs = self.values.pop();
+ const result = try compareValues(lhs, rhs, .gt);
+ try self.values.push(.{ .bool = result });
+ },
+ .le => {
+ std.debug.assert(self.values.ensureSize(3));
+
+ _ = self.values.pop();
+ const lhs = self.values.pop();
+ const rhs = self.values.pop();
+ const result = try compareValues(lhs, rhs, .le);
+ try self.values.push(.{ .bool = result });
+ },
+ .ge => {
+ std.debug.assert(self.values.ensureSize(3));
+
+ _ = self.values.pop();
+ const lhs = self.values.pop();
+ const rhs = self.values.pop();
+ const result = try compareValues(lhs, rhs, .ge);
+ try self.values.push(.{ .bool = result });
+ },
.object_key => |key| {
std.debug.assert(self.values.ensureSize(1));
@@ -265,3 +319,78 @@ pub const Runtime = struct {
}
}
};
+
+const CompareOp = enum { eq, ne, lt, gt, le, ge };
+
+fn compareValues(lhs: jv.Value, rhs: jv.Value, op: CompareOp) ExecuteError!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: jv.Value, rhs: jv.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,
+ };
+}
diff --git a/src/root.zig b/src/root.zig
index 782f078..cce3ce5 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -112,3 +112,55 @@ test "pipe operator" {
test "comma operator" {
try testRunMultiple(&.{ "12", "34", "56" }, "{\"a\":12,\"b\":34,\"c\":56}", ".a,.b,.c");
}
+
+test "comparison operators" {
+ try testRun("true", "null", "1 == 1");
+ try testRun("false", "null", "1 == 2");
+ try testRun("false", "null", "1 != 1");
+ try testRun("true", "null", "1 != 2");
+
+ try testRun("true", "null", "1.5 == 1.5");
+ try testRun("false", "null", "1.5 == 2.5");
+
+ try testRun("true", "null", "1 == 1.0");
+ try testRun("false", "null", "1 == 1.5");
+
+ try testRun("true", "{\"a\":\"foo\",\"b\":\"foo\"}", ".a == .b");
+ try testRun("false", "{\"a\":\"foo\",\"b\":\"bar\"}", ".a == .b");
+ try testRun("true", "{\"a\":\"foo\",\"b\":\"bar\"}", ".a != .b");
+
+ try testRun("true", "null", ". == null");
+ try testRun("false", "null", ". != null");
+
+ try testRun("true", "true", ". == true");
+ try testRun("false", "true", ". == false");
+ try testRun("true", "false", ". != true");
+
+ try testRun("true", "null", "1 < 2");
+ try testRun("false", "null", "2 < 1");
+ try testRun("false", "null", "1 < 1");
+
+ try testRun("true", "null", "2 > 1");
+ try testRun("false", "null", "1 > 2");
+ try testRun("false", "null", "1 > 1");
+
+ try testRun("true", "null", "1 <= 2");
+ try testRun("true", "null", "1 <= 1");
+ try testRun("false", "null", "2 <= 1");
+
+ try testRun("true", "null", "2 >= 1");
+ try testRun("true", "null", "1 >= 1");
+ try testRun("false", "null", "1 >= 2");
+
+ try testRun("true", "null", "1.5 < 2.5");
+ try testRun("false", "null", "2.5 < 1.5");
+
+ try testRun("true", "null", "1 < 1.5");
+ try testRun("false", "null", "2 < 1.5");
+
+ try testRun("true", "{\"a\":\"abc\",\"b\":\"abd\"}", ".a < .b");
+ try testRun("false", "{\"a\":\"abd\",\"b\":\"abc\"}", ".a < .b");
+ try testRun("true", "{\"a\":\"abc\",\"b\":\"abc\"}", ".a <= .b");
+ try testRun("true", "{\"a\":\"abd\",\"b\":\"abc\"}", ".a > .b");
+ try testRun("true", "{\"a\":\"abc\",\"b\":\"abc\"}", ".a >= .b");
+}