feat(tvix/eval): implement ?
operator (single-level only)
This makes it possible to check things like `{} ? a` with a single level of nesting. Change-Id: I567c36fcfd2f9e2f60071acd3ebfe56dea59b26f Reviewed-on: https://cl.tvl.fyi/c/depot/+/6161 Tested-by: BuildkiteCI Reviewed-by: grfn <grfn@gws.fyi> Reviewed-by: sterni <sternenseemann@systemli.org>
This commit is contained in:
parent
e8c4e26b41
commit
cf3e3b784b
3 changed files with 50 additions and 7 deletions
|
@ -163,14 +163,15 @@ impl Compiler {
|
|||
}
|
||||
|
||||
fn compile_binop(&mut self, op: rnix::types::BinOp) -> EvalResult<()> {
|
||||
// Short-circuiting logical operators, which are under the
|
||||
// same node type as NODE_BIN_OP, but need to be handled
|
||||
// separately (i.e. before compiling the expressions used for
|
||||
// standard binary operators).
|
||||
// Short-circuiting and other strange operators, which are
|
||||
// under the same node type as NODE_BIN_OP, but need to be
|
||||
// handled separately (i.e. before compiling the expressions
|
||||
// used for standard binary operators).
|
||||
match op.operator().unwrap() {
|
||||
BinOpKind::And => return self.compile_and(op),
|
||||
BinOpKind::Or => return self.compile_or(op),
|
||||
BinOpKind::Implication => return self.compile_implication(op),
|
||||
BinOpKind::IsSet => return self.compile_is_set(op),
|
||||
|
||||
_ => {}
|
||||
};
|
||||
|
@ -196,10 +197,10 @@ impl Compiler {
|
|||
self.chunk.add_op(OpCode::OpInvert)
|
||||
}
|
||||
|
||||
BinOpKind::IsSet => todo!("? operator"),
|
||||
|
||||
// Handled by separate branch above.
|
||||
BinOpKind::And | BinOpKind::Implication | BinOpKind::Or => unreachable!(),
|
||||
BinOpKind::And | BinOpKind::Implication | BinOpKind::Or | BinOpKind::IsSet => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
@ -432,6 +433,40 @@ impl Compiler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_is_set(&mut self, node: rnix::types::BinOp) -> EvalResult<()> {
|
||||
debug_assert!(
|
||||
matches!(node.operator(), Some(BinOpKind::IsSet)),
|
||||
"compile_is_set called with wrong operator kind: {:?}",
|
||||
node.operator(),
|
||||
);
|
||||
|
||||
// Put the attribute set on the stack.
|
||||
self.compile(node.lhs().unwrap())?;
|
||||
|
||||
// If the key is a NODE_SELECT, the check is deeper than one
|
||||
// level and requires special handling.
|
||||
//
|
||||
// Otherwise, the right hand side is the (only) key expression
|
||||
// itself and can be compiled directly.
|
||||
let rhs = node.rhs().unwrap();
|
||||
|
||||
if matches!(rhs.kind(), rnix::SyntaxKind::NODE_SELECT) {
|
||||
// Keep nesting deeper until we encounter something
|
||||
// different than `NODE_SELECT` on the left side. This is
|
||||
// required because `rnix` parses nested keys as select
|
||||
// expressions, instead of as a key expression.
|
||||
//
|
||||
// The parsed tree will nest something like `a.b.c.d.e.f`
|
||||
// as (((((a, b), c), d), e), f).
|
||||
todo!("nested '?' check")
|
||||
} else {
|
||||
self.compile_with_literal_ident(rhs)?;
|
||||
}
|
||||
|
||||
self.chunk.add_op(OpCode::OpAttrsIsSet);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn patch_jump(&mut self, idx: CodeIdx) {
|
||||
let offset = self.chunk.code.len() - 1 - idx.0;
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ pub enum OpCode {
|
|||
OpAttrPath(usize),
|
||||
OpAttrsUpdate,
|
||||
OpAttrsSelect,
|
||||
OpAttrsIsSet,
|
||||
|
||||
// Lists
|
||||
OpList(usize),
|
||||
|
|
|
@ -173,6 +173,13 @@ impl VM {
|
|||
}
|
||||
}
|
||||
|
||||
OpCode::OpAttrsIsSet => {
|
||||
let key = self.pop().as_string()?;
|
||||
let attrs = self.pop().as_attrs()?;
|
||||
let result = Value::Bool(attrs.select(key.as_str()).is_some());
|
||||
self.push(result);
|
||||
}
|
||||
|
||||
OpCode::OpList(count) => {
|
||||
let list =
|
||||
NixList::construct(count, self.stack.split_off(self.stack.len() - count));
|
||||
|
|
Loading…
Reference in a new issue