diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-01-25 00:26:55 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-01-25 00:29:49 +0900 |
| commit | 0b51c7019d55995ba53f361521163007941c844b (patch) | |
| tree | 8beffbb50a7d1e85f53c5cdc532e62e5967c405a | |
| parent | fa8b75121b915e1ae1eb3311fa8051e241e7ddef (diff) | |
| download | zgjq-0b51c7019d55995ba53f361521163007941c844b.tar.gz zgjq-0b51c7019d55995ba53f361521163007941c844b.tar.zst zgjq-0b51c7019d55995ba53f361521163007941c844b.zip | |
implement comma operator
| -rw-r--r-- | src/jq/compile.zig | 25 | ||||
| -rw-r--r-- | src/jq/execute.zig | 29 | ||||
| -rw-r--r-- | src/jq/parse.zig | 32 | ||||
| -rw-r--r-- | src/root.zig | 92 |
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"); } |
