diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/jq/compile.zig | 99 | ||||
| -rw-r--r-- | src/jq/execute.zig | 37 | ||||
| -rw-r--r-- | src/jq/parse.zig | 12 | ||||
| -rw-r--r-- | src/root.zig | 38 |
4 files changed, 180 insertions, 6 deletions
diff --git a/src/jq/compile.zig b/src/jq/compile.zig index 32e309a..043262e 100644 --- a/src/jq/compile.zig +++ b/src/jq/compile.zig @@ -9,7 +9,10 @@ pub const Opcode = enum { nop, ret, jump, + jump_unless, fork, + dup, + pop, subexp_begin, subexp_end, index, @@ -26,6 +29,8 @@ pub const Opcode = enum { le, ge, @"const", + const_true, + const_false, }; pub const Instr = union(Opcode) { @@ -34,7 +39,10 @@ pub const Instr = union(Opcode) { nop, ret, jump: usize, + jump_unless: usize, fork: usize, + dup, + pop, subexp_begin, subexp_end, index, @@ -51,6 +59,8 @@ pub const Instr = union(Opcode) { le, ge, @"const": ConstIndex, + const_true, + const_false, pub fn op(self: Self) Opcode { return self; @@ -101,6 +111,95 @@ fn compileExpr(allocator: std.mem.Allocator, compile_allocator: std.mem.Allocato }; try instrs.append(allocator, op_instr); }, + .and_expr => |and_expr| { + // DUP + // <lhs> + // JUMP_UNLESS l3 + // POP + // <rhs> + // JUMP_UNLESS l1 + // CONST_TRUE + // JUMP l2 + // l1: CONST_FALSE + // l2: JUMP l4 + // l3: POP + // CONST_FALSE + // l4: + const lhs_instrs = try compileExpr(allocator, compile_allocator, and_expr.lhs); + defer allocator.free(lhs_instrs); + const rhs_instrs = try compileExpr(allocator, compile_allocator, and_expr.rhs); + defer allocator.free(rhs_instrs); + + try instrs.append(allocator, .dup); + try instrs.appendSlice(allocator, lhs_instrs); + const jump1_idx = instrs.items.len; + try instrs.append(allocator, .{ .jump_unless = 0 }); + try instrs.append(allocator, .pop); + try instrs.appendSlice(allocator, rhs_instrs); + const jump2_idx = instrs.items.len; + try instrs.append(allocator, .{ .jump_unless = 0 }); + try instrs.append(allocator, .const_true); + const jump3_idx = instrs.items.len; + try instrs.append(allocator, .{ .jump = 0 }); + const l1 = instrs.items.len; + try instrs.append(allocator, .const_false); + const jump4_idx = instrs.items.len; + const l2 = instrs.items.len; + try instrs.append(allocator, .{ .jump = 0 }); + const l3 = instrs.items.len; + try instrs.append(allocator, .pop); + try instrs.append(allocator, .const_false); + const l4 = instrs.items.len; + + instrs.items[jump1_idx] = .{ .jump_unless = l3 - jump1_idx }; + instrs.items[jump2_idx] = .{ .jump_unless = l1 - jump2_idx }; + instrs.items[jump3_idx] = .{ .jump = l2 - jump3_idx }; + instrs.items[jump4_idx] = .{ .jump = l4 - jump4_idx }; + }, + .or_expr => |or_expr| { + // DUP + // <lhs> + // JUMP_UNLESS l1 + // POP + // CONST_TRUE + // JUMP l3 + // l1: POP + // <rhs> + // JUMP_UNLESS l2 + // CONST_TRUE + // JUMP l3 + // l2: CONST_FALSE + // l3: + const lhs_instrs = try compileExpr(allocator, compile_allocator, or_expr.lhs); + defer allocator.free(lhs_instrs); + const rhs_instrs = try compileExpr(allocator, compile_allocator, or_expr.rhs); + defer allocator.free(rhs_instrs); + + try instrs.append(allocator, .dup); + try instrs.appendSlice(allocator, lhs_instrs); + const jump1_idx = instrs.items.len; + try instrs.append(allocator, .{ .jump_unless = 0 }); + try instrs.append(allocator, .pop); + try instrs.append(allocator, .const_true); + const jump2_idx = instrs.items.len; + try instrs.append(allocator, .{ .jump = 0 }); + const l1 = instrs.items.len; + try instrs.append(allocator, .pop); + try instrs.appendSlice(allocator, rhs_instrs); + const jump3_idx = instrs.items.len; + try instrs.append(allocator, .{ .jump_unless = 0 }); + try instrs.append(allocator, .const_true); + const jump4_idx = instrs.items.len; + try instrs.append(allocator, .{ .jump = 0 }); + const l2 = instrs.items.len; + try instrs.append(allocator, .const_false); + const l3 = instrs.items.len; + + instrs.items[jump1_idx] = .{ .jump_unless = l1 - jump1_idx }; + instrs.items[jump2_idx] = .{ .jump = l3 - jump2_idx }; + instrs.items[jump3_idx] = .{ .jump_unless = l2 - jump3_idx }; + instrs.items[jump4_idx] = .{ .jump = l3 - jump4_idx }; + }, .pipe => |pipe_expr| { const lhs_instrs = try compileExpr(allocator, compile_allocator, pipe_expr.lhs); defer allocator.free(lhs_instrs); diff --git a/src/jq/execute.zig b/src/jq/execute.zig index caf525a..b30bd3d 100644 --- a/src/jq/execute.zig +++ b/src/jq/execute.zig @@ -183,9 +183,34 @@ pub const Runtime = struct { .jump => |offset| { self.pc += offset - 1; }, + .jump_unless => |offset| { + std.debug.assert(self.values.ensureSize(1)); + + const value = self.values.pop(); + const is_falsy = switch (value) { + .null => true, + .bool => |b| !b, + else => false, + }; + if (is_falsy) { + self.pc += offset - 1; + } + // FIXME: optimize pop and push + try self.values.push(value); + }, .fork => |offset| { try self.save_stack(self.pc + offset); }, + .dup => { + std.debug.assert(self.values.ensureSize(1)); + + try self.values.dup(); + }, + .pop => { + std.debug.assert(self.values.ensureSize(1)); + + _ = self.values.pop(); + }, .subexp_begin => try self.values.dup(), .subexp_end => try self.values.swap(), .index => { @@ -309,6 +334,18 @@ pub const Runtime = struct { _ = self.values.pop(); try self.values.push(self.constants.items[@intFromEnum(idx)]); }, + .const_true => { + std.debug.assert(self.values.ensureSize(1)); + + _ = self.values.pop(); + try self.values.push(.{ .bool = true }); + }, + .const_false => { + std.debug.assert(self.values.ensureSize(1)); + + _ = self.values.pop(); + try self.values.push(.{ .bool = false }); + }, } } diff --git a/src/jq/parse.zig b/src/jq/parse.zig index 58c3722..1760875 100644 --- a/src/jq/parse.zig +++ b/src/jq/parse.zig @@ -14,6 +14,8 @@ pub const AstKind = enum { index, literal, binary_expr, + or_expr, + and_expr, pipe, comma, }; @@ -28,8 +30,6 @@ pub const BinaryOp = enum { mul_assign, div_assign, mod_assign, - @"or", - @"and", eq, ne, lt, @@ -48,6 +48,8 @@ pub const Ast = union(AstKind) { index: struct { base: *Ast, index: *Ast, is_optional: bool }, literal: ConstIndex, binary_expr: struct { op: BinaryOp, lhs: *Ast, rhs: *Ast }, + or_expr: struct { lhs: *Ast, rhs: *Ast }, + and_expr: struct { lhs: *Ast, rhs: *Ast }, pipe: struct { lhs: *Ast, rhs: *Ast }, comma: struct { lhs: *Ast, rhs: *Ast }, @@ -202,8 +204,7 @@ const Parser = struct { } const rhs = try self.parseExpr4(); const ast = try self.parse_allocator.create(Ast); - ast.* = .{ .binary_expr = .{ - .op = .@"or", + ast.* = .{ .or_expr = .{ .lhs = lhs, .rhs = rhs, } }; @@ -217,8 +218,7 @@ const Parser = struct { } const rhs = try self.parseExpr5(); const ast = try self.parse_allocator.create(Ast); - ast.* = .{ .binary_expr = .{ - .op = .@"and", + ast.* = .{ .and_expr = .{ .lhs = lhs, .rhs = rhs, } }; diff --git a/src/root.zig b/src/root.zig index 123dc7e..b612d89 100644 --- a/src/root.zig +++ b/src/root.zig @@ -216,3 +216,41 @@ test "comparison operators" { try testRun("true", "{\"a\":\"abd\",\"b\":\"abc\"}", ".a > .b"); try testRun("true", "{\"a\":\"abc\",\"b\":\"abc\"}", ".a >= .b"); } + +test "and operator" { + try testRun("true", "null", "true and true"); + try testRun("false", "null", "true and false"); + try testRun("false", "null", "false and true"); + try testRun("false", "null", "false and false"); + + try testRun("false", "null", "null and true"); + try testRun("false", "null", "true and null"); + + try testRun("true", "null", "1 and 1"); + try testRun("false", "null", "1 and false"); + try testRun("true", "null", "\"hello\" and true"); + + try testRun("true", "{\"a\":true,\"b\":true}", ".a and .b"); + try testRun("false", "{\"a\":true,\"b\":false}", ".a and .b"); + try testRun("false", "{\"a\":false,\"b\":true}", ".a and .b"); +} + +test "or operator" { + try testRun("true", "null", "true or true"); + try testRun("true", "null", "true or false"); + try testRun("true", "null", "false or true"); + try testRun("false", "null", "false or false"); + + try testRun("true", "null", "null or true"); + try testRun("true", "null", "true or null"); + try testRun("false", "null", "null or false"); + try testRun("false", "null", "false or null"); + + try testRun("true", "null", "1 or false"); + try testRun("true", "null", "false or 1"); + try testRun("false", "null", "false or false"); + + try testRun("true", "{\"a\":true,\"b\":false}", ".a or .b"); + try testRun("true", "{\"a\":false,\"b\":true}", ".a or .b"); + try testRun("false", "{\"a\":false,\"b\":false}", ".a or .b"); +} |
