use alloc::format;
use alloc::string::{String, ToString as _};
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;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Program {
pub instructions: Vec<Instruction>,
#[serde(skip, default = "Vec::new")]
pub literals: Vec<Value>,
pub instruction_data: InstructionData,
pub builtin_info_table: Vec<BuiltinInfo>,
#[serde(skip, default = "IndexMap::new")]
pub entry_points: IndexMap<String, usize>,
#[serde(skip, default = "Vec::new")]
pub sources: Vec<SourceFile>,
pub rule_infos: Vec<RuleInfo>,
pub instruction_spans: Vec<Option<SpanInfo>>,
pub main_entry_point: u32,
pub max_rule_window_size: u8,
pub dispatch_window_size: u8,
pub metadata: ProgramMetadata,
#[serde(skip, default = "Value::new_object")]
pub rule_tree: Value,
#[serde(skip)]
pub resolved_builtins: Vec<BuiltinFcn>,
pub needs_runtime_recursion_check: bool,
#[serde(default)]
pub needs_recompilation: bool,
#[serde(skip, default)]
pub rego_v0: bool,
}
impl Program {
pub const SERIALIZATION_VERSION: u32 = 3;
pub const MAGIC: [u8; 4] = *b"REGO";
pub const MAX_INSTRUCTIONS: usize = 65_535; pub const MAX_LITERALS: usize = 65_535; pub const MAX_RULES: usize = 4_000;
pub const MAX_ENTRY_POINTS: usize = 1_000;
pub const MAX_SOURCES: usize = 256;
pub const MAX_BUILTINS: usize = 512;
pub const MAX_PATH_DEPTH: usize = 32;
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, }
}
pub fn validate_limits(&self) -> Result<(), String> {
if self.instructions.len() > Self::MAX_INSTRUCTIONS {
return Err(format!(
"Program exceeds max instructions ({} > {})",
self.instructions.len(),
Self::MAX_INSTRUCTIONS
));
}
if self.literals.len() > Self::MAX_LITERALS {
return Err(format!(
"Program exceeds max literals ({} > {})",
self.literals.len(),
Self::MAX_LITERALS
));
}
if self.rule_infos.len() > Self::MAX_RULES {
return Err(format!(
"Program exceeds max rules ({} > {})",
self.rule_infos.len(),
Self::MAX_RULES
));
}
if self.entry_points.len() > Self::MAX_ENTRY_POINTS {
return Err(format!(
"Program exceeds max entry points ({} > {})",
self.entry_points.len(),
Self::MAX_ENTRY_POINTS
));
}
if self.sources.len() > Self::MAX_SOURCES {
return Err(format!(
"Program exceeds max sources ({} > {})",
self.sources.len(),
Self::MAX_SOURCES
));
}
if self.builtin_info_table.len() > Self::MAX_BUILTINS {
return Err(format!(
"Program exceeds max builtins ({} > {})",
self.builtin_info_table.len(),
Self::MAX_BUILTINS
));
}
for params in &self.instruction_data.loop_params {
let body_end = core::cmp::max(params.body_start, params.loop_end);
if usize::from(body_end) > Self::MAX_INSTRUCTIONS {
return Err("Loop offsets exceed supported instruction range".to_string());
}
}
for params in &self.instruction_data.comprehension_begin_params {
let body_end = core::cmp::max(params.body_start, params.comprehension_end);
if usize::from(body_end) > Self::MAX_INSTRUCTIONS {
return Err("Comprehension offsets exceed supported instruction range".to_string());
}
}
for instr in &self.instructions {
if let &crate::rvm::Instruction::LoopNext {
body_start,
loop_end,
} = instr
{
let body_end = core::cmp::max(body_start, loop_end);
if usize::from(body_end) > Self::MAX_INSTRUCTIONS {
return Err("LoopNext offsets exceed supported instruction range".to_string());
}
}
}
Ok(())
}
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
}
pub fn add_loop_params(&mut self, params: crate::rvm::instructions::LoopStartParams) -> u16 {
self.instruction_data.add_loop_params(params)
}
pub fn add_comprehension_begin_params(
&mut self,
params: crate::rvm::instructions::ComprehensionBeginParams,
) -> u16 {
self.instruction_data.add_comprehension_begin_params(params)
}
pub fn add_builtin_call_params(
&mut self,
params: crate::rvm::instructions::BuiltinCallParams,
) -> u16 {
self.instruction_data.add_builtin_call_params(params)
}
pub fn add_function_call_params(
&mut self,
params: crate::rvm::instructions::FunctionCallParams,
) -> u16 {
self.instruction_data.add_function_call_params(params)
}
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);
u16::try_from(index).unwrap_or(u16::MAX)
}
pub fn get_builtin_info(&self, index: u16) -> Option<&BuiltinInfo> {
self.builtin_info_table.get(usize::from(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);
}
}
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);
}
}
pub fn display_instruction_with_params(&self, instruction: &Instruction) -> String {
instruction.display_with_params(&self.instruction_data)
}
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
}
pub fn get_source_file(&self, index: usize) -> Option<&SourceFile> {
self.sources.get(index)
}
pub fn get_source(&self, index: usize) -> Option<&str> {
self.sources.get(index).map(|s| s.content.as_str())
}
pub fn get_source_name(&self, index: usize) -> Option<&str> {
self.sources.get(index).map(|s| s.name.as_str())
}
pub fn get_rule_info(&self, rule_index: usize) -> Option<&RuleInfo> {
self.rule_infos.get(rule_index)
}
pub fn get_instruction_span(&self, instruction_index: usize) -> Option<&SpanInfo> {
self.instruction_spans
.get(instruction_index)
.and_then(|span| span.as_ref())
}
pub fn add_instruction(&mut self, instruction: Instruction, span: Option<SpanInfo>) {
self.instructions.push(instruction);
self.instruction_spans.push(span);
}
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
}
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(())
}
pub fn get_resolved_builtin(&self, index: u16) -> Option<&BuiltinFcn> {
self.resolved_builtins.get(usize::from(index))
}
pub fn has_resolved_builtins(&self) -> bool {
!self.resolved_builtins.is_empty()
}
pub fn add_entry_point(&mut self, path: String, rule_index: usize) {
self.entry_points.insert(path, rule_index);
}
pub fn get_entry_point(&self, path: &str) -> Option<usize> {
self.entry_points.get(path).copied()
}
pub const fn get_entry_points(&self) -> &IndexMap<String, usize> {
&self.entry_points
}
pub const fn needs_recompilation(&self) -> bool {
self.needs_recompilation
}
pub const fn set_needs_recompilation(&mut self, needs_recompilation: bool) {
self.needs_recompilation = needs_recompilation;
}
pub const fn is_fully_functional(&self) -> bool {
!self.needs_recompilation
}
}
impl Default for Program {
fn default() -> Self {
Self::new()
}
}