feat(rvm): implement Azure Policy condition evaluation (#661)

Add VM support for Azure Policy's condition operators and allOf/anyOf
short-circuit logic, gated behind cfg(feature = "azure_policy").

Policy conditions (equals, contains, like, match, exists, and their
negations — 21 total) are encoded as a single PolicyCondition
instruction with a PolicyOp sub-opcode rather than bloating the
Instruction enum with 21 variants. The dispatch handles Azure Policy's
quirky comparison semantics: case-insensitive string comparison,
string↔number coercion, null vs undefined distinction, and element-wise
collection membership.

allOf/anyOf blocks use four instructions — LogicalBlockStart,
AllOfNext/AnyOfNext, and LogicalBlockEnd — that wire up a result
register and short-circuit on the first failing (allOf) or passing
(anyOf) child.

Helper functions for case-folded comparison, wildcard/glob matching, and
type coercion live in builtins::azure_policy::helpers.

Two YAML test suites (~2200 lines) exercise the full operator matrix and
the allOf/anyOf control flow.
This commit is contained in:
Anand Krishnamoorthi
2026-04-07 19:04:24 -05:00
committed by GitHub
parent 83ce8c3580
commit 4d35744c4f
10 changed files with 3165 additions and 3 deletions
+46 -1
View File
@@ -5,7 +5,7 @@ use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use super::types::GuardMode;
use super::types::{GuardMode, LogicalBlockMode, PolicyOp};
use super::{Instruction, InstructionData, LiteralOrRegister};
impl Instruction {
@@ -291,6 +291,51 @@ impl core::fmt::Display for Instruction {
|k| format!("COMPREHENSION_YIELD R({}) R({})", k, value_reg),
),
Instruction::ComprehensionEnd {} => String::from("COMPREHENSION_END"),
// Azure Policy consolidated instruction
Instruction::PolicyCondition {
dest,
left,
right,
op,
} => match op {
PolicyOp::Not => format!("{} R({}) R({})", op.display_name(), dest, left),
_ => format!("{} R({}) R({}) R({})", op.display_name(), dest, left, right),
},
// AllOf / AnyOf structured instructions
Instruction::LogicalBlockStart {
mode,
result,
end_pc,
} => {
let name = match mode {
LogicalBlockMode::AllOf => "ALL_OF_START",
LogicalBlockMode::AnyOf => "ANY_OF_START",
};
format!("{} R({}) {}", name, result, end_pc)
}
Instruction::AllOfNext {
check,
result,
end_pc,
} => {
format!("ALL_OF_NEXT R({}) R({}) {}", check, result, end_pc)
}
Instruction::AnyOfNext {
check,
result,
end_pc,
} => {
format!("ANY_OF_NEXT R({}) R({}) {}", check, result, end_pc)
}
Instruction::LogicalBlockEnd { mode, result } => {
let name = match mode {
LogicalBlockMode::AllOf => "ALL_OF_END",
LogicalBlockMode::AnyOf => "ANY_OF_END",
};
format!("{} R({})", name, result)
}
};
write!(f, "{}", text)
}
+62 -1
View File
@@ -10,7 +10,9 @@ pub use params::{
FunctionCallParams, InstructionData, LoopStartParams, ObjectCreateParams, SetCreateParams,
VirtualDataDocumentLookupParams,
};
pub use types::{ComprehensionMode, GuardMode, LiteralOrRegister, LoopMode};
pub use types::{
ComprehensionMode, GuardMode, LiteralOrRegister, LogicalBlockMode, LoopMode, PolicyOp,
};
use serde::{Deserialize, Serialize};
@@ -355,6 +357,65 @@ pub enum Instruction {
/// End a comprehension block
ComprehensionEnd {},
// ── Azure Policy condition operators (consolidated) ────────────────
/// Consolidated Azure Policy condition instruction.
///
/// Replaces 21 separate Policy* variants. The `op` discriminant selects
/// the specific Azure Policy condition semantics.
///
/// For most ops: `dest = op(left, right)`.
/// For `PolicyOp::Not`: `dest = !is_true(left)`, `right` is unused (0).
/// For `PolicyOp::ValueConditionGuard`: `left` = value register,
/// `right` = condition register.
PolicyCondition {
dest: u8,
left: u8,
right: u8,
op: PolicyOp,
},
// ── AllOf / AnyOf structured short-circuit instructions ───────────
/// Initialize allOf/anyOf: set result register to false.
LogicalBlockStart {
mode: LogicalBlockMode,
/// Register that accumulates the result.
result: u8,
/// PC of the corresponding End instruction.
end_pc: u16,
},
/// Check one allOf child: if not true, short-circuit (result stays false),
/// jump to end_pc.
AllOfNext {
/// Register holding the child condition result.
check: u8,
/// Register that accumulates the allOf result.
result: u8,
/// PC of the AllOfEnd instruction (jump target on short-circuit).
end_pc: u16,
},
/// Check one anyOf child: if true, short-circuit (set result to true),
/// jump to end_pc.
AnyOfNext {
/// Register holding the child condition result.
check: u8,
/// Register that accumulates the anyOf result.
result: u8,
/// PC of the AnyOfEnd instruction.
end_pc: u16,
},
/// Finalize allOf/anyOf block.
///
/// For AllOf: all children passed → set result to true.
/// For AnyOf: no child matched → result stays false (no-op).
LogicalBlockEnd {
mode: LogicalBlockMode,
/// Register that accumulates the result.
result: u8,
},
}
impl Instruction {
+112
View File
@@ -46,6 +46,110 @@ pub enum ComprehensionMode {
Object,
}
/// Azure Policy condition operator sub-opcodes.
///
/// Each variant maps to one of the ~21 Azure Policy condition operators.
/// Stored inside `Instruction::PolicyCondition` to collapse 21 enum variants
/// into a single instruction with a sub-op discriminant.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PolicyOp {
Equals,
NotEquals,
Greater,
GreaterOrEquals,
Less,
LessOrEquals,
In,
NotIn,
Contains,
NotContains,
ContainsKey,
NotContainsKey,
Like,
NotLike,
Match,
NotMatch,
MatchInsensitively,
NotMatchInsensitively,
Exists,
/// Guard for `value:` conditions — forces false when LHS is undefined.
/// Uses `left` = value register, `right` = condition register.
ValueConditionGuard,
/// Logical negation: `!is_true(operand)`. Uses `left` = operand, `right` is unused (0).
Not,
}
impl PolicyOp {
/// Display name used in assembly listings and Debug output.
pub const fn display_name(self) -> &'static str {
match self {
Self::Equals => "POLICY_EQUALS",
Self::NotEquals => "POLICY_NOT_EQUALS",
Self::Greater => "POLICY_GREATER",
Self::GreaterOrEquals => "POLICY_GREATER_OR_EQUALS",
Self::Less => "POLICY_LESS",
Self::LessOrEquals => "POLICY_LESS_OR_EQUALS",
Self::In => "POLICY_IN",
Self::NotIn => "POLICY_NOT_IN",
Self::Contains => "POLICY_CONTAINS",
Self::NotContains => "POLICY_NOT_CONTAINS",
Self::ContainsKey => "POLICY_CONTAINS_KEY",
Self::NotContainsKey => "POLICY_NOT_CONTAINS_KEY",
Self::Like => "POLICY_LIKE",
Self::NotLike => "POLICY_NOT_LIKE",
Self::Match => "POLICY_MATCH",
Self::NotMatch => "POLICY_NOT_MATCH",
Self::MatchInsensitively => "POLICY_MATCH_INSENSITIVELY",
Self::NotMatchInsensitively => "POLICY_NOT_MATCH_INSENSITIVELY",
Self::Exists => "POLICY_EXISTS",
Self::ValueConditionGuard => "VALUE_CONDITION_GUARD",
Self::Not => "POLICY_NOT",
}
}
/// Compact name for tabular assembly listings.
pub const fn compact_name(self) -> &'static str {
match self {
Self::Equals => "POLICY_EQ",
Self::NotEquals => "POLICY_NE",
Self::Greater => "POLICY_GT",
Self::GreaterOrEquals => "POLICY_GE",
Self::Less => "POLICY_LT",
Self::LessOrEquals => "POLICY_LE",
Self::In => "POLICY_IN",
Self::NotIn => "POLICY_NOT_IN",
Self::Contains => "POLICY_CONTAINS",
Self::NotContains => "POLICY_NOT_CONTAINS",
Self::ContainsKey => "POLICY_CONTAINS_KEY",
Self::NotContainsKey => "POLICY_NOT_CONTAINS_KEY",
Self::Like => "POLICY_LIKE",
Self::NotLike => "POLICY_NOT_LIKE",
Self::Match => "POLICY_MATCH",
Self::NotMatch => "POLICY_NOT_MATCH",
Self::MatchInsensitively => "POLICY_MATCH_CI",
Self::NotMatchInsensitively => "POLICY_NOT_MATCH_CI",
Self::Exists => "POLICY_EXISTS",
Self::ValueConditionGuard => "VAL_COND_GUARD",
Self::Not => "POLICY_NOT",
}
}
/// Returns `true` for negated condition operators (NotEquals, NotIn, etc.).
pub const fn is_negated(self) -> bool {
matches!(
self,
Self::NotEquals
| Self::NotIn
| Self::NotContains
| Self::NotContainsKey
| Self::NotLike
| Self::NotMatch
| Self::NotMatchInsensitively
)
}
}
/// Guard sub-modes for the consolidated `Guard` instruction.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -57,3 +161,11 @@ pub enum GuardMode {
/// Assert not undefined — fail (return undefined) if register is undefined.
NotUndefined,
}
/// Mode discriminant for merged AllOf/AnyOf Start and End instructions.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum LogicalBlockMode {
AllOf,
AnyOf,
}
+22
View File
@@ -911,6 +911,15 @@ fn format_instruction_readable(
let base = format!("{}}} CompEnd", indent);
align_comment(&base, "End comprehension block", config.comment_column)
}
// Azure Policy & allOf/anyOf instructions — use Display impl
instruction @ Instruction::PolicyCondition { .. }
| instruction @ Instruction::LogicalBlockStart { .. }
| instruction @ Instruction::AllOfNext { .. }
| instruction @ Instruction::AnyOfNext { .. }
| instruction @ Instruction::LogicalBlockEnd { .. } => {
format!("{}{}", indent, instruction)
}
}
}
@@ -1045,6 +1054,19 @@ const fn get_instruction_name(instruction: &Instruction) -> &'static str {
Instruction::ComprehensionBegin { .. } => "COMP_BEGIN",
Instruction::ComprehensionYield { .. } => "COMP_YIELD",
Instruction::ComprehensionEnd {} => "COMP_END",
// Azure Policy instructions
Instruction::PolicyCondition { op, .. } => op.compact_name(),
// AllOf / AnyOf
Instruction::LogicalBlockStart { mode, .. } => match mode {
crate::rvm::instructions::LogicalBlockMode::AllOf => "ALL_OF_START",
crate::rvm::instructions::LogicalBlockMode::AnyOf => "ANY_OF_START",
},
Instruction::AllOfNext { .. } => "ALL_OF_NEXT",
Instruction::AnyOfNext { .. } => "ANY_OF_NEXT",
Instruction::LogicalBlockEnd { mode, .. } => match mode {
crate::rvm::instructions::LogicalBlockMode::AllOf => "ALL_OF_END",
crate::rvm::instructions::LogicalBlockMode::AnyOf => "ANY_OF_END",
},
}
}
+116 -1
View File
@@ -9,7 +9,7 @@
clippy::pattern_type_mismatch
)] // tests unwrap conversions and slice math for brevity
use crate::rvm::instructions::{GuardMode, Instruction, LoopMode};
use crate::rvm::instructions::{GuardMode, Instruction, LogicalBlockMode, LoopMode, PolicyOp};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::{anyhow, bail, Result};
@@ -83,6 +83,41 @@ pub fn parse_instruction(text: &str) -> Result<Instruction> {
"ComprehensionYield" => parse_comprehension_add(params_text),
"ReturnUndefinedIfNotTrue" => parse_return_undefined_if_not_true(params_text),
"CoalesceUndefinedToNull" => parse_coalesce_undefined_to_null(params_text),
// Azure Policy condition operators
"PolicyEquals" => parse_policy_condition(params_text, PolicyOp::Equals),
"PolicyNotEquals" => parse_policy_condition(params_text, PolicyOp::NotEquals),
"PolicyGreater" => parse_policy_condition(params_text, PolicyOp::Greater),
"PolicyGreaterOrEquals" => {
parse_policy_condition(params_text, PolicyOp::GreaterOrEquals)
}
"PolicyLess" => parse_policy_condition(params_text, PolicyOp::Less),
"PolicyLessOrEquals" => parse_policy_condition(params_text, PolicyOp::LessOrEquals),
"PolicyIn" => parse_policy_condition(params_text, PolicyOp::In),
"PolicyNotIn" => parse_policy_condition(params_text, PolicyOp::NotIn),
"PolicyContains" => parse_policy_condition(params_text, PolicyOp::Contains),
"PolicyNotContains" => parse_policy_condition(params_text, PolicyOp::NotContains),
"PolicyContainsKey" => parse_policy_condition(params_text, PolicyOp::ContainsKey),
"PolicyNotContainsKey" => parse_policy_condition(params_text, PolicyOp::NotContainsKey),
"PolicyLike" => parse_policy_condition(params_text, PolicyOp::Like),
"PolicyNotLike" => parse_policy_condition(params_text, PolicyOp::NotLike),
"PolicyMatch" => parse_policy_condition(params_text, PolicyOp::Match),
"PolicyNotMatch" => parse_policy_condition(params_text, PolicyOp::NotMatch),
"PolicyMatchInsensitively" => {
parse_policy_condition(params_text, PolicyOp::MatchInsensitively)
}
"PolicyNotMatchInsensitively" => {
parse_policy_condition(params_text, PolicyOp::NotMatchInsensitively)
}
"PolicyExists" => parse_policy_condition(params_text, PolicyOp::Exists),
"ValueConditionGuard" => parse_value_condition_guard(params_text),
"PolicyNot" => parse_policy_not(params_text),
// AllOf / AnyOf
"AllOfStart" => parse_logical_block_start(params_text, LogicalBlockMode::AllOf),
"AllOfNext" => parse_allof_next(params_text),
"AllOfEnd" => parse_logical_block_end(params_text, LogicalBlockMode::AllOf),
"AnyOfStart" => parse_logical_block_start(params_text, LogicalBlockMode::AnyOf),
"AnyOfNext" => parse_anyof_next(params_text),
"AnyOfEnd" => parse_logical_block_end(params_text, LogicalBlockMode::AnyOf),
_ => bail!("Unknown instruction: {}", name),
}
} else {
@@ -707,3 +742,83 @@ fn parse_coalesce_undefined_to_null(params_text: &str) -> Result<Instruction> {
register: register.try_into().unwrap(),
})
}
/// Generic parser for PolicyCondition instructions with { dest, left, right } fields.
fn parse_policy_condition(params_text: &str, op: PolicyOp) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest: u8 = get_param_u16(&params, "dest")?.try_into().unwrap();
let left: u8 = get_param_u16(&params, "left")?.try_into().unwrap();
let right: u8 = get_param_u16(&params, "right")?.try_into().unwrap();
Ok(Instruction::PolicyCondition {
dest,
left,
right,
op,
})
}
fn parse_value_condition_guard(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest: u8 = get_param_u16(&params, "dest")?.try_into().unwrap();
let value: u8 = get_param_u16(&params, "value")?.try_into().unwrap();
let condition: u8 = get_param_u16(&params, "condition")?.try_into().unwrap();
Ok(Instruction::PolicyCondition {
dest,
left: value,
right: condition,
op: PolicyOp::ValueConditionGuard,
})
}
fn parse_policy_not(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let dest: u8 = get_param_u16(&params, "dest")?.try_into().unwrap();
let operand: u8 = get_param_u16(&params, "operand")?.try_into().unwrap();
Ok(Instruction::PolicyCondition {
dest,
left: operand,
right: 0,
op: PolicyOp::Not,
})
}
fn parse_logical_block_start(params_text: &str, mode: LogicalBlockMode) -> Result<Instruction> {
let params = parse_params(params_text)?;
let result: u8 = get_param_u16(&params, "result")?.try_into().unwrap();
let end_pc = get_param_u16(&params, "end_pc")?;
Ok(Instruction::LogicalBlockStart {
mode,
result,
end_pc,
})
}
fn parse_allof_next(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let check: u8 = get_param_u16(&params, "check")?.try_into().unwrap();
let result: u8 = get_param_u16(&params, "result")?.try_into().unwrap();
let end_pc = get_param_u16(&params, "end_pc")?;
Ok(Instruction::AllOfNext {
check,
result,
end_pc,
})
}
fn parse_logical_block_end(params_text: &str, mode: LogicalBlockMode) -> Result<Instruction> {
let params = parse_params(params_text)?;
let result: u8 = get_param_u16(&params, "result")?.try_into().unwrap();
Ok(Instruction::LogicalBlockEnd { mode, result })
}
fn parse_anyof_next(params_text: &str) -> Result<Instruction> {
let params = parse_params(params_text)?;
let check: u8 = get_param_u16(&params, "check")?.try_into().unwrap();
let result: u8 = get_param_u16(&params, "result")?.try_into().unwrap();
let end_pc = get_param_u16(&params, "end_pc")?;
Ok(Instruction::AnyOfNext {
check,
result,
end_pc,
})
}
+6
View File
@@ -1059,4 +1059,10 @@ mod tests {
fn run_loop_test_file(file: &str) {
run_vm_test_suite(file).unwrap()
}
#[cfg(feature = "azure_policy")]
#[test_resources("tests/rvm/vm/suites/azure_policy/*.yaml")]
fn run_azure_policy_test_file(file: &str) {
run_vm_test_suite(file).unwrap()
}
}
+291
View File
@@ -807,6 +807,297 @@ impl RegoVM {
let result = self.get_register(0)?.clone();
Ok(InstructionOutcome::Return(result))
}
other => self.execute_policy_instruction(program, other),
}
}
#[cfg(not(feature = "azure_policy"))]
fn execute_policy_instruction(
&mut self,
program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
match instruction {
instruction @ (Instruction::PolicyCondition { .. }
| Instruction::LogicalBlockStart { .. }
| Instruction::LogicalBlockEnd { .. }
| Instruction::AllOfNext { .. }
| Instruction::AnyOfNext { .. }) => Err(VmError::UnhandledInstruction {
instruction: alloc::format!("{:?} requires the azure_policy feature", instruction),
pc: self.pc,
}),
other => self.execute_virtual_instruction(program, other),
}
}
/// Check whether `l` "contains" `r` using Azure Policy semantics.
///
/// Works on strings (case-insensitive substring), arrays/sets (element
/// membership), and objects (key membership). For string haystacks,
/// non-string scalar RHS values are coerced to strings before the
/// substring check. For non-string scalar LHS values, coercion to string
/// only happens when the RHS is already a string.
#[cfg(feature = "azure_policy")]
#[inline]
fn policy_contains_check(l: &Value, r: &Value) -> bool {
use crate::builtins::azure_policy::helpers::{case_insensitive_equals, coerce_to_string};
use crate::languages::azure_policy::strings;
match *l {
Value::String(ref haystack) => match *r {
Value::String(ref needle) => strings::case_fold::contains(haystack, needle),
_ => coerce_to_string(r)
.is_some_and(|needle| strings::case_fold::contains(haystack, &needle)),
},
Value::Array(ref items) => items.iter().any(|item| case_insensitive_equals(item, r)),
Value::Set(ref items) => items.iter().any(|item| case_insensitive_equals(item, r)),
// ARM template contains(object, key) checks key membership.
Value::Object(ref map) => map.keys().any(|key| case_insensitive_equals(key, r)),
// Coerce non-string scalar LHS (e.g., count result)
// to a string only when the RHS is already a string.
_ => {
if let Value::String(ref needle) = *r {
coerce_to_string(l)
.is_some_and(|haystack| strings::case_fold::contains(&haystack, needle))
} else {
false
}
}
}
}
/// Evaluate a Policy comparison operator. Undefined LHS → false.
#[cfg(feature = "azure_policy")]
fn policy_compare(
&mut self,
dest: u8,
left: u8,
right: u8,
cmp: fn(i8) -> bool,
) -> Result<InstructionOutcome> {
use crate::builtins::azure_policy::helpers::{compare_values, is_undefined};
let l = self.get_register(left)?;
if is_undefined(l) {
self.set_register(dest, Value::Bool(false))?;
} else {
let r = self.get_register(right)?;
let result = compare_values(l, r).is_some_and(cmp);
self.set_register(dest, Value::Bool(result))?;
}
Ok(InstructionOutcome::Continue)
}
#[cfg(feature = "azure_policy")]
fn execute_policy_instruction(
&mut self,
program: &Program,
instruction: Instruction,
) -> Result<InstructionOutcome> {
use crate::builtins::azure_policy::helpers::{
as_boolish, case_insensitive_equals, coerce_to_string_ci,
collection_any_ci_eq_excluding_null, collection_has_null, is_true, is_undefined,
match_like_pattern_ci, match_pattern,
};
use crate::rvm::instructions::{LogicalBlockMode, PolicyOp};
use Instruction::*;
match instruction {
PolicyCondition {
dest,
left,
right,
op,
} => {
let l = self.get_register(left)?;
let result = match op {
PolicyOp::Equals => {
let r = self.get_register(right)?;
if is_undefined(l) {
matches!(r, Value::Null)
} else {
case_insensitive_equals(l, r)
}
}
PolicyOp::NotEquals => {
let r = self.get_register(right)?;
if is_undefined(l) {
!matches!(r, Value::Null)
} else {
!case_insensitive_equals(l, r)
}
}
PolicyOp::Greater => {
return self.policy_compare(dest, left, right, |c| c > 0);
}
PolicyOp::GreaterOrEquals => {
return self.policy_compare(dest, left, right, |c| c >= 0);
}
PolicyOp::Less => {
return self.policy_compare(dest, left, right, |c| c < 0);
}
PolicyOp::LessOrEquals => {
return self.policy_compare(dest, left, right, |c| c <= 0);
}
PolicyOp::In => {
let r = self.get_register(right)?;
if is_undefined(l) {
collection_has_null(r)
} else if matches!(*l, Value::Null) || is_undefined(r) {
false
} else {
collection_any_ci_eq_excluding_null(r, l)
}
}
PolicyOp::NotIn => {
let r = self.get_register(right)?;
if is_undefined(l) {
!collection_has_null(r)
} else if matches!(*l, Value::Null) || is_undefined(r) {
true
} else {
!collection_any_ci_eq_excluding_null(r, l)
}
}
PolicyOp::Contains | PolicyOp::NotContains => {
let negated = op.is_negated();
if is_undefined(l) {
negated
} else {
let r = self.get_register(right)?;
if is_undefined(r) {
// undefined RHS: positive → false, negated → false
false
} else {
negated ^ Self::policy_contains_check(l, r)
}
}
}
PolicyOp::ContainsKey | PolicyOp::NotContainsKey => {
let negated = op.is_negated();
if is_undefined(l) {
negated
} else {
let r = self.get_register(right)?;
if is_undefined(r) {
false
} else {
let found = match *l {
Value::Object(ref map) => {
map.keys().any(|key| case_insensitive_equals(key, r))
}
_ => false,
};
negated ^ found
}
}
}
PolicyOp::Like | PolicyOp::NotLike => {
let negated = op.is_negated();
if is_undefined(l) {
negated
} else {
let r = self.get_register(right)?;
let positive = match (coerce_to_string_ci(l), coerce_to_string_ci(r)) {
(Some(input), Some(pattern)) => {
match_like_pattern_ci(&input, &pattern)
}
_ => false,
};
negated ^ positive
}
}
PolicyOp::Match
| PolicyOp::NotMatch
| PolicyOp::MatchInsensitively
| PolicyOp::NotMatchInsensitively => {
let negated = op.is_negated();
let case_insensitive = matches!(
op,
PolicyOp::MatchInsensitively | PolicyOp::NotMatchInsensitively
);
if is_undefined(l) {
negated
} else {
let r = self.get_register(right)?;
negated ^ match_pattern(l, r, case_insensitive)
}
}
PolicyOp::Exists => {
let r = self.get_register(right)?;
let expected = as_boolish(r).unwrap_or(false);
let is_defined = !is_undefined(l) && !matches!(l, Value::Null);
is_defined == expected
}
PolicyOp::ValueConditionGuard => {
// left = value register, right = condition register
if is_undefined(l) {
self.set_register(dest, Value::Bool(false))?;
return Ok(InstructionOutcome::Continue);
} else {
let c = self.get_register(right)?.clone();
self.set_register(dest, c)?;
return Ok(InstructionOutcome::Continue);
}
}
PolicyOp::Not => {
// left = operand, right unused
!is_true(l)
}
};
self.set_register(dest, Value::Bool(result))?;
Ok(InstructionOutcome::Continue)
}
// AllOf / AnyOf structured instructions
LogicalBlockStart {
mode: _,
result,
end_pc: _,
} => {
// Initialize result to false (pessimistic).
self.set_register(result, Value::Bool(false))?;
Ok(InstructionOutcome::Continue)
}
AllOfNext {
check,
result,
end_pc,
} => {
let val = self.get_register(check)?;
if !matches!(val, Value::Bool(true)) {
// Child failed — short-circuit. Ensure the block result is false.
self.set_register(result, Value::Bool(false))?;
self.pc = usize::from(end_pc);
}
Ok(InstructionOutcome::Continue)
}
AnyOfNext {
check,
result,
end_pc,
} => {
let val = self.get_register(check)?;
if matches!(val, Value::Bool(true)) {
// Child succeeded — short-circuit.
self.set_register(result, Value::Bool(true))?;
self.pc = usize::from(end_pc);
}
Ok(InstructionOutcome::Continue)
}
LogicalBlockEnd { mode, result } => {
match mode {
LogicalBlockMode::AllOf => {
// All children passed — set result to true.
self.set_register(result, Value::Bool(true))?;
}
LogicalBlockMode::AnyOf => {
// No child matched — result stays false (set by LogicalBlockStart).
}
}
Ok(InstructionOutcome::Continue)
}
other => self.execute_virtual_instruction(program, other),
}
}