mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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.
1161 lines
47 KiB
Rust
1161 lines
47 KiB
Rust
// 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")
|
||
)
|
||
}
|
||
}
|
||
}
|