about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/compiler.rs69
-rw-r--r--tvix/eval/src/opcode.rs3
-rw-r--r--tvix/eval/src/vm.rs19
3 files changed, 88 insertions, 3 deletions
diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs
index 4526bf16d6..d7d6bb38a7 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 a9f7f89040..58372f61cb 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 9a718500b5..e8c0551caf 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)]