feat!: add Rego Virtual Machine (RVM) implementation (#495)

* feat!: add Rego Virtual Machine (RVM) implementation

This commit introduces a register-based virtual machine for executing Rego
policies with bytecode-style instructions. Unlike the existing tree-walking
interpreter, the RVM compiles policies into instruction sequences that operate
on virtual registers, offering better performance and optimization potential.

Core Components:

Instruction Set Architecture:
- Define instruction types for data operations, control flow, and builtins
- Implement instruction parameter encoding and display formatting
- Add instruction parser with comprehensive test coverage

Virtual Machine Engine:
- Register-based execution model with program counter management
- Loop execution supporting iterators, comprehensions, and quantifiers
- Function call handling with argument evaluation and context management
- Rule evaluation with default value resolution and virtual data support
- Arithmetic and comparison operation implementations

Program Representation:
- Program listing builder with instruction sequencing
- Rule tree construction for organizing policy rules
- Binary and JSON serialization for compiled programs
- Recompilation support for program modification

Testing Infrastructure:
- Extensive YAML test suites covering all VM features
- Rust unit tests for VM execution and instruction parsing
- Test suites for loops, comprehensions, builtins, and control flow

BREAKING CHANGE: Introduces new VM execution path alongside interpreter

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

* docs: add detailed RVM architecture references

Introduce architecture.md explaining program artifacts, serialization, and runtime subsystems.
Document the full opcode catalog in instruction-set.md, including operands, parameter tables, and outcomes.
Walk through execution flow, stacks, and operational guidance in vm-runtime.md, tying the runtime to the new architecture docs.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>

---------

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2025-11-14 11:43:19 -06:00
committed by GitHub
parent 6dc505c88b
commit 49bd3c22f3
89 changed files with 19158 additions and 313 deletions
+322
View File
@@ -0,0 +1,322 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::Result as AnyResult;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use super::types::{BuiltinInfo, ProgramMetadata, RuleInfo, SourceFile, SpanInfo};
use crate::builtins::BuiltinFcn;
use crate::rvm::instructions::InstructionData;
use crate::rvm::Instruction;
use crate::value::Value;
/// Complete compiled program containing all execution artifacts
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Program {
/// Compiled bytecode instructions
pub instructions: Vec<Instruction>,
/// Literal value table (skipped in serde, serialized separately as JSON)
#[serde(skip, default = "Vec::new")]
pub literals: Vec<Value>,
/// Complex instruction parameter data (for LoopStart, Call, etc.)
pub instruction_data: InstructionData,
/// Builtin function information table
pub builtin_info_table: Vec<BuiltinInfo>,
/// Entry points mapping with preserved insertion order (skipped in serde, serialized separately as JSON)
#[serde(skip, default = "IndexMap::new")]
pub entry_points: IndexMap<String, usize>,
/// Source files table with content (skipped in serde, serialized separately as JSON)
#[serde(skip, default = "Vec::new")]
pub sources: Vec<SourceFile>,
/// Rule metadata: rule_index -> rule information
pub rule_infos: Vec<RuleInfo>,
/// Span information for each instruction (for debugging)
pub instruction_spans: Vec<Option<SpanInfo>>,
/// Main program entry point
pub main_entry_point: usize,
/// Maximum register window size observed across all rule definitions
pub max_rule_window_size: usize,
/// Register window size needed for entry point dispatch
pub dispatch_window_size: usize,
/// Program metadata
pub metadata: ProgramMetadata,
/// Rule tree for efficient rule lookup (skipped in serde, serialized separately as JSON)
/// Maps rule paths (e.g., "data.p1.r1") to rule indices
/// Structure: {"p1": {"r1": rule_index}, "p2": {"p3": {"r2": rule_index}}}
#[serde(skip, default = "Value::new_object")]
pub rule_tree: Value,
/// Resolved builtins - actual builtin function values fetched from interpreter's builtin map
/// This field is skipped during serialization and reinitialized after deserialization
#[serde(skip)]
pub resolved_builtins: Vec<BuiltinFcn>,
/// Flag indicating that VirtualDataDocumentLookup instruction was used and runtime recursion checking is needed
pub needs_runtime_recursion_check: bool,
/// Flag indicating that recompilation is needed due to partial deserialization failure
/// This is set to true when the artifact section was successfully read but the extensible
/// section failed to deserialize (e.g., due to version incompatibility)
#[serde(default)]
pub needs_recompilation: bool,
/// Rego language version used for compilation (true for Rego v0, false for Rego v1)
/// This must be preserved during recompilation to maintain policy semantics
/// Serialized separately in the artifact section for guaranteed availability
#[serde(skip, default)]
pub rego_v0: bool,
}
impl Program {
/// Current serialization format version
pub const SERIALIZATION_VERSION: u32 = 3;
/// Magic bytes to identify Regorus program files
pub const MAGIC: [u8; 4] = *b"REGO";
/// Create a new empty program
pub fn new() -> Self {
Self {
instructions: Vec::new(),
literals: Vec::new(),
instruction_data: InstructionData::new(),
builtin_info_table: Vec::new(),
entry_points: IndexMap::new(),
sources: Vec::new(),
rule_infos: Vec::new(),
instruction_spans: Vec::new(),
main_entry_point: 0,
max_rule_window_size: 0,
dispatch_window_size: 0,
metadata: ProgramMetadata {
compiler_version: env!("CARGO_PKG_VERSION").to_string(),
compiled_at: "unknown".to_string(),
source_info: "unknown".to_string(),
optimization_level: 0,
},
rule_tree: Value::new_object(),
resolved_builtins: Vec::new(),
needs_runtime_recursion_check: false,
needs_recompilation: false,
rego_v0: false, // Default to Rego v1
}
}
/// Add a source file and return its index
pub fn add_source(&mut self, name: String, content: String) -> usize {
let source_file = SourceFile::new(name.clone(), content);
let index = self.sources.len();
self.sources.push(source_file);
index
}
/// Add loop parameters and return the index
pub fn add_loop_params(&mut self, params: crate::rvm::instructions::LoopStartParams) -> u16 {
self.instruction_data.add_loop_params(params)
}
/// Add comprehension begin parameters and return the index
pub fn add_comprehension_begin_params(
&mut self,
params: crate::rvm::instructions::ComprehensionBeginParams,
) -> u16 {
self.instruction_data.add_comprehension_begin_params(params)
}
/// Add builtin call parameters and return the index
pub fn add_builtin_call_params(
&mut self,
params: crate::rvm::instructions::BuiltinCallParams,
) -> u16 {
self.instruction_data.add_builtin_call_params(params)
}
/// Add function call parameters and return the index
pub fn add_function_call_params(
&mut self,
params: crate::rvm::instructions::FunctionCallParams,
) -> u16 {
self.instruction_data.add_function_call_params(params)
}
/// Add builtin info and return the index
pub fn add_builtin_info(&mut self, builtin_info: BuiltinInfo) -> u16 {
let index = self.builtin_info_table.len();
self.builtin_info_table.push(builtin_info);
index as u16
}
/// Get builtin info by index
pub fn get_builtin_info(&self, index: u16) -> Option<&BuiltinInfo> {
self.builtin_info_table.get(index as usize)
}
/// Update loop parameters by index
pub fn update_loop_params<F>(&mut self, params_index: u16, updater: F)
where
F: FnOnce(&mut crate::rvm::instructions::LoopStartParams),
{
if let Some(params) = self.instruction_data.get_loop_params_mut(params_index) {
updater(params);
}
}
/// Update comprehension begin parameters by index
pub fn update_comprehension_begin_params<F>(&mut self, params_index: u16, updater: F)
where
F: FnOnce(&mut crate::rvm::instructions::ComprehensionBeginParams),
{
if let Some(params) = self
.instruction_data
.get_comprehension_begin_params_mut(params_index)
{
updater(params);
}
}
/// Get detailed instruction display with parameter resolution
pub fn display_instruction_with_params(&self, instruction: &Instruction) -> String {
instruction.display_with_params(&self.instruction_data)
}
/// Add a source file directly and return its index
pub fn add_source_file(&mut self, source_file: SourceFile) -> usize {
for (i, existing) in self.sources.iter().enumerate() {
if existing.name == source_file.name {
return i;
}
}
let index = self.sources.len();
self.sources.push(source_file);
index
}
/// Get source file by index
pub fn get_source_file(&self, index: usize) -> Option<&SourceFile> {
self.sources.get(index)
}
/// Get source content by index
pub fn get_source(&self, index: usize) -> Option<&str> {
self.sources.get(index).map(|s| s.content.as_str())
}
/// Get source name by index
pub fn get_source_name(&self, index: usize) -> Option<&str> {
self.sources.get(index).map(|s| s.name.as_str())
}
/// Get rule info by index
pub fn get_rule_info(&self, rule_index: usize) -> Option<&RuleInfo> {
self.rule_infos.get(rule_index)
}
/// Get span information for instruction
pub fn get_instruction_span(&self, instruction_index: usize) -> Option<&SpanInfo> {
self.instruction_spans
.get(instruction_index)
.and_then(|span| span.as_ref())
}
/// Add instruction with optional span
pub fn add_instruction(&mut self, instruction: Instruction, span: Option<SpanInfo>) {
self.instructions.push(instruction);
self.instruction_spans.push(span);
}
/// Add literal value and return its index
pub fn add_literal(&mut self, value: Value) -> usize {
for (i, existing) in self.literals.iter().enumerate() {
if existing == &value {
return i;
}
}
let index = self.literals.len();
self.literals.push(value);
index
}
/// Initialize resolved builtins directly from the BUILTINS HashMap
/// This should be called after deserialization to populate the skipped field
/// Returns an error if any required builtin is missing
pub fn initialize_resolved_builtins(&mut self) -> AnyResult<()> {
self.resolved_builtins.clear();
self.resolved_builtins
.reserve(self.builtin_info_table.len());
for builtin_info in &self.builtin_info_table {
if let Some(&builtin_fcn) = crate::builtins::BUILTINS.get(builtin_info.name.as_str()) {
self.resolved_builtins.push(builtin_fcn);
} else {
return Err(anyhow::anyhow!(
"Missing builtin function: {}",
builtin_info.name
));
}
}
Ok(())
}
/// Get resolved builtin function by index
pub fn get_resolved_builtin(&self, index: u16) -> Option<&BuiltinFcn> {
self.resolved_builtins.get(index as usize)
}
/// Check if resolved builtins are initialized
pub fn has_resolved_builtins(&self) -> bool {
!self.resolved_builtins.is_empty()
}
/// Add an entry point mapping from path to rule index
pub fn add_entry_point(&mut self, path: String, rule_index: usize) {
self.entry_points.insert(path, rule_index);
}
/// Get rule index for an entry point path
pub fn get_entry_point(&self, path: &str) -> Option<usize> {
self.entry_points.get(path).copied()
}
/// Get all entry points as IndexMap
pub fn get_entry_points(&self) -> &IndexMap<String, usize> {
&self.entry_points
}
/// Check if recompilation is needed due to partial deserialization failure
pub fn needs_recompilation(&self) -> bool {
self.needs_recompilation
}
/// Mark that recompilation is needed
pub fn set_needs_recompilation(&mut self, needs_recompilation: bool) {
self.needs_recompilation = needs_recompilation;
}
/// Check if the program is fully functional (not needing recompilation)
pub fn is_fully_functional(&self) -> bool {
!self.needs_recompilation
}
}
impl Default for Program {
fn default() -> Self {
Self::new()
}
}
+964
View File
@@ -0,0 +1,964 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt::Write;
use crate::rvm::{
instructions::{Instruction, InstructionData, LoopMode},
program::Program,
};
/// 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
writeln!(
output,
"; RVM Assembly - {} instructions, {} literals, {} builtins",
program.instructions.len(),
program.literals.len(),
program.builtin_info_table.len()
)
.unwrap();
// Add builtins table
if !program.builtin_info_table.is_empty() {
writeln!(output, ";").unwrap();
writeln!(output, "; BUILTINS TABLE:").unwrap();
for (idx, builtin_info) in program.builtin_info_table.iter().enumerate() {
writeln!(output, "; B{:2}: {}", idx, builtin_info.name).unwrap();
}
}
// Add literals table
if config.show_literal_values && !program.literals.is_empty() {
writeln!(output, ";").unwrap();
writeln!(output, "; LITERALS (JSON values):").unwrap();
for (idx, literal) in program.literals.iter().enumerate() {
let literal_json =
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
writeln!(output, "; L{:2}: {}", idx, literal_json).unwrap();
}
}
// Add rules table if available
if !program.rule_infos.is_empty() {
writeln!(output, ";").unwrap();
writeln!(output, "; RULES TABLE:").unwrap();
for (idx, rule_info) in program.rule_infos.iter().enumerate() {
writeln!(output, "; R{:2}: {}", idx, rule_info.name).unwrap();
}
}
writeln!(output, ";").unwrap();
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() {
writeln!(output).unwrap();
}
current_rule_index = Some(*rule_index);
// Add rule name prefix
if let Some(rule_info) = program.rule_infos.get(*rule_index as usize) {
writeln!(output, "; ===== RULE: {} =====", rule_info.name).unwrap();
} else {
writeln!(output, "; ===== RULE: rule_{} =====", rule_index).unwrap();
}
}
// Check if current PC matches any active end addresses (loops, comprehensions, rules)
let current_pc = pc as u16;
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 * 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,
);
writeln!(output, "{}{}", addr_str, inst_str).unwrap();
// 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 += 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 += 1;
}
}
Instruction::RuleInit { .. } => {
indent_level += 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 - 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 = if *literal_idx < program.literals.len() as u16 {
let literal_json = serde_json::to_string(&program.literals[*literal_idx as usize])
.unwrap_or_else(|_| "<invalid>".to_string());
format!("Load literal: {}", literal_json)
} else {
"Load literal: <invalid index>".to_string()
};
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::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 } => {
if let Some(params) = instruction_data.get_builtin_call_params(*params_index) {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let builtin_name = program
.builtin_info_table
.get(params.builtin_index as usize)
.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)
} else {
let base = format!("{}BuiltinCall [INVALID P({})]", indent, params_index);
align_comment(
&base,
"ERROR: Invalid builtin call parameters",
config.comment_column,
)
}
}
Instruction::FunctionCall { params_index } => {
if let Some(params) = instruction_data.get_function_call_params(*params_index) {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("r{}", r))
.collect::<Vec<_>>()
.join(", ");
let func_name = program
.rule_infos
.get(params.func_rule_index as usize)
.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)
} else {
let base = format!("{}FunctionCall [INVALID P({})]", indent, params_index);
align_comment(
&base,
"ERROR: Invalid function call parameters",
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 = match params {
Some(p) => format!(
"Create object with {} fields (P{})",
p.field_count(),
params_index
),
None => format!("Create object (P{} - INVALID)", 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 = if *literal_idx < program.literals.len() as u16 {
let literal_json = serde_json::to_string(&program.literals[*literal_idx as usize])
.unwrap_or_else(|_| "<invalid>".to_string());
format!("Index with literal key: r{}[{}]", container, literal_json)
} else {
format!(
"Index with literal: r{}[L{}] (invalid index)",
container, literal_idx
)
};
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::ArrayCreate { params_index } => {
if let Some(params) = instruction_data.get_array_create_params(*params_index) {
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)
} else {
format!("{}ArrayCreate <invalid params P{}>", indent, params_index)
}
}
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 } => {
if let Some(params) = instruction_data.get_set_create_params(*params_index) {
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)
} else {
format!("{}SetCreate <invalid params P{}>", indent, params_index)
}
}
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::AssertCondition { condition } => {
let base = format!("{}Assert assert r{}", indent, condition);
let comment = format!("Assert r{} is true (exit if false/undefined)", condition);
align_comment(&base, &comment, config.comment_column)
}
Instruction::AssertNotUndefined { register } => {
let base = format!(
"{}AssertNotUndefined assert_not_undefined r{}",
indent, register
);
let comment = format!("Assert r{} is not undefined (exit if undefined)", register);
align_comment(&base, &comment, config.comment_column)
}
Instruction::LoopStart { params_index } => {
if let Some(params) = instruction_data.get_loop_params(*params_index) {
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)
} else {
let base = format!("{}LoopStart [INVALID P({})] {{", indent, params_index);
align_comment(
&base,
"ERROR: Invalid loop parameters",
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(*rule_index as usize)
.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(*rule_index as usize)
.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) =
if let Some(params) = instruction_data.get_chained_index_params(*params_index) {
let chain_parts: Vec<String> = params
.path_components
.iter()
.map(|component| match component {
crate::rvm::instructions::LiteralOrRegister::Literal(idx) => {
if let Some(literal) = program.literals.get(*idx as usize) {
match literal {
crate::Value::String(s) => format!(".{}", s.as_ref()),
other => format!(
"[{}]",
serde_json::to_string(other)
.unwrap_or_else(|_| "?".to_string())
),
}
} else {
format!("[L{}?]", idx)
}
}
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)
} else {
let base_str = format!("{}ChainedIndex chained_index", indent);
let comment_str = "Multi-level chained indexing (invalid params)".to_string();
(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 } => {
if let Some(params) = instruction_data.get_comprehension_begin_params(*params_index) {
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)
} else {
let base = format!("{}CompBegin [INVALID P({})] {{", indent, params_index);
align_comment(
&base,
"ERROR: Invalid comprehension parameters",
config.comment_column,
)
}
}
Instruction::ComprehensionYield { value_reg, key_reg } => {
let base = match key_reg {
Some(k) => format!("{}CompYield r{} r{}", indent, k, value_reg),
None => format!("{}CompYield r{}", indent, 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)
}
}
}
/// 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
writeln!(output, "; RVM Assembly (Tabular Format)").unwrap();
writeln!(
output,
"; {} instructions, {} literals",
program.instructions.len(),
program.literals.len()
)
.unwrap();
writeln!(output, ";").unwrap();
writeln!(output, "; PC | Instruction | Operation").unwrap();
writeln!(output, ";-----|--------------|----------").unwrap();
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 * 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);
writeln!(output, "{:>4} | {:12} | {}", addr_str, inst_name, operation).unwrap();
// Increase indentation for loop/rule starts
match instruction {
Instruction::LoopStart { .. } => {
indent_level += 1;
}
Instruction::RuleInit { .. } => {
indent_level += 1;
}
_ => {}
}
}
output
}
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::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::ArrayCreate { .. } => "ARRAY_CREATE",
Instruction::SetNew { .. } => "SET_NEW",
Instruction::SetAdd { .. } => "SET_ADD",
Instruction::SetCreate { .. } => "SET_CREATE",
Instruction::Contains { .. } => "CONTAINS",
Instruction::Count { .. } => "COUNT",
Instruction::AssertCondition { .. } => "ASSERT",
Instruction::AssertNotUndefined { .. } => "ASSERT_NOT_UNDEF",
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",
}
}
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::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 } => {
if let Some(params) = instruction_data.get_loop_params(*params_index) {
format!(
"{}loop r{} in r{} {{",
indent, params.value_reg, params.collection
)
} else {
format!("{}loop P({}) {{", indent, params_index)
}
}
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")
)
}
}
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
mod core;
mod listing;
mod recompile;
mod rule_tree;
mod serialization;
mod types;
pub use core::Program;
pub use listing::{
generate_assembly_listing, generate_tabular_assembly_listing, AssemblyListingConfig,
};
pub(crate) use serialization::value::{binaries_to_values, BinaryValue};
pub use serialization::{DeserializationResult, VersionedProgram};
pub use types::{
BuiltinInfo, FunctionInfo, ProgramMetadata, RuleInfo, RuleType, SourceFile, SpanInfo,
};
+22
View File
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use super::Program;
use alloc::string::{String, ToString};
impl Program {
/// Compile a partial deserialized program to a complete one
///
/// This method takes a partial program (containing only entry_points and sources)
/// and recompiles it to create a complete program with all instructions and data.
pub fn compile_from_partial(partial_program: Program) -> Result<Program, String> {
if partial_program.entry_points.is_empty() {
return Err("Partial program must contain entry points".to_string());
}
if partial_program.sources.is_empty() {
return Err("Partial program must contain sources".to_string());
}
Err("Recompilation from partial program is not yet implemented".to_string())
}
}
+105
View File
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::Result as AnyResult;
use crate::value::Value;
use super::Program;
impl Program {
/// Add a rule to the rule tree
/// path: Package path components (e.g., ["p1", "p2"] for data.p1.p2.rule)
/// rule_name: Rule name (e.g., "rule")
/// rule_index: Index of the rule in rule_infos
pub fn add_rule_to_tree(
&mut self,
path: &[String],
rule_name: &str,
rule_index: usize,
) -> AnyResult<()> {
let mut full_path = Vec::with_capacity(path.len() + 1);
full_path.extend(path.iter().map(|s| s.as_str()));
full_path.push(rule_name);
let target = self.rule_tree.make_or_get_value_mut(&full_path)?;
*target = Value::Number(rule_index.into());
Ok(())
}
/// Check for conflicts between rule tree and data
/// Returns an error if any rule path conflicts with data paths
pub fn check_rule_data_conflicts(&self, data: &Value) -> Result<(), crate::rvm::vm::VmError> {
let actual_rule_tree = &self.rule_tree["data"];
match actual_rule_tree {
Value::Undefined => return Ok(()),
Value::Object(rule_obj) if rule_obj.is_empty() => return Ok(()),
_ => {}
}
Self::check_conflicts_recursive(actual_rule_tree, data, &mut Vec::new())
}
fn check_conflicts_recursive(
rule_tree: &Value,
data: &Value,
current_path: &mut Vec<String>,
) -> Result<(), crate::rvm::vm::VmError> {
match rule_tree {
Value::Object(rule_obj) => {
for (key, rule_value) in rule_obj.iter() {
if let Value::String(key_str) = key {
current_path.push(key_str.to_string());
let data_value = &data[key];
match rule_value {
Value::Number(_) => {
if data_value != &Value::Undefined {
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
"Conflict: rule defines path '{}' but data also provides this path",
current_path.join("."),
)));
}
}
Value::Object(_) => {
if let Value::Object(_) = data_value {
Self::check_conflicts_recursive(
rule_value,
data_value,
current_path,
)?;
} else if data_value != &Value::Undefined {
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
"Conflict: rule defines subpaths under '{}' but data provides a non-object value at this path",
current_path.join("."),
)));
}
}
_ => {
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
"Invalid rule tree structure at path '{}'",
current_path.join("."),
)));
}
}
current_path.pop();
}
}
}
_ => {
return Err(crate::rvm::vm::VmError::RuleDataConflict(
"Rule tree root must be an object".to_string(),
));
}
}
Ok(())
}
}
+482
View File
@@ -0,0 +1,482 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use bincode::config::standard;
use bincode::serde::{decode_from_slice, encode_to_vec};
use indexmap::IndexMap;
use super::super::types::SourceFile;
use super::{DeserializationResult, Program};
use crate::value::Value;
use super::value::{
binaries_to_values, binary_to_value, BinaryValue, BinaryValueRef, BinaryValueSlice,
};
type ArtifactData = (IndexMap<String, usize>, Vec<SourceFile>, bool);
impl Program {
/// Serialize program to binary format.
/// Uses pure bincode for all sections now that `Value` supports serde.
pub fn serialize_binary(&self) -> Result<Vec<u8>, String> {
let mut buffer = Vec::new();
buffer.extend_from_slice(&Self::MAGIC);
buffer.extend_from_slice(&Self::SERIALIZATION_VERSION.to_le_bytes());
let entry_points_bin = encode_to_vec(&self.entry_points, standard())
.map_err(|e| format!("Entry points bincode serialization failed: {}", e))?;
let sources_bin = encode_to_vec(&self.sources, standard())
.map_err(|e| format!("Sources bincode serialization failed: {}", e))?;
let literals_bin = encode_to_vec(BinaryValueSlice(self.literals.as_slice()), standard())
.map_err(|e| format!("Literals bincode serialization failed: {}", e))?;
let rule_tree_bin = encode_to_vec(BinaryValueRef(&self.rule_tree), standard())
.map_err(|e| format!("Rule tree bincode serialization failed: {}", e))?;
let binary_data = encode_to_vec(self, standard())
.map_err(|e| format!("Program structure binary serialization failed: {}", e))?;
buffer.extend_from_slice(&(entry_points_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(&(sources_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(&(literals_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(&(rule_tree_bin.len() as u32).to_le_bytes());
buffer.push(if self.rego_v0 { 1 } else { 0 });
buffer.extend_from_slice(&entry_points_bin);
buffer.extend_from_slice(&sources_bin);
buffer.extend_from_slice(&literals_bin);
buffer.extend_from_slice(&rule_tree_bin);
buffer.extend_from_slice(&(binary_data.len() as u32).to_le_bytes());
buffer.extend_from_slice(&binary_data);
Ok(buffer)
}
/// Deserialize only the artifact section (entry_points and sources) from binary format
pub fn deserialize_artifacts_only(data: &[u8]) -> Result<ArtifactData, String> {
if data.len() < 9 {
return Err("Data too short for artifact header".to_string());
}
if data[0..4] != Self::MAGIC {
return Err("Invalid file format - magic number mismatch".to_string());
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
match version {
1 => {
if data.len() < 17 {
return Err("Data too short for artifact header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let rego_v0 = data[16] != 0;
let entry_points_start = 17;
let sources_start = entry_points_start + entry_points_len;
let sources_end = sources_start + sources_len;
if data.len() < sources_end {
return Err("Data truncated in artifact section".to_string());
}
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| IndexMap::new());
let sources = decode_from_slice(&data[sources_start..sources_end], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| Vec::new());
Ok((entry_points, sources, rego_v0))
}
2 | 3 => {
if data.len() < 25 {
return Err("Data too short for artifact header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let literals_len =
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let rule_tree_len =
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
let rego_v0 = data[24] != 0;
let entry_points_start = 25;
let sources_start = entry_points_start + entry_points_len;
let literals_start = sources_start + sources_len;
let rule_tree_start = literals_start + literals_len;
let rule_tree_end = rule_tree_start + rule_tree_len;
if data.len() < rule_tree_end {
return Err("Data truncated in artifact section".to_string());
}
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| IndexMap::new());
let sources = decode_from_slice(&data[sources_start..literals_start], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| Vec::new());
Ok((entry_points, sources, rego_v0))
}
v => Err(format!("Unsupported version {}", v)),
}
}
/// Deserialize program from binary format with version checking
pub fn deserialize_binary(data: &[u8]) -> Result<DeserializationResult, String> {
if data.len() < 9 {
return Err("Data too short for header".to_string());
}
if data[0..4] != Self::MAGIC {
return Err("Invalid file format - magic number mismatch".to_string());
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
if version > Self::SERIALIZATION_VERSION {
return Err(format!(
"Unsupported version {}. Maximum supported version is {}",
version,
Self::SERIALIZATION_VERSION
));
}
match version {
1 => {
if data.len() < 25 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let rego_v0 = data[16] != 0;
let entry_points_start = 17;
let sources_start = entry_points_start + entry_points_len;
let binary_len_start = sources_start + sources_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
let json_len_start = binary_len_start + 4 + binary_len;
if data.len() < json_len_start + 4 {
return Err("Data too short for JSON length".to_string());
}
let json_len = u32::from_le_bytes([
data[json_len_start],
data[json_len_start + 1],
data[json_len_start + 2],
data[json_len_start + 3],
]) as usize;
let total_expected = json_len_start + 4 + json_len;
if data.len() < total_expected {
return Err("Data truncated".to_string());
}
let binary_start = binary_len_start + 4;
let json_start = json_len_start + 4;
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
let sources = decode_from_slice(&data[sources_start..binary_len_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
let mut needs_recompilation = false;
let mut program = match decode_from_slice::<Program, _>(
&data[binary_start..json_start],
standard(),
) {
Ok((prog, _)) => prog,
Err(_e) => {
needs_recompilation = true;
Program::new()
}
};
let (literals, rule_tree) = match serde_json::from_slice::<serde_json::Value>(
&data[json_start..json_start + json_len],
) {
Ok(combined) => {
let literals = combined
.get("literals")
.and_then(|v| serde_json::from_value::<Vec<Value>>(v.clone()).ok())
.unwrap_or_else(|| {
needs_recompilation = true;
Vec::new()
});
let rule_tree = combined
.get("rule_tree")
.and_then(|v| serde_json::from_value::<Value>(v.clone()).ok())
.unwrap_or_else(|| {
needs_recompilation = true;
Value::new_object()
});
(literals, rule_tree)
}
Err(_e) => {
needs_recompilation = true;
(Vec::new(), Value::new_object())
}
};
program.entry_points = entry_points;
program.sources = sources;
program.literals = literals;
program.rule_tree = rule_tree;
program.rego_v0 = rego_v0;
program.needs_recompilation = needs_recompilation;
if !program.builtin_info_table.is_empty() {
if let Err(_e) = program.initialize_resolved_builtins() {
program.needs_recompilation = true;
}
}
if program.needs_recompilation {
Ok(DeserializationResult::Partial(program))
} else {
Ok(DeserializationResult::Complete(program))
}
}
2 | 3 => {
if data.len() < 29 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let literals_len =
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let rule_tree_len =
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
let rego_v0 = data[24] != 0;
let entry_points_start = 25;
let sources_start = entry_points_start + entry_points_len;
let literals_start = sources_start + sources_len;
let rule_tree_start = literals_start + literals_len;
let binary_len_start = rule_tree_start + rule_tree_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
let binary_start = binary_len_start + 4;
let binary_end = binary_start + binary_len;
if data.len() < binary_end {
return Err("Data truncated".to_string());
}
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
let sources = decode_from_slice(&data[sources_start..literals_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
let mut needs_recompilation = false;
let literals = match decode_from_slice::<Vec<BinaryValue>, _>(
&data[literals_start..rule_tree_start],
standard(),
) {
Ok((binary_literals, _)) => match binaries_to_values(binary_literals) {
Ok(values) => values,
Err(_e) => {
needs_recompilation = true;
Vec::new()
}
},
Err(_e) => {
needs_recompilation = true;
Vec::new()
}
};
let rule_tree = match decode_from_slice::<BinaryValue, _>(
&data[rule_tree_start..binary_len_start],
standard(),
) {
Ok((binary_tree, _)) => match binary_to_value(binary_tree) {
Ok(value) => value,
Err(_e) => {
needs_recompilation = true;
Value::new_object()
}
},
Err(_e) => {
needs_recompilation = true;
Value::new_object()
}
};
let mut program = match decode_from_slice::<Program, _>(
&data[binary_start..binary_end],
standard(),
) {
Ok((prog, _)) => prog,
Err(_e) => {
needs_recompilation = true;
Program::new()
}
};
program.entry_points = entry_points;
program.sources = sources;
program.literals = literals;
program.rule_tree = rule_tree;
program.rego_v0 = rego_v0;
program.needs_recompilation = needs_recompilation;
if !program.builtin_info_table.is_empty() {
if let Err(_e) = program.initialize_resolved_builtins() {
program.needs_recompilation = true;
}
}
if program.needs_recompilation {
Ok(DeserializationResult::Partial(program))
} else {
Ok(DeserializationResult::Complete(program))
}
}
v => Err(format!("Unsupported version {}", v)),
}
}
/// Check if data can be deserialized without actually deserializing
pub fn can_deserialize(data: &[u8]) -> Result<bool, String> {
if data.len() < 8 {
return Ok(false);
}
if data[0..4] != Self::MAGIC {
return Ok(false);
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
match version {
1..=3 => Ok(true),
_ => Ok(false),
}
}
/// Get file format information without deserializing
pub fn get_file_info(data: &[u8]) -> Result<(u32, usize), String> {
if data.len() < 9 {
return Err("Data too short for header".to_string());
}
if data[0..4] != Self::MAGIC {
return Err("Invalid file format".to_string());
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
match version {
1 => {
if data.len() < 25 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let binary_len_start = 17 + entry_points_len + sources_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
Ok((version, binary_len))
}
2 | 3 => {
if data.len() < 29 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let literals_len =
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let rule_tree_len =
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
let binary_len_start =
25 + entry_points_len + sources_len + literals_len + rule_tree_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
Ok((version, binary_len))
}
v => Err(format!("Unsupported version {}", v)),
}
}
}
+196
View File
@@ -0,0 +1,196 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use super::super::types::SourceFile;
use super::super::types::{BuiltinInfo, ProgramMetadata, RuleInfo, SpanInfo};
use super::Program;
use crate::rvm::instructions::InstructionData;
use crate::rvm::Instruction;
use crate::value::Value;
use indexmap::IndexMap;
impl Program {
/// Serialize to JSON format with complete program information and proper field names
pub fn serialize_json(&self) -> Result<String, String> {
let json_data = serde_json::json!({
"metadata": {
"compiler_version": self.metadata.compiler_version,
"compiled_at": self.metadata.compiled_at,
"source_info": self.metadata.source_info,
"optimization_level": self.metadata.optimization_level,
"rego_v0": self.rego_v0,
"needs_runtime_recursion_check": self.needs_runtime_recursion_check,
"needs_recompilation": self.needs_recompilation
},
"program_structure": {
"main_entry_point": self.main_entry_point,
"max_rule_window_size": self.max_rule_window_size,
"dispatch_window_size": self.dispatch_window_size,
},
"instructions": self.instructions,
"instruction_data": {
"loop_params": self.instruction_data.loop_params,
"builtin_call_params": self.instruction_data.builtin_call_params,
"function_call_params": self.instruction_data.function_call_params,
"object_create_params": self.instruction_data.object_create_params,
"array_create_params": self.instruction_data.array_create_params,
"set_create_params": self.instruction_data.set_create_params,
"virtual_data_document_lookup_params": self.instruction_data.virtual_data_document_lookup_params,
"chained_index_params": self.instruction_data.chained_index_params,
"comprehension_begin_params": self.instruction_data.comprehension_begin_params
},
"literals": self.literals,
"builtin_info_table": self.builtin_info_table,
"entry_points": self.entry_points,
"sources": self.sources,
"rule_infos": self.rule_infos,
"instruction_spans": self.instruction_spans,
"rule_tree": self.rule_tree
});
serde_json::to_string_pretty(&json_data)
.map_err(|e| format!("JSON serialization failed: {}", e))
}
/// Deserialize program from JSON format
pub fn deserialize_json(data: &str) -> Result<Program, String> {
let json_data: serde_json::Value =
serde_json::from_str(data).map_err(|e| format!("JSON parsing failed: {}", e))?;
let metadata = json_data
.get("metadata")
.ok_or("Missing metadata section")?;
let compiler_version = metadata
.get("compiler_version")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let compiled_at = metadata
.get("compiled_at")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let source_info = metadata
.get("source_info")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let optimization_level = metadata
.get("optimization_level")
.and_then(|v| v.as_u64())
.unwrap_or(0) as u8;
let rego_v0 = metadata
.get("rego_v0")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let needs_runtime_recursion_check = metadata
.get("needs_runtime_recursion_check")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let needs_recompilation = metadata
.get("needs_recompilation")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let program_structure = json_data
.get("program_structure")
.ok_or("Missing program_structure section")?;
let main_entry_point = program_structure
.get("main_entry_point")
.and_then(|v| v.as_u64())
.unwrap_or(0) as usize;
let max_rule_window_size = program_structure
.get("max_rule_window_size")
.and_then(|v| v.as_u64())
.unwrap_or(0) as usize;
let dispatch_window_size = program_structure
.get("dispatch_window_size")
.and_then(|v| v.as_u64())
.unwrap_or(0) as usize;
let instructions: Vec<Instruction> = serde_json::from_value(
json_data
.get("instructions")
.ok_or("Missing instructions section")?
.clone(),
)
.map_err(|e| format!("Failed to deserialize instructions: {}", e))?;
let instruction_data_json = json_data
.get("instruction_data")
.ok_or("Missing instruction_data section")?;
let instruction_data: InstructionData =
serde_json::from_value(instruction_data_json.clone())
.map_err(|e| format!("Failed to deserialize instruction_data: {}", e))?;
let literals: Vec<Value> = json_data
.get("literals")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let builtin_info_table: Vec<BuiltinInfo> = json_data
.get("builtin_info_table")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let entry_points: IndexMap<String, usize> = json_data
.get("entry_points")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let sources: Vec<SourceFile> = json_data
.get("sources")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let rule_infos: Vec<RuleInfo> = json_data
.get("rule_infos")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let instruction_spans: Vec<Option<SpanInfo>> = json_data
.get("instruction_spans")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default();
let rule_tree: Value = json_data
.get("rule_tree")
.map(|v| serde_json::from_value(v.clone()).unwrap_or_else(|_| Value::new_object()))
.unwrap_or_else(Value::new_object);
let mut program = Program {
instructions,
literals,
instruction_data,
builtin_info_table,
entry_points,
sources,
rule_infos,
instruction_spans,
main_entry_point,
max_rule_window_size,
dispatch_window_size,
metadata: ProgramMetadata {
compiler_version,
compiled_at,
source_info,
optimization_level,
},
rule_tree,
resolved_builtins: Vec::new(),
needs_runtime_recursion_check,
needs_recompilation,
rego_v0,
};
if !program.builtin_info_table.is_empty() {
let _ = program.initialize_resolved_builtins();
}
Ok(program)
}
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
pub(crate) mod binary;
mod json;
pub(crate) mod value;
use serde::{Deserialize, Serialize};
use super::Program;
/// Versioned program wrapper for serialization compatibility
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionedProgram {
/// Format version for compatibility checking
pub version: u32,
/// The actual program data
pub program: Program,
}
/// Result of program deserialization that explicitly indicates completeness
#[derive(Debug, Clone)]
pub enum DeserializationResult {
/// Full deserialization was successful - program is fully functional
Complete(Program),
/// Only artifact section was deserialized - extensible sections failed
/// The program contains entry_points and sources but requires recompilation
Partial(Program),
}
+294
View File
@@ -0,0 +1,294 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use core::str::FromStr;
use serde::de::{self, EnumAccess, VariantAccess, Visitor};
use serde::ser::{SerializeSeq, SerializeTuple};
use serde::{Deserialize, Serialize};
use crate::number::Number;
use crate::value::Value;
const VARIANT_NULL: u32 = 0;
const VARIANT_BOOL: u32 = 1;
const VARIANT_NUMBER_STRING: u32 = 2;
const VARIANT_STRING: u32 = 3;
const VARIANT_ARRAY: u32 = 4;
const VARIANT_SET: u32 = 5;
const VARIANT_OBJECT: u32 = 6;
const VARIANT_UNDEFINED: u32 = 7;
const VARIANT_NUMBER_I64: u32 = 8;
const VARIANT_NUMBER_U64: u32 = 9;
const VARIANT_NUMBER_F64: u32 = 10;
/// Wrapper type for zero-copy binary serialization of a `Value`.
/// Keeps references into the original data so collections and strings are not cloned.
pub(crate) struct BinaryValueRef<'a>(pub &'a Value);
impl<'a> Serialize for BinaryValueRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self.0 {
Value::Null => serializer.serialize_unit_variant("BinaryValue", VARIANT_NULL, "Null"),
Value::Bool(b) => {
serializer.serialize_newtype_variant("BinaryValue", VARIANT_BOOL, "Bool", &b)
}
Value::Number(n) => {
if let Some(value) = n.as_i64() {
serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_NUMBER_I64,
"NumberI64",
&value,
)
} else if let Some(value) = n.as_u64() {
serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_NUMBER_U64,
"NumberU64",
&value,
)
} else if let Some(value) = n.as_f64() {
serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_NUMBER_F64,
"NumberF64",
&value,
)
} else {
serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_NUMBER_STRING,
"Number",
&n.format_scientific(),
)
}
}
Value::String(s) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_STRING,
"String",
s.as_ref(),
),
Value::Array(items) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_ARRAY,
"Array",
&BinaryValueSlice(items.as_slice()),
),
Value::Set(items) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_SET,
"Set",
&BinarySetRef(items.as_ref()),
),
Value::Object(entries) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_OBJECT,
"Object",
&BinaryObjectRef(entries.as_ref()),
),
Value::Undefined => {
serializer.serialize_unit_variant("BinaryValue", VARIANT_UNDEFINED, "Undefined")
}
}
}
}
/// Slice wrapper allowing zero-copy serialization of value collections.
pub(crate) struct BinaryValueSlice<'a>(pub &'a [Value]);
impl<'a> Serialize for BinaryValueSlice<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for value in self.0 {
seq.serialize_element(&BinaryValueRef(value))?;
}
seq.end()
}
}
struct BinarySetRef<'a>(&'a BTreeSet<Value>);
impl<'a> Serialize for BinarySetRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for value in self.0.iter() {
seq.serialize_element(&BinaryValueRef(value))?;
}
seq.end()
}
}
struct BinaryObjectRef<'a>(&'a BTreeMap<Value, Value>);
impl<'a> Serialize for BinaryObjectRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
for (key, value) in self.0.iter() {
seq.serialize_element(&BinaryEntryRef(key, value))?;
}
seq.end()
}
}
struct BinaryEntryRef<'a>(&'a Value, &'a Value);
impl<'a> Serialize for BinaryEntryRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut tuple = serializer.serialize_tuple(2)?;
tuple.serialize_element(&BinaryValueRef(self.0))?;
tuple.serialize_element(&BinaryValueRef(self.1))?;
tuple.end()
}
}
/// Owned counterpart used during deserialization.
#[derive(Debug, Clone)]
pub(crate) struct BinaryValue(pub Value);
impl BinaryValue {
fn into_value(self) -> Value {
self.0
}
}
const BINARY_VARIANTS: &[&str] = &[
"Null",
"Bool",
"Number",
"String",
"Array",
"Set",
"Object",
"Undefined",
"NumberI64",
"NumberU64",
"NumberF64",
];
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
enum BinaryVariant {
Null,
Bool,
Number,
String,
Array,
Set,
Object,
Undefined,
NumberI64,
NumberU64,
NumberF64,
}
struct BinaryValueVisitor;
impl<'de> Visitor<'de> for BinaryValueVisitor {
type Value = BinaryValue;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a BinaryValue enum")
}
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
where
A: EnumAccess<'de>,
{
match data.variant()? {
(BinaryVariant::Null, variant) => {
variant.unit_variant()?;
Ok(BinaryValue(Value::Null))
}
(BinaryVariant::Bool, variant) => {
let value = variant.newtype_variant::<bool>()?;
Ok(BinaryValue(Value::from(value)))
}
(BinaryVariant::Number, variant) => {
let numeric = variant.newtype_variant::<&'de str>()?;
let number = Number::from_str(numeric).map_err(|_| {
de::Error::custom(format!("Invalid numeric string '{numeric}'"))
})?;
Ok(BinaryValue(Value::from(number)))
}
(BinaryVariant::NumberI64, variant) => {
let value = variant.newtype_variant::<i64>()?;
Ok(BinaryValue(Value::from(value)))
}
(BinaryVariant::NumberU64, variant) => {
let value = variant.newtype_variant::<u64>()?;
Ok(BinaryValue(Value::from(value)))
}
(BinaryVariant::NumberF64, variant) => {
let value = variant.newtype_variant::<f64>()?;
Ok(BinaryValue(Value::from(value)))
}
(BinaryVariant::String, variant) => {
let s = variant.newtype_variant::<&'de str>()?;
Ok(BinaryValue(Value::from(s)))
}
(BinaryVariant::Array, variant) => {
let items: Vec<BinaryValue> = variant.newtype_variant()?;
let values: Vec<Value> = items.into_iter().map(BinaryValue::into_value).collect();
Ok(BinaryValue(Value::from(values)))
}
(BinaryVariant::Set, variant) => {
let items: Vec<BinaryValue> = variant.newtype_variant()?;
let mut set = BTreeSet::new();
for item in items {
set.insert(item.into_value());
}
Ok(BinaryValue(Value::from(set)))
}
(BinaryVariant::Object, variant) => {
let entries: Vec<(BinaryValue, BinaryValue)> = variant.newtype_variant()?;
let mut map = BTreeMap::new();
for (key, value) in entries {
map.insert(key.into_value(), value.into_value());
}
Ok(BinaryValue(Value::from(map)))
}
(BinaryVariant::Undefined, variant) => {
variant.unit_variant()?;
Ok(BinaryValue(Value::Undefined))
}
}
}
}
impl<'de> Deserialize<'de> for BinaryValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_enum("BinaryValue", BINARY_VARIANTS, BinaryValueVisitor)
}
}
pub(crate) fn binaries_to_values(binaries: Vec<BinaryValue>) -> Result<Vec<Value>, String> {
Ok(binaries.into_iter().map(BinaryValue::into_value).collect())
}
pub(crate) fn binary_to_value(binary: BinaryValue) -> Result<Value, String> {
Ok(binary.into_value())
}
+182
View File
@@ -0,0 +1,182 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
/// Builtin function information stored in program's builtin info table
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuiltinInfo {
/// Builtin function name
pub name: String,
/// Exact number of arguments required
pub num_args: u16,
}
/// Span information for debugging and error reporting
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpanInfo {
/// Index into the source table
pub source_index: usize,
/// Line number (1-based)
pub line: usize,
/// Column number (1-based)
pub column: usize,
/// Length of the span
pub length: usize,
}
impl SpanInfo {
pub fn new(source_index: usize, line: usize, column: usize, length: usize) -> Self {
Self {
source_index,
line,
column,
length,
}
}
/// Create SpanInfo from lexer Span with source table lookup
pub fn from_lexer_span(span: &crate::lexer::Span, source_index: usize) -> Self {
Self {
source_index,
line: span.line as usize,
column: span.col as usize,
length: span.text().len(),
}
}
/// Get source information using the program's source table
pub fn get_source<'a>(&self, source_table: &'a [SourceFile]) -> Option<&'a str> {
source_table
.get(self.source_index)
.map(|s| s.content.as_str())
}
/// Get source name using the program's source table
pub fn get_source_name<'a>(&self, source_table: &'a [SourceFile]) -> Option<&'a str> {
source_table.get(self.source_index).map(|s| s.name.as_str())
}
}
/// Rule type enumeration for different kinds of rules (complete, partial set, partial object)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
pub enum RuleType {
Complete,
PartialSet,
PartialObject,
}
/// Information about function rules
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionInfo {
/// Parameter names in order
pub param_names: Vec<String>,
/// Number of parameters
pub num_params: u32,
}
/// Rule metadata for debugging and introspection
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleInfo {
/// Rule name (e.g., "data.package.rule_name")
pub name: String,
/// Rule type
pub rule_type: RuleType,
/// Definitions
pub definitions: crate::Rc<Vec<Vec<u32>>>,
/// Function-specific information (only present for function rules)
pub function_info: Option<FunctionInfo>,
/// Index into the program's literal table for default value (only for Complete rules)
pub default_literal_index: Option<u16>,
/// Register allocated for this rule's result accumulation
pub result_reg: u8,
/// Number of registers used by this rule (for register windowing)
pub num_registers: u8,
/// Optional destructuring block entry point per definition
/// Index: definition_index → Some(entry_point) | None
pub destructuring_blocks: Vec<Option<u32>>,
}
impl RuleInfo {
pub fn new(
name: String,
rule_type: RuleType,
definitions: crate::Rc<Vec<Vec<u32>>>,
result_reg: u8,
num_registers: u8,
) -> Self {
let num_definitions = definitions.len();
Self {
name,
rule_type,
definitions,
function_info: None,
default_literal_index: None,
result_reg,
num_registers,
destructuring_blocks: alloc::vec![None; num_definitions],
}
}
/// Create a new function rule with parameter information
pub fn new_function(
name: String,
rule_type: RuleType,
definitions: crate::Rc<Vec<Vec<u32>>>,
param_names: Vec<String>,
result_reg: u8,
num_registers: u8,
) -> Self {
let num_params = param_names.len() as u32;
let num_definitions = definitions.len();
Self {
name,
rule_type,
definitions,
function_info: Some(FunctionInfo {
param_names,
num_params,
}),
default_literal_index: None,
result_reg,
num_registers,
destructuring_blocks: alloc::vec![None; num_definitions],
}
}
/// Set the default literal index for this rule
pub fn set_default_literal_index(&mut self, default_literal_index: u16) {
self.default_literal_index = Some(default_literal_index);
}
}
/// Source file information containing filename and contents
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceFile {
/// Source file identifier/path
pub name: String,
/// The actual source code content
pub content: String,
}
impl SourceFile {
pub fn new(name: String, content: String) -> Self {
Self { name, content }
}
}
/// Program compilation metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgramMetadata {
/// Compiler version that generated this program
pub compiler_version: String,
/// Compilation timestamp
pub compiled_at: String,
/// Source policy information
pub source_info: String,
/// Optimization level used
pub optimization_level: u8,
}