about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-08-11T12·29+0300
committertazjin <tazjin@tvl.su>2022-08-26T09·02+0000
commit20f5ccefeb88ce4b79369085977b674c612e8fed (patch)
treeb76f0bea439f792bc0f092f81e8871354c31b75b
parent671915837aee2908431b1d1908352fc0ab9cd628 (diff)
feat(tvix/eval): implement attribute set access operator r/4492
Fairly straightforward, handling the optimised representations
manually and otherwise delegating to BTreeMap.

Note that parsing of raw identifiers is not yet implemented.

Encountering an identifier node usually means that there is locals
access going on, so we need a special case for compiling a node in
such a way that an identifier's literal value ends up on the stack.

Change-Id: I13fbab7ac657b17ef3f4c5859fe737c321890c8a
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6158
Tested-by: BuildkiteCI
Reviewed-by: sterni <sternenseemann@systemli.org>
Reviewed-by: grfn <grfn@gws.fyi>
-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));