about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-08-11T11·12+0300
committertazjin <tazjin@tvl.su>2022-08-25T16·00+0000
commit4b920912b8ea5ad963b76359c27902f6ece2ceec (patch)
tree2cff2672e3dd53a4669c291d1c54c41f87416f46
parentf3f8262637a522e783e6114c6882e31e7a68da17 (diff)
feat(tvix/compiler): implement `&&` operator r/4485
This logical operator is implemented in terms of jumping operations
and thus requires slightly different treatment than other binary
operators.

Change-Id: Ib3d768b70dd7e16014c9b47d770aa74eec60ae92
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6150
Tested-by: BuildkiteCI
Reviewed-by: grfn <grfn@gws.fyi>
Reviewed-by: sterni <sternenseemann@systemli.org>
-rw-r--r--tvix/eval/src/compiler.rs48
1 files changed, 41 insertions, 7 deletions
diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs
index e38b06b456..f03ebee530 100644
--- a/tvix/eval/src/compiler.rs
+++ b/tvix/eval/src/compiler.rs
@@ -19,7 +19,7 @@ use crate::opcode::{CodeIdx, OpCode};
 use crate::value::Value;
 
 use rnix;
-use rnix::types::{EntryHolder, TokenWrapper, TypedNode, Wrapper};
+use rnix::types::{BinOpKind, EntryHolder, TokenWrapper, TypedNode, Wrapper};
 
 struct Compiler {
     chunk: Chunk,
@@ -142,11 +142,21 @@ 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).
+        match op.operator().unwrap() {
+            BinOpKind::And => return self.compile_and(op),
+            BinOpKind::Implication => todo!(),
+            BinOpKind::Or => todo!(),
+
+            _ => {}
+        };
+
         self.compile(op.lhs().unwrap())?;
         self.compile(op.rhs().unwrap())?;
 
-        use rnix::types::BinOpKind;
-
         match op.operator().unwrap() {
             BinOpKind::Add => self.chunk.add_op(OpCode::OpAdd),
             BinOpKind::Sub => self.chunk.add_op(OpCode::OpSub),
@@ -160,16 +170,15 @@ impl Compiler {
             BinOpKind::MoreOrEq => self.chunk.add_op(OpCode::OpMoreOrEq),
             BinOpKind::Concat => self.chunk.add_op(OpCode::OpConcat),
 
-            BinOpKind::And => todo!(),
-            BinOpKind::Or => todo!(),
-            BinOpKind::Implication => todo!(),
-
             BinOpKind::NotEqual => {
                 self.chunk.add_op(OpCode::OpEqual);
                 self.chunk.add_op(OpCode::OpInvert)
             }
 
             BinOpKind::IsSet => todo!("? operator"),
+
+            // Handled by separate branch above.
+            BinOpKind::And | BinOpKind::Implication | BinOpKind::Or => unreachable!(),
         };
 
         Ok(())
@@ -320,6 +329,31 @@ impl Compiler {
         Ok(())
     }
 
+    fn compile_and(&mut self, node: rnix::types::BinOp) -> EvalResult<()> {
+        debug_assert!(
+            matches!(node.operator(), Some(BinOpKind::And)),
+            "compile_and called with wrong operator kind: {:?}",
+            node.operator(),
+        );
+
+        // Leave left-hand side value on the stack.
+        self.compile(node.lhs().unwrap())?;
+
+        // If this value is false, jump over the right-hand side - the
+        // whole expression is false.
+        let end_idx = self.chunk.add_op(OpCode::OpJumpIfFalse(0));
+
+        // Otherwise, remove the previous value and leave the
+        // right-hand side on the stack. Its result is now the value
+        // of the whole expression.
+        self.chunk.add_op(OpCode::OpPop);
+        self.compile(node.rhs().unwrap())?;
+
+        self.patch_jump(end_idx);
+
+        Ok(())
+    }
+
     fn patch_jump(&mut self, idx: CodeIdx) {
         let offset = self.chunk.code.len() - 1 - idx.0;