diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs index 1bfd765a0..3a6c685c7 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 f7f64f4e6..cba46c71f 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 4831a71eb..8b0cafb91 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 9204a5bb9..ecb819fad 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) -> EvalResult { diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index 3e8509187..c833d7b19 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));