aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--docs/jq_grammar.md3
-rw-r--r--src/jq/codegen.zig24
-rw-r--r--src/jq/execute.zig24
-rw-r--r--src/jq/parse.zig40
-rw-r--r--src/jv/ops.zig56
-rw-r--r--src/root.zig60
6 files changed, 203 insertions, 4 deletions
diff --git a/docs/jq_grammar.md b/docs/jq_grammar.md
index d636756..f9850cf 100644
--- a/docs/jq_grammar.md
+++ b/docs/jq_grammar.md
@@ -89,6 +89,9 @@ term:
suffix:
'[' query ']' '?'?
+ '[' query ':' query ']' '?'?
+ '[' query ':' ']' '?'?
+ '[' ':' query ']' '?'?
primary:
'null'
diff --git a/src/jq/codegen.zig b/src/jq/codegen.zig
index 6ca5382..62535c1 100644
--- a/src/jq/codegen.zig
+++ b/src/jq/codegen.zig
@@ -19,6 +19,8 @@ pub const Opcode = enum {
subexp_end,
index,
index_opt,
+ slice,
+ slice_opt,
add,
sub,
mul,
@@ -52,6 +54,8 @@ pub const Instr = union(Opcode) {
subexp_end,
index,
index_opt,
+ slice,
+ slice_opt,
add,
sub,
mul,
@@ -97,6 +101,26 @@ const Codegen = struct {
try self.emit(.subexp_end);
try self.emit(if (idx.is_optional) .index_opt else .index);
},
+ .slice => |slice| {
+ try self.generate(slice.base);
+ // from
+ try self.emit(.subexp_begin);
+ if (slice.from) |from| {
+ try self.generate(from);
+ } else {
+ try self.emit(.{ .@"const" = .null });
+ }
+ try self.emit(.subexp_end);
+ // to
+ try self.emit(.subexp_begin);
+ if (slice.to) |to| {
+ try self.generate(to);
+ } else {
+ try self.emit(.{ .@"const" = .null });
+ }
+ try self.emit(.subexp_end);
+ try self.emit(if (slice.is_optional) .slice_opt else .slice);
+ },
.literal => |idx| try self.emit(.{ .@"const" = idx }),
.binary_expr => |binary_expr| {
try self.emit(.subexp_begin);
diff --git a/src/jq/execute.zig b/src/jq/execute.zig
index 3096be2..9fa410f 100644
--- a/src/jq/execute.zig
+++ b/src/jq/execute.zig
@@ -273,6 +273,30 @@ pub const Runtime = struct {
key.deinit(self.allocator);
try self.values.push(result);
},
+ .slice => {
+ std.debug.assert(self.values.ensureSize(3));
+
+ const base = self.values.pop();
+ const to = self.values.pop();
+ const from = self.values.pop();
+ const result = try jv.ops.slice(self.allocator, base, from, to);
+ base.deinit(self.allocator);
+ to.deinit(self.allocator);
+ from.deinit(self.allocator);
+ try self.values.push(result);
+ },
+ .slice_opt => {
+ std.debug.assert(self.values.ensureSize(3));
+
+ const base = self.values.pop();
+ const to = self.values.pop();
+ const from = self.values.pop();
+ const result = jv.ops.slice(self.allocator, base, from, to) catch jv.Value.null;
+ base.deinit(self.allocator);
+ to.deinit(self.allocator);
+ from.deinit(self.allocator);
+ 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 6abd7c2..ad64824 100644
--- a/src/jq/parse.zig
+++ b/src/jq/parse.zig
@@ -12,6 +12,7 @@ pub const ParseError = error{
pub const AstKind = enum {
identity,
index,
+ slice,
literal,
binary_expr,
or_expr,
@@ -47,6 +48,7 @@ pub const BinaryOp = enum {
pub const Ast = union(AstKind) {
identity,
index: struct { base: *Ast, index: *Ast, is_optional: bool },
+ slice: struct { base: *Ast, from: ?*Ast, to: ?*Ast, is_optional: bool },
literal: ConstIndex,
binary_expr: struct { op: BinaryOp, lhs: *Ast, rhs: *Ast },
or_expr: struct { lhs: *Ast, rhs: *Ast },
@@ -397,13 +399,43 @@ const Parser = struct {
fn parseSuffix(self: *Self, base: *Ast) Error!*Ast {
_ = try self.tokens.expect(.bracket_left);
- const index_expr = try self.parseExpr();
- _ = try self.tokens.expect(.bracket_right);
- const is_optional = self.tokens.consumeIf(.question);
+ // Handle [:to] form.
+ if (self.tokens.consumeIf(.colon)) {
+ const to_expr = try self.parseQuery();
+ _ = try self.tokens.expect(.bracket_right);
+ const is_optional = self.tokens.consumeIf(.question);
+ const ast = try self.compile_allocator.create(Ast);
+ ast.* = .{ .slice = .{ .base = base, .from = null, .to = to_expr, .is_optional = is_optional } };
+ return ast;
+ }
+
+ const first_query = try self.parseQuery();
+ // Handle [from:to] or [from:] form.
+ if (self.tokens.consumeIf(.colon)) {
+ if (self.tokens.consumeIf(.bracket_right)) {
+ // [from:]
+ const is_optional = self.tokens.consumeIf(.question);
+ const ast = try self.compile_allocator.create(Ast);
+ ast.* = .{ .slice = .{ .base = base, .from = first_query, .to = null, .is_optional = is_optional } };
+ return ast;
+ } else {
+ // [from:to]
+ const to_expr = try self.parseQuery();
+ _ = try self.tokens.expect(.bracket_right);
+ const is_optional = self.tokens.consumeIf(.question);
+ const ast = try self.compile_allocator.create(Ast);
+ ast.* = .{ .slice = .{ .base = base, .from = first_query, .to = to_expr, .is_optional = is_optional } };
+ return ast;
+ }
+ }
+
+ // Handle [index] form.
+ _ = try self.tokens.expect(.bracket_right);
+ const is_optional = self.tokens.consumeIf(.question);
const ast = try self.compile_allocator.create(Ast);
- ast.* = .{ .index = .{ .base = base, .index = index_expr, .is_optional = is_optional } };
+ ast.* = .{ .index = .{ .base = base, .index = first_query, .is_optional = is_optional } };
return ast;
}
};
diff --git a/src/jv/ops.zig b/src/jv/ops.zig
index 8d5f8fc..e84cb7d 100644
--- a/src/jv/ops.zig
+++ b/src/jv/ops.zig
@@ -5,6 +5,7 @@ const Object = @import("./value.zig").Object;
pub const OpsError = error{
InvalidType,
+ InternalError,
Unimplemented,
};
@@ -23,6 +24,61 @@ pub fn index(base: Value, key: Value) OpsError!Value {
};
}
+pub fn slice(allocator: std.mem.Allocator, base: Value, from: Value, to: Value) OpsError!Value {
+ switch (base) {
+ .array => |a| {
+ const len: i64 = @intCast(a.len());
+
+ var start: i64 = if (from == .null) 0 else if (from == .integer) from.integer else return error.InvalidType;
+ var end: i64 = if (to == .null) len else if (to == .integer) to.integer else return error.InvalidType;
+
+ if (start < 0) start += len;
+ if (end < 0) end += len;
+
+ start = @max(0, @min(start, len));
+ end = @max(0, @min(end, len));
+
+ if (start >= end) {
+ const empty = Array.init(allocator) catch return error.InternalError;
+ return Value.initArray(empty);
+ }
+
+ var result = Array.init(allocator) catch return error.InternalError;
+ const ustart: usize = @intCast(start);
+ const uend: usize = @intCast(end);
+ var i: usize = ustart;
+ while (i < uend) : (i += 1) {
+ result.append(allocator, a.get(i)) catch return error.InternalError;
+ }
+ return Value.initArray(result);
+ },
+ .string => |s| {
+ const len: i64 = @intCast(s.len);
+
+ var start: i64 = if (from == .null) 0 else if (from == .integer) from.integer else return error.InvalidType;
+ var end: i64 = if (to == .null) len else if (to == .integer) to.integer else return error.InvalidType;
+
+ if (start < 0) start += len;
+ if (end < 0) end += len;
+
+ start = @max(0, @min(start, len));
+ end = @max(0, @min(end, len));
+
+ if (start >= end) {
+ const duped = allocator.dupe(u8, "") catch return error.InternalError;
+ return Value.initString(duped);
+ }
+
+ const ustart: usize = @intCast(start);
+ const uend: usize = @intCast(end);
+ const duped = allocator.dupe(u8, s[ustart..uend]) catch return error.InternalError;
+ return Value.initString(duped);
+ },
+ .null => return .null,
+ else => return error.InvalidType,
+ }
+}
+
pub const CompareOp = enum { eq, ne, lt, gt, le, ge };
pub fn compare(lhs: Value, rhs: Value, op: CompareOp) OpsError!bool {
diff --git a/src/root.zig b/src/root.zig
index e2152d3..ac9c21c 100644
--- a/src/root.zig
+++ b/src/root.zig
@@ -118,6 +118,66 @@ test "index access" {
try testRun("5", "[1,2,3,4,5]", ".[2 * 2]");
}
+test "slice" {
+ // [begin:end]
+ try testRun("[]", "[1,2,3]", ".[1:1]");
+ try testRun(
+ \\[
+ \\ 2,
+ \\ 3
+ \\]
+ , "[1,2,3]", ".[1:3]");
+ try testRun(
+ \\[
+ \\ 2
+ \\]
+ , "[1,2,3]", ".[1:2]");
+
+ // [begin:]
+ try testRun(
+ \\[
+ \\ 2,
+ \\ 3
+ \\]
+ , "[1,2,3]", ".[1:]");
+ try testRun(
+ \\[
+ \\ 1,
+ \\ 2,
+ \\ 3
+ \\]
+ , "[1,2,3]", ".[0:]");
+
+ // [:end]
+ try testRun(
+ \\[
+ \\ 1
+ \\]
+ , "[1,2,3]", ".[:1]");
+ try testRun(
+ \\[
+ \\ 1,
+ \\ 2
+ \\]
+ , "[1,2,3]", ".[:2]");
+
+ try testRun(
+ \\[
+ \\ 1,
+ \\ 2,
+ \\ 3
+ \\]
+ , "[1,2,3]", ".[0:10]");
+ try testRun("[]", "[1,2,3]", ".[5:10]");
+
+ try testRun(
+ \\[
+ \\ 2,
+ \\ 3
+ \\]
+ , "[[1,2,3],[4,5,6]]", ".[0] | .[1:]");
+}
+
test "arithmetic operations" {
try testRun("579", "null", "123 + 456");
try testRun("35", "{\"a\":12,\"b\":23}", ".a + .b");