diff options
| -rw-r--r-- | docs/jq_grammar.md | 3 | ||||
| -rw-r--r-- | src/jq/codegen.zig | 24 | ||||
| -rw-r--r-- | src/jq/execute.zig | 24 | ||||
| -rw-r--r-- | src/jq/parse.zig | 40 | ||||
| -rw-r--r-- | src/jv/ops.zig | 56 | ||||
| -rw-r--r-- | src/root.zig | 60 |
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"); |
