From 7cfdedfdfb6657a05ca8ce423874833eddb4ee72 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 15 Aug 2022 00:13:57 +0300 Subject: feat(tvix/eval): compile `with` expression Adds an additional structure to the compiler's scope to track the runtime "with stack", i.e. the stack of values through which identifiers should be dynamically resolved within a with-scope. When encountering a `with` expression, the value from which the bindings should be resolved is pushed onto the stack and tracked by the compiler in the "with stack", as well as with a "phantom value" which indicates that the stack contains an additional slot which is not available to users via identifiers. Runtime handling of this is not yet implemented. Change-Id: I5e96fb55b6378e8e2a59c20c8518caa6df83da1c Reviewed-on: https://cl.tvl.fyi/c/depot/+/6217 Tested-by: BuildkiteCI Reviewed-by: sterni --- tvix/eval/src/compiler.rs | 71 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 11 deletions(-) (limited to 'tvix/eval/src/compiler.rs') diff --git a/tvix/eval/src/compiler.rs b/tvix/eval/src/compiler.rs index c5516d199add..5edf83535b67 100644 --- a/tvix/eval/src/compiler.rs +++ b/tvix/eval/src/compiler.rs @@ -40,6 +40,17 @@ struct Local { // Scope depth of this local. depth: usize, + + // Phantom locals are not actually accessible by users (e.g. + // intermediate values used for `with`). + phantom: bool, +} + +/// Represents a stack offset containing keys which are currently +/// in-scope through a with expression. +#[derive(Debug)] +struct With { + depth: usize, } /// Represents a scope known during compilation, which can be resolved @@ -54,6 +65,10 @@ struct Scope { // How many scopes "deep" are these locals? scope_depth: usize, + + // Stack indices of attribute sets currently in scope through + // `with`. + with_stack: Vec, } struct Compiler { @@ -140,6 +155,11 @@ impl Compiler { self.compile_let_in(node) } + rnix::SyntaxKind::NODE_WITH => { + let node = rnix::types::With::cast(node).unwrap(); + self.compile_with(node) + } + kind => panic!("visiting unsupported node: {:?}", kind), } } @@ -689,7 +709,7 @@ impl Compiler { // Unless in a non-standard scope, the encountered values are // simply pushed on the stack and their indices noted in the // entries vector. - fn compile_let_in(&mut self, node: rnix::types::LetIn) -> Result<(), Error> { + fn compile_let_in(&mut self, node: rnix::types::LetIn) -> EvalResult<()> { self.begin_scope(); let mut entries = vec![]; let mut from_inherits = vec![]; @@ -709,10 +729,7 @@ impl Compiler { Some(_) => { for ident in inherit.idents() { - self.scope.locals.push(Local { - name: ident.as_str().to_string(), - depth: self.scope.scope_depth, - }); + self.push_local(ident.as_str()); } from_inherits.push(inherit); } @@ -732,11 +749,7 @@ impl Compiler { } entries.push(entry.value().unwrap()); - - self.scope.locals.push(Local { - name: path.pop().unwrap(), - depth: self.scope.scope_depth, - }); + self.push_local(path.pop().unwrap()); } // Now we can add instructions to look up each inherited value @@ -766,6 +779,26 @@ impl Compiler { Ok(()) } + // Compile `with` expressions by emitting instructions that + // pop/remove the indices of attribute sets that are implicitly in + // scope through `with` on the "with-stack". + fn compile_with(&mut self, node: rnix::types::With) -> EvalResult<()> { + // TODO: Detect if the namespace is just an identifier, and + // resolve that directly (thus avoiding duplication on the + // stack). + self.compile(node.namespace().unwrap())?; + + self.push_phantom(); + self.scope.with_stack.push(With { + depth: self.scope.scope_depth, + }); + + self.chunk + .push_op(OpCode::OpPushWith(self.scope.locals.len() - 1)); + + self.compile(node.body().unwrap()) + } + // Emit the literal string value of an identifier. Required for // several operations related to attribute sets, where identifiers // are used as string keys. @@ -819,11 +852,27 @@ impl Compiler { } } + fn push_local>(&mut self, name: S) { + self.scope.locals.push(Local { + name: name.into(), + depth: self.scope.scope_depth, + phantom: false, + }); + } + + fn push_phantom(&mut self) { + self.scope.locals.push(Local { + name: "".into(), + depth: self.scope.scope_depth, + phantom: true, + }); + } + fn resolve_local(&mut self, name: &str) -> Option { let scope = &self.scope; for (idx, local) in scope.locals.iter().enumerate().rev() { - if local.name == name { + if !local.phantom && local.name == name { return Some(idx); } } -- cgit 1.4.1