Files
regorus/src/rvm/program/listing.rs
Anand Krishnamoorthi 4d35744c4f 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.
2026-04-07 19:04:24 -05:00

1161 lines
47 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
use core::fmt::{self, Write as _};
use crate::rvm::{
instructions::{Instruction, InstructionData, LoopMode},
program::Program,
};
// Writing into a String via fmt never fails, so we intentionally ignore writeln! results.
fn push_line(buf: &mut String, args: fmt::Arguments) {
let _ = buf.write_fmt(args);
let _ = buf.write_char('\n');
}
/// Configuration for assembly listing output
#[derive(Debug, Clone)]
pub struct AssemblyListingConfig {
/// Show instruction addresses
pub show_addresses: bool,
/// Show raw instruction bytes (if available)
pub show_bytes: bool,
/// Indent size for nested loops
pub indent_size: usize,
/// Maximum width for instruction column
pub instruction_width: usize,
/// Show literal values inline
pub show_literal_values: bool,
/// Column position for comments
pub comment_column: usize,
}
impl Default for AssemblyListingConfig {
fn default() -> Self {
Self {
show_addresses: true,
show_bytes: false,
indent_size: 4,
instruction_width: 40,
show_literal_values: true,
comment_column: 50,
}
}
}
/// Generate annotated assembly listing for a compiled program
pub fn generate_assembly_listing(program: &Program, config: &AssemblyListingConfig) -> String {
let mut output = String::new();
let mut indent_level: usize = 0;
let mut current_rule_index: Option<u16> = None;
// Track active loops and comprehensions by their end addresses
let mut active_ends: Vec<u16> = Vec::new();
// Add header
push_line(
&mut output,
format_args!(
"; RVM Assembly - {} instructions, {} literals, {} builtins",
program.instructions.len(),
program.literals.len(),
program.builtin_info_table.len()
),
);
// Add builtins table
if !program.builtin_info_table.is_empty() {
push_line(&mut output, format_args!(";"));
push_line(&mut output, format_args!("; BUILTINS TABLE:"));
for (idx, builtin_info) in program.builtin_info_table.iter().enumerate() {
push_line(
&mut output,
format_args!("; B{:2}: {}", idx, builtin_info.name),
);
}
}
// Add literals table
if config.show_literal_values && !program.literals.is_empty() {
push_line(&mut output, format_args!(";"));
push_line(&mut output, format_args!("; LITERALS (JSON values):"));
for (idx, literal) in program.literals.iter().enumerate() {
let literal_json =
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
push_line(
&mut output,
format_args!("; L{:2}: {}", idx, literal_json),
);
}
}
// Add rules table if available
if !program.rule_infos.is_empty() {
push_line(&mut output, format_args!(";"));
push_line(&mut output, format_args!("; RULES TABLE:"));
for (idx, rule_info) in program.rule_infos.iter().enumerate() {
push_line(
&mut output,
format_args!("; R{:2}: {}", idx, rule_info.name),
);
}
}
// Add metadata section
{
push_line(&mut output, format_args!(";"));
push_line(&mut output, format_args!("; METADATA:"));
push_line(
&mut output,
format_args!(
"; compiler_version: {}",
program.metadata.compiler_version
),
);
push_line(
&mut output,
format_args!("; compiled_at: {}", program.metadata.compiled_at),
);
if !program.metadata.source_info.is_empty() {
push_line(
&mut output,
format_args!("; source_info: {}", program.metadata.source_info),
);
}
push_line(
&mut output,
format_args!(
"; optimization_level: {}",
program.metadata.optimization_level
),
);
if !program.metadata.language.is_empty() {
push_line(
&mut output,
format_args!("; language: {}", program.metadata.language),
);
}
if !program.metadata.annotations.is_empty() {
push_line(&mut output, format_args!("; annotations:"));
for (key, value) in &program.metadata.annotations {
let json = serde_json::to_string(value).unwrap_or_else(|_| "<invalid>".to_string());
push_line(&mut output, format_args!("; {}: {}", key, json));
}
}
}
push_line(&mut output, format_args!(";"));
for (pc, instruction) in program.instructions.iter().enumerate() {
// Handle rule transitions and add gaps
if let &Instruction::RuleInit { rule_index, .. } = instruction {
// Add gap before new rule (except for the first rule)
if current_rule_index.is_some() {
push_line(&mut output, format_args!(""));
}
current_rule_index = Some(rule_index);
// Add rule name prefix
if let Some(rule_info) = program.rule_infos.get(usize::from(rule_index)) {
push_line(
&mut output,
format_args!("; ===== RULE: {} =====", rule_info.name),
);
} else {
push_line(
&mut output,
format_args!("; ===== RULE: rule_{} =====", rule_index),
);
}
}
// Check if current PC matches any active end addresses (loops, comprehensions, rules)
let current_pc = u16::try_from(pc).unwrap_or(u16::MAX);
while let Some(&end_addr) = active_ends.last() {
if current_pc >= end_addr {
active_ends.pop();
indent_level = indent_level.saturating_sub(1);
} else {
break;
}
}
// Handle explicit end instructions
match *instruction {
Instruction::LoopNext { .. } => {
// LoopNext already handled by end address tracking above
}
Instruction::RuleReturn { .. } => {
indent_level = indent_level.saturating_sub(1);
}
_ => {}
}
// Special case: Block end instructions should be indented at their block level (one level out)
let effective_indent_level = match *instruction {
Instruction::ComprehensionEnd {} => indent_level.saturating_sub(1),
Instruction::LoopNext { .. } => indent_level.saturating_sub(1),
_ => indent_level,
};
let indent = " ".repeat(effective_indent_level.saturating_mul(config.indent_size));
// Format address
let addr_str = if config.show_addresses {
format!("{:03}: ", pc)
} else {
String::new()
};
// Format instruction with proper indentation and aligned comments
let inst_str = format_instruction_readable(
instruction,
&indent,
&program.instruction_data,
program,
config,
);
push_line(&mut output, format_args!("{}{}", addr_str, inst_str));
// Increase indentation for loop/rule/comprehension starts and track their end addresses
match *instruction {
Instruction::LoopStart { params_index } => {
if let Some(params) = program.instruction_data.get_loop_params(params_index) {
active_ends.push(params.loop_end);
indent_level = indent_level.saturating_add(1);
}
}
Instruction::ComprehensionBegin { params_index } => {
if let Some(params) = program
.instruction_data
.get_comprehension_begin_params(params_index)
{
active_ends.push(params.comprehension_end);
indent_level = indent_level.saturating_add(1);
}
}
Instruction::RuleInit { .. } => {
indent_level = indent_level.saturating_add(1);
// Note: Rules end with RuleReturn, not an address, so we don't track them here
}
_ => {}
}
}
output
}
/// Helper function to align comments at a specific column
fn align_comment(base_text: &str, comment: &str, target_column: usize) -> String {
let current_len = base_text.len();
if current_len >= target_column {
format!("{} ; {}", base_text, comment)
} else {
let padding = " ".repeat(target_column.saturating_sub(current_len));
format!("{}{} ; {}", base_text, padding, comment)
}
}
/// Format a single instruction with proper indentation and mathematical notation
fn format_instruction_readable(
instruction: &Instruction,
indent: &str,
instruction_data: &InstructionData,
program: &Program,
config: &AssemblyListingConfig,
) -> String {
match *instruction {
Instruction::Load { dest, literal_idx } => {
let base = format!("{}Load r{} ← L{}", indent, dest, literal_idx);
let comment = program.literals.get(usize::from(literal_idx)).map_or_else(
|| "Load literal: <invalid index>".to_string(),
|literal| {
let literal_json =
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
format!("Load literal: {}", literal_json)
},
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::LoadTrue { dest } => {
let base = format!("{}LoadTrue r{} ← true", indent, dest);
align_comment(&base, "Load boolean constant true", config.comment_column)
}
Instruction::LoadFalse { dest } => {
let base = format!("{}LoadFalse r{} ← false", indent, dest);
align_comment(&base, "Load boolean constant false", config.comment_column)
}
Instruction::LoadNull { dest } => {
let base = format!("{}LoadNull r{} ← null", indent, dest);
align_comment(&base, "Load null value", config.comment_column)
}
Instruction::LoadBool { dest, value } => {
let base = format!("{}LoadBool r{}{}", indent, dest, value);
let comment = format!("Load boolean constant {}", value);
align_comment(&base, &comment, config.comment_column)
}
Instruction::LoadData { dest } => {
let base = format!("{}LoadData r{} ← data", indent, dest);
align_comment(&base, "Load global data document", config.comment_column)
}
Instruction::LoadInput { dest } => {
let base = format!("{}LoadInput r{} ← input", indent, dest);
align_comment(&base, "Load global input document", config.comment_column)
}
Instruction::LoadContext { dest } => {
let base = format!("{}LoadContext r{} ← context", indent, dest);
align_comment(&base, "Load evaluation context", config.comment_column)
}
Instruction::LoadMetadata { dest } => {
let base = format!("{}LoadMetadata r{} ← metadata", indent, dest);
align_comment(&base, "Load program metadata", config.comment_column)
}
Instruction::Move { dest, src } => {
let base = format!("{}Move r{} ← r{}", indent, dest, src);
let comment = format!("Copy value from r{} to r{}", src, dest);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Add { dest, left, right } => {
let base = format!("{}Add r{} ← r{} + r{}", indent, dest, left, right);
let comment = format!("Arithmetic addition: r{} + r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Sub { dest, left, right } => {
let base = format!("{}Sub r{} ← r{} - r{}", indent, dest, left, right);
let comment = format!("Arithmetic subtraction: r{} - r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Mul { dest, left, right } => {
let base = format!("{}Mul r{} ← r{} × r{}", indent, dest, left, right);
let comment = format!("Arithmetic multiplication: r{} × r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Div { dest, left, right } => {
let base = format!("{}Div r{} ← r{} ÷ r{}", indent, dest, left, right);
let comment = format!("Arithmetic division: r{} ÷ r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Mod { dest, left, right } => {
let base = format!(
"{}Mod r{} ← r{} mod r{}",
indent, dest, left, right
);
let comment = format!("Modulo operation: r{} mod r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Eq { dest, left, right } => {
let base = format!(
"{}Eq r{} ← (r{} = r{})",
indent, dest, left, right
);
let comment = format!("Equality test: r{} == r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Ne { dest, left, right } => {
let base = format!(
"{}Ne r{} ← (r{} ≠ r{})",
indent, dest, left, right
);
let comment = format!("Inequality test: r{} != r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Lt { dest, left, right } => {
let base = format!(
"{}Lt r{} ← (r{} < r{})",
indent, dest, left, right
);
let comment = format!("Less than comparison: r{} < r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Le { dest, left, right } => {
let base = format!(
"{}Le r{} ← (r{} ≤ r{})",
indent, dest, left, right
);
let comment = format!("Less or equal comparison: r{} <= r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Gt { dest, left, right } => {
let base = format!(
"{}Gt r{} ← (r{} > r{})",
indent, dest, left, right
);
let comment = format!("Greater than comparison: r{} > r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Ge { dest, left, right } => {
let base = format!(
"{}Ge r{} ← (r{} ≥ r{})",
indent, dest, left, right
);
let comment = format!("Greater or equal comparison: r{} >= r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::And { dest, left, right } => {
let base = format!("{}And r{} ← r{} ∧ r{}", indent, dest, left, right);
let comment = format!("Logical AND: r{} && r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Or { dest, left, right } => {
let base = format!("{}Or r{} ← r{} r{}", indent, dest, left, right);
let comment = format!("Logical OR: r{} || r{}", left, right);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Not { dest, operand } => {
let base = format!("{}Not r{} ← ¬r{}", indent, dest, operand);
let comment = format!("Logical NOT: !r{}", operand);
align_comment(&base, &comment, config.comment_column)
}
Instruction::BuiltinCall { params_index } => instruction_data
.get_builtin_call_params(params_index)
.map_or_else(
|| {
let base = format!("{}BuiltinCall [INVALID P({})]", indent, params_index);
align_comment(
&base,
"ERROR: Invalid builtin call parameters",
config.comment_column,
)
},
|params| {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let builtin_name = program
.builtin_info_table
.get(usize::from(params.builtin_index))
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
let base = format!(
"{}BuiltinCall r{}{}({})",
indent, params.dest, builtin_name, args_str
);
let comment = format!(
"Call builtin '{}' (B{}) with {} args",
builtin_name, params.builtin_index, params.num_args
);
align_comment(&base, &comment, config.comment_column)
},
),
Instruction::FunctionCall { params_index } => instruction_data
.get_function_call_params(params_index)
.map_or_else(
|| {
let base = format!("{}FunctionCall [INVALID P({})]", indent, params_index);
align_comment(
&base,
"ERROR: Invalid function call parameters",
config.comment_column,
)
},
|params| {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let func_name = program
.rule_infos
.get(usize::from(params.func_rule_index))
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
let base = format!(
"{}FunctionCall r{}{}({})",
indent, params.dest, func_name, args_str
);
let comment = format!(
"Call function '{}' (R{}) with {} args",
func_name, params.func_rule_index, params.num_args
);
align_comment(&base, &comment, config.comment_column)
},
),
Instruction::HostAwait { dest, arg, id } => {
let base = format!(
"{}HostAwait r{} ← await r{} (id r{})",
indent, dest, arg, id
);
align_comment(
&base,
&format!(
"Suspend and request host result using r{} with identifier r{}",
arg, id
),
config.comment_column,
)
}
Instruction::Return { value } => {
let base = format!("{}Return return r{}", indent, value);
let comment = format!("Return value from r{}", value);
align_comment(&base, &comment, config.comment_column)
}
Instruction::ObjectSet { obj, key, value } => {
let base = format!("{}ObjectSet r{}[r{}] ← r{}", indent, obj, key, value);
let comment = format!("Set field r{}[r{}] = r{}", obj, key, value);
align_comment(&base, &comment, config.comment_column)
}
Instruction::ObjectCreate { params_index } => {
let params = program
.instruction_data
.get_object_create_params(params_index);
let base = format!(
"{}ObjectCreate r{}{{...}}",
indent,
params.map_or(0, |p| p.dest)
);
let comment = params.map_or_else(
|| format!("Create object (P{} - INVALID)", params_index),
|p| {
format!(
"Create object with {} fields (P{})",
p.field_count(),
params_index,
)
},
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Index {
dest,
container,
key,
} => {
let base = format!(
"{}Index r{} ← r{}[r{}]",
indent, dest, container, key
);
let comment = format!("Index operation: get r{}[r{}]", container, key);
align_comment(&base, &comment, config.comment_column)
}
Instruction::IndexLiteral {
dest,
container,
literal_idx,
} => {
let base = format!(
"{}IndexLiteral r{} ← r{}[L{}]",
indent, dest, container, literal_idx
);
let comment = program.literals.get(usize::from(literal_idx)).map_or_else(
|| {
format!(
"Index with literal: r{}[L{}] (invalid index)",
container, literal_idx,
)
},
|literal| {
let literal_json =
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
format!("Index with literal key: r{}[{}]", container, literal_json)
},
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::ArrayNew { dest } => {
let base = format!("{}ArrayNew r{} ← []", indent, dest);
align_comment(&base, "Create new empty array", config.comment_column)
}
Instruction::ArrayPush { arr, value } => {
let base = format!("{}ArrayPush r{}.push(r{})", indent, arr, value);
let comment = format!("Append r{} to array r{}", value, arr);
align_comment(&base, &comment, config.comment_column)
}
Instruction::ArrayPushDefined { arr, value } => {
let base = format!("{}ArrayPushDef r{}.push(r{})", indent, arr, value);
let comment = format!("Append r{} to array r{} (skip if undefined)", value, arr);
align_comment(&base, &comment, config.comment_column)
}
Instruction::ArrayCreate { params_index } => instruction_data
.get_array_create_params(params_index)
.map_or_else(
|| format!("{}ArrayCreate <invalid params P{}>", indent, params_index),
|params| {
let elements = params
.element_registers()
.iter()
.map(|r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let base = format!("{}ArrayCreate r{} ← [{}]", indent, params.dest, elements);
let comment = format!(
"Create array from {} elements (undefined if any element is undefined)",
params.element_count()
);
align_comment(&base, &comment, config.comment_column)
},
),
Instruction::SetNew { dest } => {
let base = format!("{}SetNew r{} ← set()", indent, dest);
align_comment(&base, "Create new empty set", config.comment_column)
}
Instruction::SetAdd { set, value } => {
let base = format!("{}SetAdd r{} = r{}", indent, set, value);
let comment = format!("Add r{} to set r{}", value, set);
align_comment(&base, &comment, config.comment_column)
}
Instruction::SetCreate { params_index } => instruction_data
.get_set_create_params(params_index)
.map_or_else(
|| format!("{}SetCreate <invalid params P{}>", indent, params_index),
|params| {
let elements = params
.element_registers()
.iter()
.map(|r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let base =
format!("{}SetCreate r{}{{{}}}", indent, params.dest, elements);
let comment = format!(
"Create set from {} elements (undefined if any element is undefined)",
params.element_count()
);
align_comment(&base, &comment, config.comment_column)
},
),
Instruction::Contains {
dest,
collection,
value,
} => {
let base = format!(
"{}Contains r{} ← (r{} ∈ r{})",
indent, dest, value, collection
);
let comment = format!("Membership test: r{} in r{}", value, collection);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Count { dest, collection } => {
let base = format!("{}Count r{} ← count(r{})", indent, dest, collection);
let comment = format!("Get count/length of collection r{}", collection);
align_comment(&base, &comment, config.comment_column)
}
Instruction::AssertEq { left, right } => {
let base = format!("{}AssertEq assert r{} == r{}", indent, left, right);
let comment = format!(
"Assert r{} equals r{} (exit if either undefined or different)",
left, right
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::Guard { register, mode } => {
let (keyword, comment) = match mode {
crate::rvm::instructions::GuardMode::Not => (
format!("{}AssertNot assert !r{}", indent, register),
format!("Assert r{} is false/undefined (exit if true)", register),
),
crate::rvm::instructions::GuardMode::Condition => (
format!("{}Assert assert r{}", indent, register),
format!("Assert r{} is true (exit if false/undefined)", register),
),
crate::rvm::instructions::GuardMode::NotUndefined => (
format!(
"{}AssertNotUndefined assert_not_undefined r{}",
indent, register
),
format!("Assert r{} is not undefined (exit if undefined)", register),
),
};
align_comment(&keyword, &comment, config.comment_column)
}
Instruction::ReturnUndefinedIfNotTrue { condition } => {
let base = format!(
"{}ReturnUndefinedIfNotTrue if r{} != true return undefined",
indent, condition
);
let comment = format!(
"Return undefined unless r{} is exactly boolean true",
condition
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::CoalesceUndefinedToNull { register } => {
let base = format!(
"{}CoalesceUndefinedToNull r{} = null if undefined",
indent, register
);
let comment = format!("Azure Policy: absent field → null (r{})", register);
align_comment(&base, &comment, config.comment_column)
}
Instruction::LoopStart { params_index } => {
instruction_data.get_loop_params(params_index).map_or_else(
|| {
let base = format!("{}LoopStart [INVALID P({})] {{", indent, params_index);
align_comment(
&base,
"ERROR: Invalid loop parameters",
config.comment_column,
)
},
|params| {
let mode_str = match params.mode {
LoopMode::Any => "any",
LoopMode::Every => "every",
LoopMode::ForEach => "foreach",
};
let base = format!(
"{}LoopStart {} r{},r{} in r{} → r{} {{",
indent,
mode_str,
params.key_reg,
params.value_reg,
params.collection,
params.result_reg
);
let comment = format!(
"{} loop over r{}, body: {}-{} (P{})",
mode_str,
params.collection,
params.body_start,
params.loop_end,
params_index
);
align_comment(&base, &comment, config.comment_column)
},
)
}
Instruction::LoopNext {
body_start,
loop_end,
} => {
let base = format!(
"{}}} continue → {} or exit → {}",
indent, body_start, loop_end
);
let comment = format!(
"Next iteration or exit loop (body:{}-{})",
body_start, loop_end
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::CallRule { dest, rule_index } => {
let rule_name = program
.rule_infos
.get(usize::from(rule_index))
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
let base = format!("{}CallRule r{}{}", indent, dest, rule_name);
let comment = format!("Call rule '{}' (R{}) with caching", rule_name, rule_index);
align_comment(&base, &comment, config.comment_column)
}
Instruction::RuleInit {
result_reg,
rule_index,
} => {
let rule_name = program
.rule_infos
.get(usize::from(rule_index))
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
let base = format!("{}RuleInit {} → r{} {{", indent, rule_name, result_reg);
let comment = format!(
"Initialize rule '{}' (R{}) evaluation",
rule_name, rule_index
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::RuleReturn {} => {
let base = format!("{}}} return from rule", indent);
align_comment(&base, "End of rule evaluation", config.comment_column)
}
Instruction::ChainedIndex { params_index } => {
let (base, comment) = instruction_data
.get_chained_index_params(params_index)
.map_or_else(
|| {
let base_str = format!("{}ChainedIndex chained_index", indent);
let comment_str =
"Multi-level chained indexing (invalid params)".to_string();
(base_str, comment_str)
},
|params| {
let chain_parts: Vec<String> = params
.path_components
.iter()
.map(|component| match *component {
crate::rvm::instructions::LiteralOrRegister::Literal(idx) => {
program.literals.get(usize::from(idx)).map_or_else(
|| format!("[L{}?]", idx),
|literal| match *literal {
crate::Value::String(ref s) => {
format!(".{}", s.as_ref())
}
ref other => format!(
"[{}]",
serde_json::to_string(other)
.unwrap_or_else(|_| "?".to_string())
),
},
)
}
crate::rvm::instructions::LiteralOrRegister::Register(reg) => {
format!("[r{}]", reg)
}
})
.collect();
let chain_display = if chain_parts.is_empty() {
String::new()
} else {
format!(" r{}{}", params.root, chain_parts.join(""))
};
let base_str = format!(
"{}ChainedIndex r{} ← r{}{}",
indent, params.dest, params.root, chain_display
);
let comment_str = format!(
"Multi-level chained indexing: r{} → r{}",
params.root, params.dest
);
(base_str, comment_str)
},
);
align_comment(&base, &comment, config.comment_column)
}
Instruction::VirtualDataDocumentLookup { .. } => {
let base = format!(
"{}VirtualDataDocumentLookup virtual_data_document_lookup",
indent
);
align_comment(
&base,
"Lookup in data namespace virtual documents",
config.comment_column,
)
}
Instruction::DestructuringSuccess {} => {
let base = format!("{}DestructuringSuccess ✓", indent);
align_comment(
&base,
"Parameter destructuring validated",
config.comment_column,
)
}
Instruction::Halt {} => {
let base = format!("{}Halt halt", indent);
align_comment(&base, "Stop execution", config.comment_column)
}
Instruction::ComprehensionBegin { params_index } => instruction_data
.get_comprehension_begin_params(params_index)
.map_or_else(
|| {
let base = format!("{}CompBegin [INVALID P({})] {{", indent, params_index);
align_comment(
&base,
"ERROR: Invalid comprehension parameters",
config.comment_column,
)
},
|params| {
let mode_str = match params.mode {
crate::rvm::instructions::ComprehensionMode::Array => "array",
crate::rvm::instructions::ComprehensionMode::Set => "set",
crate::rvm::instructions::ComprehensionMode::Object => "object",
};
let (source_desc, result_desc) = if params.collection_reg == params.result_reg {
(
format!("r{}", params.collection_reg),
format!("r{}", params.result_reg),
)
} else {
(
format!("r{} (src)", params.collection_reg),
format!("r{} (dst)", params.result_reg),
)
};
let base = format!(
"{}CompBegin {} {}{} k:{} v:{} {{",
indent,
mode_str,
source_desc,
result_desc,
params.key_reg,
params.value_reg
);
let comment = format!(
"{} comprehension in r{}, body: {}-{} (P{})",
mode_str,
params.collection_reg,
params.body_start,
params.comprehension_end,
params_index
);
align_comment(&base, &comment, config.comment_column)
},
),
Instruction::ComprehensionYield { value_reg, key_reg } => {
let base = key_reg.map_or_else(
|| format!("{}CompYield r{}", indent, value_reg),
|k| format!("{}CompYield r{} r{}", indent, k, value_reg),
);
align_comment(&base, "Yield value to comprehension", config.comment_column)
}
Instruction::ComprehensionEnd {} => {
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)
}
}
}
/// Generate compact tabular assembly listing
pub fn generate_tabular_assembly_listing(
program: &Program,
_config: &AssemblyListingConfig,
) -> String {
let mut output = String::new();
let mut indent_level: usize = 0;
// Add header
push_line(&mut output, format_args!("; RVM Assembly (Tabular Format)"));
push_line(
&mut output,
format_args!(
"; {} instructions, {} literals",
program.instructions.len(),
program.literals.len()
),
);
push_line(&mut output, format_args!(";"));
push_line(
&mut output,
format_args!("; PC | Instruction | Operation"),
);
push_line(
&mut output,
format_args!(";-----|--------------|----------"),
);
for (pc, instruction) in program.instructions.iter().enumerate() {
// Handle loop indentation
match *instruction {
Instruction::LoopNext { .. } => {
indent_level = indent_level.saturating_sub(1);
}
Instruction::RuleReturn { .. } => {
indent_level = indent_level.saturating_sub(1);
}
_ => {}
}
let indent = " ".repeat(indent_level.saturating_mul(2)); // Smaller indent for tabular format
// Format in tabular style
let addr_str = format!("{:03}", pc);
let inst_name = get_instruction_name(instruction);
let operation =
format_operation_compact(instruction, &indent, &program.instruction_data, program);
push_line(
&mut output,
format_args!("{:>4} | {:12} | {}", addr_str, inst_name, operation),
);
// Increase indentation for loop/rule starts
match *instruction {
Instruction::LoopStart { .. } => {
indent_level = indent_level.saturating_add(1);
}
Instruction::RuleInit { .. } => {
indent_level = indent_level.saturating_add(1);
}
_ => {}
}
}
output
}
const fn get_instruction_name(instruction: &Instruction) -> &'static str {
match *instruction {
Instruction::Load { .. } => "LOAD",
Instruction::LoadTrue { .. } => "LOAD_TRUE",
Instruction::LoadFalse { .. } => "LOAD_FALSE",
Instruction::LoadNull { .. } => "LOAD_NULL",
Instruction::LoadBool { .. } => "LOAD_BOOL",
Instruction::LoadData { .. } => "LOAD_DATA",
Instruction::LoadInput { .. } => "LOAD_INPUT",
Instruction::LoadContext { .. } => "LOAD_CONTEXT",
Instruction::LoadMetadata { .. } => "LOAD_METADATA",
Instruction::Move { .. } => "MOVE",
Instruction::Add { .. } => "ADD",
Instruction::Sub { .. } => "SUB",
Instruction::Mul { .. } => "MUL",
Instruction::Div { .. } => "DIV",
Instruction::Mod { .. } => "MOD",
Instruction::Eq { .. } => "EQ",
Instruction::Ne { .. } => "NE",
Instruction::Lt { .. } => "LT",
Instruction::Le { .. } => "LE",
Instruction::Gt { .. } => "GT",
Instruction::Ge { .. } => "GE",
Instruction::And { .. } => "AND",
Instruction::Or { .. } => "OR",
Instruction::Not { .. } => "NOT",
Instruction::BuiltinCall { .. } => "BUILTIN_CALL",
Instruction::FunctionCall { .. } => "FUNC_CALL",
Instruction::HostAwait { .. } => "HOST_AWAIT",
Instruction::Return { .. } => "RETURN",
Instruction::ObjectSet { .. } => "OBJ_SET",
Instruction::ObjectCreate { .. } => "OBJ_CREATE",
Instruction::Index { .. } => "INDEX",
Instruction::IndexLiteral { .. } => "INDEX_LIT",
Instruction::ArrayNew { .. } => "ARRAY_NEW",
Instruction::ArrayPush { .. } => "ARRAY_PUSH",
Instruction::ArrayPushDefined { .. } => "ARRAY_PUSH_DEF",
Instruction::ArrayCreate { .. } => "ARRAY_CREATE",
Instruction::SetNew { .. } => "SET_NEW",
Instruction::SetAdd { .. } => "SET_ADD",
Instruction::SetCreate { .. } => "SET_CREATE",
Instruction::Contains { .. } => "CONTAINS",
Instruction::Count { .. } => "COUNT",
Instruction::AssertEq { .. } => "ASSERT_EQ",
Instruction::Guard { mode, .. } => match mode {
crate::rvm::instructions::GuardMode::Not => "ASSERT_NOT",
crate::rvm::instructions::GuardMode::Condition => "ASSERT",
crate::rvm::instructions::GuardMode::NotUndefined => "ASSERT_NOT_UNDEF",
},
Instruction::ReturnUndefinedIfNotTrue { .. } => "RET_UNDEF_IF_NOT_TRUE",
Instruction::CoalesceUndefinedToNull { .. } => "COALESCE_UNDEF_TO_NULL",
Instruction::LoopStart { .. } => "LOOP_START",
Instruction::LoopNext { .. } => "LOOP_NEXT",
Instruction::CallRule { .. } => "CALL_RULE",
Instruction::RuleInit { .. } => "RULE_INIT",
Instruction::RuleReturn { .. } => "RULE_RET",
Instruction::DestructuringSuccess {} => "DESTRUCT_SUCCESS",
Instruction::ChainedIndex { .. } => "CHAINED_INDEX",
Instruction::VirtualDataDocumentLookup { .. } => "VIRTUAL_DATA_DOC_LOOKUP",
Instruction::Halt {} => "HALT",
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",
},
}
}
fn format_operation_compact(
instruction: &Instruction,
indent: &str,
instruction_data: &InstructionData,
_program: &Program,
) -> String {
match *instruction {
Instruction::Load { dest, literal_idx } => {
format!("{}r{} ← L{}", indent, dest, literal_idx)
}
Instruction::LoadInput { dest } => {
format!("{}r{} ← input", indent, dest)
}
Instruction::LoadContext { dest } => {
format!("{}r{} ← context", indent, dest)
}
Instruction::LoadMetadata { dest } => {
format!("{}r{} ← metadata", indent, dest)
}
Instruction::LoadData { dest } => {
format!("{}r{} ← data", indent, dest)
}
Instruction::Move { dest, src } => {
format!("{}r{} ← r{}", indent, dest, src)
}
Instruction::Add { dest, left, right } => {
format!("{}r{} ← r{} + r{}", indent, dest, left, right)
}
Instruction::Index {
dest,
container,
key,
} => {
format!("{}r{} ← r{}[r{}]", indent, dest, container, key)
}
Instruction::IndexLiteral {
dest,
container,
literal_idx,
} => {
format!("{}r{} ← r{}[L{}]", indent, dest, container, literal_idx)
}
Instruction::LoopStart { params_index } => {
instruction_data.get_loop_params(params_index).map_or_else(
|| format!("{}loop P({}) {{", indent, params_index),
|params| {
format!(
"{}loop r{} in r{} {{",
indent, params.value_reg, params.collection
)
},
)
}
Instruction::LoopNext { .. } => {
format!("{}}}", indent)
}
Instruction::CallRule { dest, rule_index } => {
format!("{}r{} ← rule_{}", indent, dest, rule_index)
}
Instruction::HostAwait { dest, arg, id } => {
format!("{}await r{} → r{} (id r{})", indent, arg, dest, id)
}
Instruction::RuleInit {
result_reg,
rule_index,
} => {
format!("{}rule_{} → r{} {{", indent, rule_index, result_reg)
}
Instruction::RuleReturn {} => {
format!("{}}}", indent)
}
Instruction::DestructuringSuccess {} => {
format!("{}✓ destructuring validated", indent)
}
_ => {
// For other instructions, use a simplified version
format!(
"{}{}",
indent,
instruction
.to_string()
.replace("R(", "r")
.replace(")", "")
.replace("L(", "L")
)
}
}
}