aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-01-25 00:26:55 +0900
committernsfisis <nsfisis@gmail.com>2026-01-25 00:29:49 +0900
commit0b51c7019d55995ba53f361521163007941c844b (patch)
tree8beffbb50a7d1e85f53c5cdc532e62e5967c405a /src
parentfa8b75121b915e1ae1eb3311fa8051e241e7ddef (diff)
downloadzgjq-0b51c7019d55995ba53f361521163007941c844b.tar.gz
zgjq-0b51c7019d55995ba53f361521163007941c844b.tar.zst
zgjq-0b51c7019d55995ba53f361521163007941c844b.zip
implement comma operator
Diffstat (limited to 'src')
-rw-r--r--src/jq/compile.zig25
-rw-r--r--src/jq/execute.zig29
-rw-r--r--src/jq/parse.zig32
-rw-r--r--src/root.zig92
4 files changed, 127 insertions, 51 deletions
diff --git a/src/jq/compile.zig b/src/jq/compile.zig
index 54563de..40459c0 100644
--- a/src/jq/compile.zig
+++ b/src/jq/compile.zig
@@ -5,6 +5,8 @@ const Ast = @import("./parse.zig").Ast;
pub const Opcode = enum {
nop,
ret,
+ jump,
+ fork,
subexp_begin,
subexp_end,
array_index,
@@ -18,6 +20,8 @@ pub const Instr = union(Opcode) {
nop,
ret,
+ jump: usize,
+ fork: usize,
subexp_begin,
subexp_end,
array_index,
@@ -74,6 +78,27 @@ fn compileExpr(allocator: std.mem.Allocator, compile_allocator: std.mem.Allocato
try instrs.appendSlice(allocator, lhs_instrs);
try instrs.appendSlice(allocator, rhs_instrs);
},
+ .comma => |comma_expr| {
+ // FORK l1
+ // <lhs>
+ // JUMP l2
+ // l1: <rhs>
+ // l2:
+ const lhs_instrs = try compileExpr(allocator, compile_allocator, comma_expr.lhs);
+ defer allocator.free(lhs_instrs);
+ const rhs_instrs = try compileExpr(allocator, compile_allocator, comma_expr.rhs);
+ defer allocator.free(rhs_instrs);
+ const fork_index = instrs.items.len;
+ try instrs.append(allocator, .{ .fork = 0 });
+ try instrs.appendSlice(allocator, lhs_instrs);
+ const jump_index = instrs.items.len;
+ try instrs.append(allocator, .{ .jump = 0 });
+ const l1 = instrs.items.len;
+ try instrs.appendSlice(allocator, rhs_instrs);
+ const l2 = instrs.items.len;
+ instrs.items[fork_index] = .{ .fork = l1 - fork_index };
+ instrs.items[jump_index] = .{ .jump = l2 - jump_index };
+ },
}
return instrs.toOwnedSlice(allocator);
diff --git a/src/jq/execute.zig b/src/jq/execute.zig
index 82b6557..6d4f194 100644
--- a/src/jq/execute.zig
+++ b/src/jq/execute.zig
@@ -110,6 +110,7 @@ pub const Runtime = struct {
allocator: std.mem.Allocator,
values: ValueStack,
+ forks: std.ArrayList(usize),
instrs: []const Instr,
pc: usize,
@@ -117,6 +118,7 @@ pub const Runtime = struct {
return .{
.allocator = allocator,
.values = try ValueStack.init(allocator),
+ .forks = .{},
.instrs = &[_]Instr{},
.pc = 0,
};
@@ -129,6 +131,7 @@ pub const Runtime = struct {
self.allocator.free(self.instrs);
self.values.deinit();
+ self.forks.deinit(self.allocator);
}
pub fn compileFromReader(self: *Self, reader: *std.Io.Reader) !void {
@@ -140,6 +143,11 @@ pub const Runtime = struct {
const ast = try parse(self.allocator, compile_allocator.allocator(), tokens);
const instrs = try compile(self.allocator, compile_allocator.allocator(), ast);
self.instrs = instrs;
+ // std.debug.print("BEGIN\n", .{});
+ // for (self.instrs) |instr| {
+ // std.debug.print("{}\n", .{instr});
+ // }
+ // std.debug.print("END\n", .{});
}
pub fn compileFromSlice(self: *Self, query: []const u8) !void {
@@ -154,14 +162,23 @@ pub const Runtime = struct {
pub fn next(self: *Self) !?jv.Value {
std.debug.assert(self.instrs.len > 0);
+ self.restore_stack();
+
while (self.pc < self.instrs.len) : (self.pc += 1) {
const cur = self.instrs[self.pc];
+ // std.debug.print("{}\n", .{cur});
switch (cur) {
.nop => {},
.ret => {
self.pc += 1;
return self.values.pop();
},
+ .jump => |offset| {
+ self.pc += offset - 1;
+ },
+ .fork => |offset| {
+ try self.save_stack(self.pc + offset);
+ },
.subexp_begin => try self.values.dup(),
.subexp_end => try self.values.swap(),
.array_index => {
@@ -199,4 +216,16 @@ pub const Runtime = struct {
return null;
}
+
+ fn save_stack(self: *Self, target_pc: usize) !void {
+ try self.forks.append(self.allocator, target_pc);
+ try self.values.save();
+ }
+
+ fn restore_stack(self: *Self) void {
+ if (self.forks.pop()) |target_pc| {
+ self.pc = target_pc;
+ self.values.restore();
+ }
+ }
};
diff --git a/src/jq/parse.zig b/src/jq/parse.zig
index fa8b28a..60ba89c 100644
--- a/src/jq/parse.zig
+++ b/src/jq/parse.zig
@@ -15,6 +15,7 @@ pub const AstKind = enum {
literal,
binary_expr,
pipe,
+ comma,
};
pub const BinaryOp = enum {
@@ -28,6 +29,7 @@ pub const Ast = union(AstKind) {
literal: *jv.Value,
binary_expr: struct { op: BinaryOp, lhs: *Ast, rhs: *Ast },
pipe: struct { lhs: *Ast, rhs: *Ast },
+ comma: struct { lhs: *Ast, rhs: *Ast },
pub fn kind(self: @This()) AstKind {
return self;
@@ -101,8 +103,36 @@ fn parseQuery(allocator: std.mem.Allocator, parse_allocator: std.mem.Allocator,
}
// GRAMMAR
-// expr := term ("+" term)*
+// expr := expr1
fn parseExpr(allocator: std.mem.Allocator, parse_allocator: std.mem.Allocator, tokens: *TokenStream) !*Ast {
+ return parseExpr1(allocator, parse_allocator, tokens);
+}
+
+// GRAMMAR
+// expr1 := expr2 ("," expr2)*
+fn parseExpr1(allocator: std.mem.Allocator, parse_allocator: std.mem.Allocator, tokens: *TokenStream) !*Ast {
+ var lhs = try parseExpr2(allocator, parse_allocator, tokens);
+ while (true) {
+ const token = try tokens.peek();
+ if (token.kind() == .comma) {
+ _ = try tokens.next();
+ const rhs = try parseExpr2(allocator, parse_allocator, tokens);
+ const ast = try parse_allocator.create(Ast);
+ ast.* = .{ .comma = .{
+ .lhs = lhs,
+ .rhs = rhs,
+ } };
+ lhs = ast;
+ } else {
+ break;
+ }
+ }
+ return lhs;
+}
+
+// GRAMMAR
+// expr2 := term ("+" term)*
+fn parseExpr2(allocator: std.mem.Allocator, parse_allocator: std.mem.Allocator, tokens: *TokenStream) !*Ast {
var lhs = try parseTerm(allocator, parse_allocator, tokens);
while (true) {
const token = try tokens.peek();
diff --git a/src/root.zig b/src/root.zig
index ccfd36d..0d2a3ce 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -16,7 +16,13 @@ pub fn run(allocator: std.mem.Allocator, input: []const u8, query: []const u8) !
return output;
}
-fn testRun(expected: []const u8, allocator: std.mem.Allocator, input: []const u8, query: []const u8) !void {
+fn testRun(expected: []const u8, input: []const u8, query: []const u8) !void {
+ try testRunMultiple(&.{expected}, input, query);
+}
+
+fn testRunMultiple(expected: []const []const u8, input: []const u8, query: []const u8) !void {
+ const allocator = std.testing.allocator;
+
const parsed = try jv.parse(allocator, input);
defer parsed.deinit();
const json = parsed.value;
@@ -26,40 +32,34 @@ fn testRun(expected: []const u8, allocator: std.mem.Allocator, input: []const u8
try runtime.compileFromSlice(query);
try runtime.start(json);
- const result_value = try runtime.next() orelse return error.NoResult;
- const result = try jv.stringify(allocator, result_value);
- defer allocator.free(result);
- try std.testing.expectEqualStrings(expected, result);
+ for (expected) |ex| {
+ const result_value = try runtime.next() orelse return error.NoResult;
+ const result = try jv.stringify(allocator, result_value);
+ defer allocator.free(result);
+ try std.testing.expectEqualStrings(ex, result);
+ }
try std.testing.expectEqual(null, try runtime.next());
}
test "identity filter" {
- var debug_allocator = std.heap.DebugAllocator(.{}).init;
- defer std.debug.assert(debug_allocator.deinit() == .ok);
- const allocator = debug_allocator.allocator();
-
- try testRun("null", allocator, "null", ".");
- try testRun("false", allocator, "false", ".");
- try testRun("true", allocator, "true", ".");
- try testRun("123", allocator, "123", ".");
- try testRun("3.1415", allocator, "3.1415", ".");
- try testRun("[]", allocator, "[]", ".");
- try testRun("{}", allocator, "{}", ".");
- try testRun("[1,2,3]", allocator, "[1,2,3]", ".");
- try testRun("{\"a\":123}", allocator, "{\"a\":123}", ".");
+ try testRun("null", "null", ".");
+ try testRun("false", "false", ".");
+ try testRun("true", "true", ".");
+ try testRun("123", "123", ".");
+ try testRun("3.1415", "3.1415", ".");
+ try testRun("[]", "[]", ".");
+ try testRun("{}", "{}", ".");
+ try testRun("[1,2,3]", "[1,2,3]", ".");
+ try testRun("{\"a\":123}", "{\"a\":123}", ".");
}
test "array index filter" {
- var debug_allocator = std.heap.DebugAllocator(.{}).init;
- defer std.debug.assert(debug_allocator.deinit() == .ok);
- const allocator = debug_allocator.allocator();
-
- try testRun("null", allocator, "[]", ".[0]");
- try testRun("1", allocator, "[1,2,3]", ".[0]");
- try testRun("null", allocator, "[1,2,3]", ".[5]");
- try testRun("11", allocator, "[0,1,2,3,4,5,6,7,8,9,10,11,12]", ".[11]");
- try testRun("100", allocator,
+ try testRun("null", "[]", ".[0]");
+ try testRun("1", "[1,2,3]", ".[0]");
+ try testRun("null", "[1,2,3]", ".[5]");
+ try testRun("11", "[0,1,2,3,4,5,6,7,8,9,10,11,12]", ".[11]");
+ try testRun("100",
\\[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
\\ 21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
\\ 41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,
@@ -69,34 +69,26 @@ test "array index filter" {
}
test "object key filter" {
- var debug_allocator = std.heap.DebugAllocator(.{}).init;
- defer std.debug.assert(debug_allocator.deinit() == .ok);
- const allocator = debug_allocator.allocator();
-
- try testRun("123", allocator, "{\"a\":123}", ".a");
- try testRun("null", allocator, "{\"a\":123}", ".b");
- try testRun("\"hello\"", allocator, "{\"foo\":\"hello\"}", ".foo");
- try testRun("[1,2,3]", allocator, "{\"arr\":[1,2,3]}", ".arr");
- try testRun("{\"bar\":true}", allocator, "{\"foo\":{\"bar\":true}}", ".foo");
+ try testRun("123", "{\"a\":123}", ".a");
+ try testRun("null", "{\"a\":123}", ".b");
+ try testRun("\"hello\"", "{\"foo\":\"hello\"}", ".foo");
+ try testRun("[1,2,3]", "{\"arr\":[1,2,3]}", ".arr");
+ try testRun("{\"bar\":true}", "{\"foo\":{\"bar\":true}}", ".foo");
}
test "addition" {
- var debug_allocator = std.heap.DebugAllocator(.{}).init;
- defer std.debug.assert(debug_allocator.deinit() == .ok);
- const allocator = debug_allocator.allocator();
-
- try testRun("579", allocator, "null", "123 + 456");
- try testRun("35", allocator, "{\"a\":12,\"b\":23}", ".a + .b");
- try testRun("12", allocator, "[1,2,3]", ".[1] + 10");
- try testRun("6", allocator, "null", "1 + 2 + 3");
+ try testRun("579", "null", "123 + 456");
+ try testRun("35", "{\"a\":12,\"b\":23}", ".a + .b");
+ try testRun("12", "[1,2,3]", ".[1] + 10");
+ try testRun("6", "null", "1 + 2 + 3");
}
test "pipe operator" {
- var debug_allocator = std.heap.DebugAllocator(.{}).init;
- defer std.debug.assert(debug_allocator.deinit() == .ok);
- const allocator = debug_allocator.allocator();
+ try testRun("123", "{\"a\":{\"b\":123}}", ".a | .b");
+ try testRun("584", "null", "123 + 456 | . + 5");
+ try testRun("10", "null", "1 | . + 2 | . + 3 | . | 4 + .");
+}
- try testRun("123", allocator, "{\"a\":{\"b\":123}}", ".a | .b");
- try testRun("584", allocator, "null", "123 + 456 | . + 5");
- try testRun("10", allocator, "null", "1 | . + 2 | . + 3 | . | 4 + .");
+test "comma operator" {
+ try testRunMultiple(&.{ "12", "34", "56" }, "{\"a\":12,\"b\":34,\"c\":56}", ".a,.b,.c");
}