feat(tvix/eval): implement attribute set access operator
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>
This commit is contained in:
parent
671915837a
commit
20f5ccefeb
5 changed files with 65 additions and 4 deletions
|
@ -75,6 +75,11 @@ impl Compiler {
|
||||||
self.compile_attr_set(node)
|
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 => {
|
rnix::SyntaxKind::NODE_LIST => {
|
||||||
let node = rnix::types::List::cast(node).unwrap();
|
let node = rnix::types::List::cast(node).unwrap();
|
||||||
self.compile_list(node)
|
self.compile_list(node)
|
||||||
|
@ -85,10 +90,7 @@ impl Compiler {
|
||||||
self.compile_if_else(node)
|
self.compile_if_else(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
kind => {
|
kind => panic!("visiting unsupported node: {:?}", kind),
|
||||||
println!("visiting unsupported node: {:?}", kind);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,6 +284,20 @@ impl Compiler {
|
||||||
Ok(())
|
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
|
// Compile list literals into equivalent bytecode. List
|
||||||
// construction is fairly simple, composing of pushing code for
|
// construction is fairly simple, composing of pushing code for
|
||||||
// each literal element and an instruction with the element count.
|
// each literal element and an instruction with the element count.
|
||||||
|
|
|
@ -10,6 +10,10 @@ pub enum Error {
|
||||||
given: &'static str,
|
given: &'static str,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
AttributeNotFound {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
|
||||||
TypeError {
|
TypeError {
|
||||||
expected: &'static str,
|
expected: &'static str,
|
||||||
actual: &'static str,
|
actual: &'static str,
|
||||||
|
|
|
@ -46,6 +46,7 @@ pub enum OpCode {
|
||||||
OpAttrs(usize),
|
OpAttrs(usize),
|
||||||
OpAttrPath(usize),
|
OpAttrPath(usize),
|
||||||
OpAttrsUpdate,
|
OpAttrsUpdate,
|
||||||
|
OpAttrsSelect,
|
||||||
|
|
||||||
// Lists
|
// Lists
|
||||||
OpList(usize),
|
OpList(usize),
|
||||||
|
|
|
@ -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)]
|
#[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
|
/// Implement construction logic of an attribute set, to encapsulate
|
||||||
/// logic about attribute set optimisations inside of this module.
|
/// logic about attribute set optimisations inside of this module.
|
||||||
pub fn construct(count: usize, mut stack_slice: Vec<Value>) -> EvalResult<Self> {
|
pub fn construct(count: usize, mut stack_slice: Vec<Value>) -> EvalResult<Self> {
|
||||||
|
|
|
@ -158,6 +158,21 @@ impl VM {
|
||||||
self.push(Value::Attrs(Rc::new(lhs.update(&rhs))))
|
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) => {
|
OpCode::OpList(count) => {
|
||||||
let list =
|
let list =
|
||||||
NixList::construct(count, self.stack.split_off(self.stack.len() - count));
|
NixList::construct(count, self.stack.split_off(self.stack.len() - count));
|
||||||
|
|
Loading…
Reference in a new issue