about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/compiler.rs24
-rw-r--r--tvix/eval/src/errors.rs4
-rw-r--r--tvix/eval/src/opcode.rs1
-rw-r--r--tvix/eval/src/value/attrs.rs25
-rw-r--r--tvix/eval/src/vm.rs15
5 files changed, 65 insertions, 4 deletions
diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs
index 1bfd765a07..3a6c685c7d 100644
--- a/tvix/eval/src/compiler.rs
+++ b/tvix/eval/src/compiler.rs
@@ -75,6 +75,11 @@ impl Compiler {
                 self.compile_attr_set(node)
             }
 
+            rnix::SyntaxKind::NODE_SELECT => {
+                let node = rnix::types::Select::cast(node).unwrap();
+                self.compile_select(node)
+            }
+
             rnix::SyntaxKind::NODE_LIST => {
                 let node = rnix::types::List::cast(node).unwrap();
                 self.compile_list(node)
@@ -85,10 +90,7 @@ impl Compiler {
                 self.compile_if_else(node)
             }
 
-            kind => {
-                println!("visiting unsupported node: {:?}", kind);
-                Ok(())
-            }
+            kind => panic!("visiting unsupported node: {:?}", kind),
         }
     }
 
@@ -282,6 +284,20 @@ impl Compiler {
         Ok(())
     }
 
+    fn compile_select(&mut self, node: rnix::types::Select) -> EvalResult<()> {
+        // Push the set onto the stack
+        self.compile(node.set().unwrap())?;
+
+        // Push the key and emit the access instruction.
+        //
+        // This order matters because the key needs to be evaluated
+        // first to fail in the correct order on type errors.
+        self.compile(node.index().unwrap())?;
+        self.chunk.add_op(OpCode::OpAttrsSelect);
+
+        Ok(())
+    }
+
     // Compile list literals into equivalent bytecode. List
     // construction is fairly simple, composing of pushing code for
     // each literal element and an instruction with the element count.
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index f7f64f4e68..cba46c71f4 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -10,6 +10,10 @@ pub enum Error {
         given: &'static str,
     },
 
+    AttributeNotFound {
+        name: String,
+    },
+
     TypeError {
         expected: &'static str,
         actual: &'static str,
diff --git a/tvix/eval/src/opcode.rs b/tvix/eval/src/opcode.rs
index 4831a71ebd..8b0cafb91f 100644
--- a/tvix/eval/src/opcode.rs
+++ b/tvix/eval/src/opcode.rs
@@ -46,6 +46,7 @@ pub enum OpCode {
     OpAttrs(usize),
     OpAttrPath(usize),
     OpAttrsUpdate,
+    OpAttrsSelect,
 
     // Lists
     OpList(usize),
diff --git a/tvix/eval/src/value/attrs.rs b/tvix/eval/src/value/attrs.rs
index 9204a5bb95..ecb819fad7 100644
--- a/tvix/eval/src/value/attrs.rs
+++ b/tvix/eval/src/value/attrs.rs
@@ -45,6 +45,26 @@ impl AttrsRep {
             }
         }
     }
+
+    fn select(&self, key: &str) -> Option<&Value> {
+        match self {
+            AttrsRep::Empty => None,
+
+            AttrsRep::KV { name, value } => {
+                if key == "name" {
+                    return Some(&name);
+                }
+
+                if key == "value" {
+                    return Some(&value);
+                }
+
+                None
+            }
+
+            AttrsRep::Map(map) => map.get(&key.to_string().into()),
+        }
+    }
 }
 
 #[derive(Clone, Debug)]
@@ -133,6 +153,11 @@ impl NixAttrs {
         }
     }
 
+    // Select a value from an attribute set by key.
+    pub fn select(&self, key: &str) -> Option<&Value> {
+        self.0.select(key)
+    }
+
     /// Implement construction logic of an attribute set, to encapsulate
     /// logic about attribute set optimisations inside of this module.
     pub fn construct(count: usize, mut stack_slice: Vec<Value>) -> EvalResult<Self> {
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs
index 3e8509187b..c833d7b191 100644
--- a/tvix/eval/src/vm.rs
+++ b/tvix/eval/src/vm.rs
@@ -158,6 +158,21 @@ impl VM {
                     self.push(Value::Attrs(Rc::new(lhs.update(&rhs))))
                 }
 
+                OpCode::OpAttrsSelect => {
+                    let key = self.pop().as_string()?;
+                    let attrs = self.pop().as_attrs()?;
+
+                    match attrs.select(key.as_str()) {
+                        Some(value) => self.push(value.clone()),
+
+                        None => {
+                            return Err(Error::AttributeNotFound {
+                                name: key.as_str().to_string(),
+                            })
+                        }
+                    }
+                }
+
                 OpCode::OpList(count) => {
                     let list =
                         NixList::construct(count, self.stack.split_off(self.stack.len() - count));