about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/errors.rs3
-rw-r--r--tvix/eval/src/opcode.rs1
-rw-r--r--tvix/eval/src/vm.rs21
3 files changed, 24 insertions, 1 deletions
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index e262d9def4..a5ba5bf400 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -28,6 +28,9 @@ pub enum Error {
 
     // Unknown variable in statically known scope.
     UnknownStaticVariable(rnix::types::Ident),
+
+    // Unknown variable in dynamic scope (with, rec, ...).
+    UnknownDynamicVariable(String),
 }
 
 impl Display for Error {
diff --git a/tvix/eval/src/opcode.rs b/tvix/eval/src/opcode.rs
index 7d2de537d5..7dcfea93aa 100644
--- a/tvix/eval/src/opcode.rs
+++ b/tvix/eval/src/opcode.rs
@@ -55,6 +55,7 @@ pub enum OpCode {
     // `with`-handling
     OpPushWith(usize),
     OpPopWith,
+    OpResolveWith,
 
     // Lists
     OpList(usize),
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs
index 8a6959114f..09e3aa2475 100644
--- a/tvix/eval/src/vm.rs
+++ b/tvix/eval/src/vm.rs
@@ -98,7 +98,7 @@ impl VM {
         #[cfg(feature = "disassembler")]
         let mut tracer = Tracer::new();
 
-        loop {
+        'dispatch: loop {
             let op = self.inc_ip();
             match op {
                 OpCode::OpConstant(idx) => {
@@ -285,6 +285,25 @@ impl VM {
                 OpCode::OpPopWith => {
                     self.with_stack.pop();
                 }
+
+                OpCode::OpResolveWith => {
+                    let ident = self.pop().to_string()?;
+
+                    // Attempt to resolve the variable, starting at
+                    // the back of the with_stack.
+                    'with: for idx in self.with_stack.iter().rev() {
+                        let with = self.stack[*idx].as_attrs()?;
+                        match with.select(ident.as_str()) {
+                            None => continue 'with,
+                            Some(val) => {
+                                self.push(val.clone());
+                                continue 'dispatch;
+                            }
+                        }
+                    }
+
+                    return Err(Error::UnknownDynamicVariable(ident.to_string()));
+                }
             }
 
             #[cfg(feature = "disassembler")]