From cf3e3b784bc322c7e2f032b0c803e42524bede38 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Thu, 11 Aug 2022 17:06:23 +0300 Subject: feat(tvix/eval): implement `?` operator (single-level only) This makes it possible to check things like `{} ? a` with a single level of nesting. Change-Id: I567c36fcfd2f9e2f60071acd3ebfe56dea59b26f Reviewed-on: https://cl.tvl.fyi/c/depot/+/6161 Tested-by: BuildkiteCI Reviewed-by: grfn Reviewed-by: sterni --- tvix/eval/src/compiler.rs | 49 ++++++++++++++++++++++++++++++++++++++++------- tvix/eval/src/opcode.rs | 1 + tvix/eval/src/vm.rs | 7 +++++++ 3 files changed, 50 insertions(+), 7 deletions(-) (limited to 'tvix/eval') diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs index cfcf78f31bf6..1d56e227f41d 100644 --- a/tvix/eval/src/compiler.rs +++ b/tvix/eval/src/compiler.rs @@ -163,14 +163,15 @@ impl Compiler { } fn compile_binop(&mut self, op: rnix::types::BinOp) -> EvalResult<()> { - // Short-circuiting logical operators, which are under the - // same node type as NODE_BIN_OP, but need to be handled - // separately (i.e. before compiling the expressions used for - // standard binary operators). + // Short-circuiting and other strange operators, which are + // under the same node type as NODE_BIN_OP, but need to be + // handled separately (i.e. before compiling the expressions + // used for standard binary operators). match op.operator().unwrap() { BinOpKind::And => return self.compile_and(op), BinOpKind::Or => return self.compile_or(op), BinOpKind::Implication => return self.compile_implication(op), + BinOpKind::IsSet => return self.compile_is_set(op), _ => {} }; @@ -196,10 +197,10 @@ impl Compiler { self.chunk.add_op(OpCode::OpInvert) } - BinOpKind::IsSet => todo!("? operator"), - // Handled by separate branch above. - BinOpKind::And | BinOpKind::Implication | BinOpKind::Or => unreachable!(), + BinOpKind::And | BinOpKind::Implication | BinOpKind::Or | BinOpKind::IsSet => { + unreachable!() + } }; Ok(()) @@ -432,6 +433,40 @@ impl Compiler { Ok(()) } + fn compile_is_set(&mut self, node: rnix::types::BinOp) -> EvalResult<()> { + debug_assert!( + matches!(node.operator(), Some(BinOpKind::IsSet)), + "compile_is_set called with wrong operator kind: {:?}", + node.operator(), + ); + + // Put the attribute set on the stack. + self.compile(node.lhs().unwrap())?; + + // If the key is a NODE_SELECT, the check is deeper than one + // level and requires special handling. + // + // Otherwise, the right hand side is the (only) key expression + // itself and can be compiled directly. + let rhs = node.rhs().unwrap(); + + if matches!(rhs.kind(), rnix::SyntaxKind::NODE_SELECT) { + // Keep nesting deeper until we encounter something + // different than `NODE_SELECT` on the left side. This is + // required because `rnix` parses nested keys as select + // expressions, instead of as a key expression. + // + // The parsed tree will nest something like `a.b.c.d.e.f` + // as (((((a, b), c), d), e), f). + todo!("nested '?' check") + } else { + self.compile_with_literal_ident(rhs)?; + } + + self.chunk.add_op(OpCode::OpAttrsIsSet); + Ok(()) + } + fn patch_jump(&mut self, idx: CodeIdx) { let offset = self.chunk.code.len() - 1 - idx.0; diff --git a/tvix/eval/src/opcode.rs b/tvix/eval/src/opcode.rs index 8b0cafb91f4c..73cf2098fe21 100644 --- a/tvix/eval/src/opcode.rs +++ b/tvix/eval/src/opcode.rs @@ -47,6 +47,7 @@ pub enum OpCode { OpAttrPath(usize), OpAttrsUpdate, OpAttrsSelect, + OpAttrsIsSet, // Lists OpList(usize), diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index c833d7b191d7..332398d7b9ff 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -173,6 +173,13 @@ impl VM { } } + OpCode::OpAttrsIsSet => { + let key = self.pop().as_string()?; + let attrs = self.pop().as_attrs()?; + let result = Value::Bool(attrs.select(key.as_str()).is_some()); + self.push(result); + } + OpCode::OpList(count) => { let list = NixList::construct(count, self.stack.split_off(self.stack.len() - count)); -- cgit 1.4.1