diff options
| -rw-r--r-- | docs/jq_grammar.md | 4 | ||||
| -rw-r--r-- | src/jq/compile.zig | 10 | ||||
| -rw-r--r-- | src/jq/execute.zig | 8 | ||||
| -rw-r--r-- | src/jq/parse.zig | 23 | ||||
| -rw-r--r-- | src/root.zig | 12 |
5 files changed, 48 insertions, 9 deletions
diff --git a/docs/jq_grammar.md b/docs/jq_grammar.md index ceb0733..a84164c 100644 --- a/docs/jq_grammar.md +++ b/docs/jq_grammar.md @@ -88,7 +88,7 @@ term: primary { suffix }* suffix: - '[' query ']' + '[' query ']' '?'? primary: 'null' @@ -97,7 +97,7 @@ primary: NUMBER STRING '.' - FIELD + FIELD '?'? ``` diff --git a/src/jq/compile.zig b/src/jq/compile.zig index 9a62bc0..32e309a 100644 --- a/src/jq/compile.zig +++ b/src/jq/compile.zig @@ -13,6 +13,7 @@ pub const Opcode = enum { subexp_begin, subexp_end, index, + index_opt, add, sub, mul, @@ -37,6 +38,7 @@ pub const Instr = union(Opcode) { subexp_begin, subexp_end, index, + index_opt, add, sub, mul, @@ -60,16 +62,16 @@ fn compileExpr(allocator: std.mem.Allocator, compile_allocator: std.mem.Allocato switch (ast.*) { .identity => try instrs.append(allocator, .nop), - .index => |index| { - const base_instrs = try compileExpr(allocator, compile_allocator, index.base); + .index => |idx| { + const base_instrs = try compileExpr(allocator, compile_allocator, idx.base); defer allocator.free(base_instrs); - const index_instrs = try compileExpr(allocator, compile_allocator, index.index); + const index_instrs = try compileExpr(allocator, compile_allocator, idx.index); defer allocator.free(index_instrs); try instrs.appendSlice(allocator, base_instrs); try instrs.append(allocator, .subexp_begin); try instrs.appendSlice(allocator, index_instrs); try instrs.append(allocator, .subexp_end); - try instrs.append(allocator, .index); + try instrs.append(allocator, if (idx.is_optional) .index_opt else .index); }, .literal => |idx| try instrs.append(allocator, .{ .@"const" = idx }), .binary_expr => |binary_expr| { diff --git a/src/jq/execute.zig b/src/jq/execute.zig index 9e3a0d5..caf525a 100644 --- a/src/jq/execute.zig +++ b/src/jq/execute.zig @@ -196,6 +196,14 @@ pub const Runtime = struct { const result = try jv.ops.index(base, key); try self.values.push(result); }, + .index_opt => { + std.debug.assert(self.values.ensureSize(2)); + + const base = self.values.pop(); + const key = self.values.pop(); + const result = jv.ops.index(base, key) catch .null; + try self.values.push(result); + }, .add => { std.debug.assert(self.values.ensureSize(3)); diff --git a/src/jq/parse.zig b/src/jq/parse.zig index b257567..10a4ce9 100644 --- a/src/jq/parse.zig +++ b/src/jq/parse.zig @@ -45,7 +45,7 @@ pub const BinaryOp = enum { pub const Ast = union(AstKind) { identity, - index: struct { base: *Ast, index: *Ast }, + index: struct { base: *Ast, index: *Ast, is_optional: bool }, literal: ConstIndex, binary_expr: struct { op: BinaryOp, lhs: *Ast, rhs: *Ast }, pipe: struct { lhs: *Ast, rhs: *Ast }, @@ -388,6 +388,14 @@ const Parser = struct { }, .field => |name| { _ = try self.tokens.next(); + const is_optional = blk: { + const token = self.tokens.peek() catch break :blk false; + if (token.kind() == .question) { + _ = try self.tokens.next(); + break :blk true; + } + break :blk false; + }; const base_ast = try self.parse_allocator.create(Ast); base_ast.* = .identity; try self.constants.append(self.allocator, .{ .string = try self.allocator.dupe(u8, name) }); @@ -395,7 +403,7 @@ const Parser = struct { const key_ast = try self.parse_allocator.create(Ast); key_ast.* = .{ .literal = idx }; const ast = try self.parse_allocator.create(Ast); - ast.* = .{ .index = .{ .base = base_ast, .index = key_ast } }; + ast.* = .{ .index = .{ .base = base_ast, .index = key_ast, .is_optional = is_optional } }; return ast; }, else => return error.InvalidQuery, @@ -407,13 +415,22 @@ const Parser = struct { const index_token = try self.tokens.expect(.number); _ = try self.tokens.expect(.bracket_right); + const is_optional = blk: { + const token = self.tokens.peek() catch break :blk false; + if (token.kind() == .question) { + _ = try self.tokens.next(); + break :blk true; + } + break :blk false; + }; + try self.constants.append(self.allocator, .{ .integer = @intFromFloat(index_token.number) }); const idx: ConstIndex = @enumFromInt(self.constants.items.len - 1); const index_node = try self.parse_allocator.create(Ast); index_node.* = .{ .literal = idx }; const ast = try self.parse_allocator.create(Ast); - ast.* = .{ .index = .{ .base = base, .index = index_node } }; + ast.* = .{ .index = .{ .base = base, .index = index_node, .is_optional = is_optional } }; return ast; } }; diff --git a/src/root.zig b/src/root.zig index f2cc29a..b1f8614 100644 --- a/src/root.zig +++ b/src/root.zig @@ -124,6 +124,18 @@ test "comma operator" { try testRunMultiple(&.{ "12", "34", "56" }, "{\"a\":12,\"b\":34,\"c\":56}", ".a,.b,.c"); } +test "optional index" { + try testRun("1", "[1,2,3]", ".[0]?"); + try testRun("null", "[1,2,3]", ".[5]?"); + try testRun("null", "null", ".[0]?"); + try testRun("null", "123", ".[0]?"); + + try testRun("123", "{\"a\":123}", ".a?"); + try testRun("null", "{\"a\":123}", ".b?"); + try testRun("null", "null", ".a?"); + try testRun("null", "[1,2,3]", ".a?"); +} + test "comparison operators" { try testRun("true", "null", "1 == 1"); try testRun("false", "null", "1 == 2"); |
