diff options
Diffstat (limited to 'tvix/eval')
-rw-r--r-- | tvix/eval/src/compiler.rs | 69 | ||||
-rw-r--r-- | tvix/eval/src/opcode.rs | 3 | ||||
-rw-r--r-- | tvix/eval/src/vm.rs | 19 |
3 files changed, 88 insertions, 3 deletions
diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs index 4526bf16d6b7..d7d6bb38a757 100644 --- a/tvix/eval/src/compiler.rs +++ b/tvix/eval/src/compiler.rs @@ -4,9 +4,10 @@ use crate::chunk::Chunk; use crate::errors::EvalResult; use crate::opcode::OpCode; -use crate::value::Value; +use crate::value::{NixString, Value}; + use rnix; -use rnix::types::{TokenWrapper, TypedNode, Wrapper}; +use rnix::types::{EntryHolder, TokenWrapper, TypedNode, Wrapper}; struct Compiler { chunk: Chunk, @@ -46,6 +47,11 @@ impl Compiler { self.compile_ident(node) } + rnix::SyntaxKind::NODE_ATTR_SET => { + let node = rnix::types::AttrSet::cast(node).unwrap(); + self.compile_attr_set(node) + } + kind => { println!("visiting unsupported node: {:?}", kind); Ok(()) @@ -125,6 +131,65 @@ impl Compiler { Ok(()) } + + // Compile attribute set literals into equivalent bytecode. + // + // This is complicated by a number of features specific to Nix + // attribute sets, most importantly: + // + // 1. Keys can be dynamically constructed through interpolation. + // 2. Keys can refer to nested attribute sets. + // 3. Attribute sets can (optionally) be recursive. + fn compile_attr_set(&mut self, node: rnix::types::AttrSet) -> EvalResult<()> { + let mut count = 0; + + for kv in node.entries() { + count += 1; + + // Because attribute set literals can contain nested keys, + // there is potentially more than one key fragment. If + // this is the case, a special operation to construct a + // runtime value representing the attribute path is + // emitted. + let mut key_count = 0; + for fragment in kv.key().unwrap().path() { + key_count += 1; + + match fragment.kind() { + rnix::SyntaxKind::NODE_IDENT => { + let ident = rnix::types::Ident::cast(fragment).unwrap(); + + // TODO(tazjin): intern! + let idx = self + .chunk + .add_constant(Value::String(NixString(ident.as_str().to_string()))); + self.chunk.add_op(OpCode::OpConstant(idx)); + } + + // For all other expression types, we simply + // compile them as normal. The operation should + // result in a string value, which is checked at + // runtime on construction. + _ => self.compile(fragment)?, + } + } + + // We're done with the key if there was only one fragment, + // otherwise we need to emit an instruction to construct + // the attribute path. + if key_count > 1 { + todo!("emit OpAttrPath(n) instruction") + } + + // The value is just compiled as normal so that its + // resulting value is on the stack when the attribute set + // is constructed at runtime. + self.compile(kv.value().unwrap())?; + } + + self.chunk.add_op(OpCode::OpAttrs(count)); + Ok(()) + } } pub fn compile(ast: rnix::AST) -> EvalResult<Chunk> { diff --git a/tvix/eval/src/opcode.rs b/tvix/eval/src/opcode.rs index a9f7f8904058..58372f61cb17 100644 --- a/tvix/eval/src/opcode.rs +++ b/tvix/eval/src/opcode.rs @@ -29,4 +29,7 @@ pub enum OpCode { // Logical binary operators OpEqual, + + // Attribute sets + OpAttrs(usize), } diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs index 9a718500b5f1..e8c0551caf67 100644 --- a/tvix/eval/src/vm.rs +++ b/tvix/eval/src/vm.rs @@ -1,11 +1,13 @@ //! This module implements the virtual (or abstract) machine that runs //! Tvix bytecode. +use std::{collections::BTreeMap, rc::Rc}; + use crate::{ chunk::Chunk, errors::{Error, EvalResult}, opcode::OpCode, - value::Value, + value::{NixAttrs, NixString, Value}, }; pub struct VM { @@ -114,6 +116,7 @@ impl VM { OpCode::OpNull => self.push(Value::Null), OpCode::OpTrue => self.push(Value::Bool(true)), OpCode::OpFalse => self.push(Value::Bool(false)), + OpCode::OpAttrs(count) => self.run_attrset(count)?, } if self.ip == self.chunk.code.len() { @@ -121,6 +124,20 @@ impl VM { } } } + + fn run_attrset(&mut self, count: usize) -> EvalResult<()> { + let mut attrs: BTreeMap<NixString, Value> = BTreeMap::new(); + + for _ in 0..count { + let value = self.pop(); + let key = self.pop().as_string()?; // TODO(tazjin): attrpath + attrs.insert(key, value); + } + // TODO(tazjin): extend_reserve(count) (rust#72631) + + self.push(Value::Attrs(Rc::new(NixAttrs::Map(attrs)))); + Ok(()) + } } #[derive(Clone, Copy, Debug, PartialEq)] |