about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-08-11T14·06+0300
committertazjin <tazjin@tvl.su>2022-08-26T09·02+0000
commitcf3e3b784bc322c7e2f032b0c803e42524bede38 (patch)
treed66dd0f541b2d759026905a45f49b9f331c81b7a
parente8c4e26b412375e6d1a5cf7eecfb3612bc33908e (diff)
feat(tvix/eval): implement `?` operator (single-level only) r/4495
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 <grfn@gws.fyi>
Reviewed-by: sterni <sternenseemann@systemli.org>
-rw-r--r--tvix/eval/src/compiler.rs49
-rw-r--r--tvix/eval/src/opcode.rs1
-rw-r--r--tvix/eval/src/vm.rs7
3 files changed, 50 insertions, 7 deletions
diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs
index cfcf78f31b..1d56e227f4 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 8b0cafb91f..73cf2098fe 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 c833d7b191..332398d7b9 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));