aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--docs/jq_grammar.md4
-rw-r--r--src/jq/compile.zig10
-rw-r--r--src/jq/execute.zig8
-rw-r--r--src/jq/parse.zig23
-rw-r--r--src/root.zig12
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");