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:
Vincent Ambo 2022-08-11 17:06:23 +03:00 committed by tazjin
parent e8c4e26b41
commit cf3e3b784b
3 changed files with 50 additions and 7 deletions

View file

@ -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;

View file

@ -47,6 +47,7 @@ pub enum OpCode {
OpAttrPath(usize),
OpAttrsUpdate,
OpAttrsSelect,
OpAttrsIsSet,
// Lists
OpList(usize),

View file

@ -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));