mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat!: Rego -> RVM Compiler and extensive testsuite (#506)
# RVM compiler test cases Coverage: - arithmetic - arrays - chained lookups - comparisons - comprehensions - default rules - destructuring - function rules - loops/quantifiers - multiple entrypoints - objects/sets - variables - negative/edge scenarios such as data/rule conflicts - virtual data lookups - etc # Modify interpreter and compiled policy for RVM Compilation - Interpreter::eval_default_rule_for_compiler: evaluates a named default rule in isolation - allows compiler to emit a constant value instead of instructions for the default value # feat: Rego Compiler Scaffolding - Introduce the rego::compiler module surface and entry point wiring - Add the core compiler concepts: - register allocator - scope tracking - literal/builtin tables - rule worklists - instruction emit helpers - compiler-specific error types - context structs for rules, comprehensions, and loops to support later lowering passes. # feat: Compile Rules/Queries - add compiler::compile_from_policy workflow plus rule worklist, entry-point wiring, and recursion checks - implement query lowering: - scheduling-aware statement ordering - loop hoisting - “every/some” semantics - context yields - literal assertions - finalize Program construction # feat: Expression Lowering - add compile_rego_expr and helpers to translate every AST expression into RVM instructions, - interop with binding plans, comprehensions, and membership checks. - implement collection literal builders (ArrayCreate, SetCreate, ObjectCreate) - dedupe literal keys and handle mixed literal/dynamic fields via instruction data blocks. - operations: - arithmetic/boolean/bin operators - membership - unary minus - set unions/intersections - etc - user-defined and builtin function calls - reference handling - analyse chained refs - distinguishe data/input/local roots - perform rule dispatch or virtual document lookups - emits optimized Index/ChainedIndex instructions. # feat: Comprehensions & Loops - shared comprehension emitter - wraps array/set/object comprehensions with ComprehensionBegin/End - context management - loop lowering utilities - read hoisting metadata - emit LoopStart/LoopNext - some in lowering - every quantifiers - index iteration - propagate binding plans into stored registers so downstream statements see bound variables. # feat: Destructuring Lowering - destructuring planner integration - assignment/parameter/loop bindings use hoisted plans instead of re-walking ASTs. - handle :=, =, wildcard matches, and equality - evaluate RHS - applying destructuring plans - emit assert condition as needed - support nested array/object destructuring, dynamic keys, and some ... in forms # test: Shared Testing + RVM Suites - move YAML test helpers into test_utils.rs and re-export via common.rs for use by interpreter and vm test suites - comprehensive compiler test suite - compiles policies with the new Rego→RVM compiler - runs them through RegoVM - compares against interpreter behavior - supports multiple entry points - provides assembly listings - filterable YAML suites. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
688e6128d4
commit
a3a20a1235
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
//! Shared helpers for YAML-driven integration tests.
|
||||
|
||||
use crate::Value;
|
||||
use alloc::{vec, vec::Vec};
|
||||
use anyhow::{bail, Result};
|
||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Support single or multiple values inside YAML fixtures.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum ValueOrVec {
|
||||
Single(Value),
|
||||
Many(Vec<Value>),
|
||||
}
|
||||
|
||||
impl Serialize for ValueOrVec {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
ValueOrVec::Single(value) => value.serialize(serializer),
|
||||
ValueOrVec::Many(v) => {
|
||||
let mut map = serializer.serialize_map(Some(1))?;
|
||||
map.serialize_entry("many!", v)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ValueOrVec {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = Value::deserialize(deserializer)?;
|
||||
|
||||
match &value["many!"] {
|
||||
Value::Array(arr) => Ok(ValueOrVec::Many(arr.to_vec())),
|
||||
_ => Ok(ValueOrVec::Single(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert any YAML-described value into an engine `Value`, handling helper encodings.
|
||||
pub fn process_value(v: &Value) -> Result<Value> {
|
||||
match v {
|
||||
Value::String(s) if s.as_ref() == "#undefined" => Ok(Value::Undefined),
|
||||
Value::Object(ref fields) if fields.len() == 1 && matches!(&v["set!"], Value::Array(_)) => {
|
||||
let mut set_value = Value::new_set();
|
||||
let set = set_value.as_set_mut()?;
|
||||
for item in v["set!"].as_array()? {
|
||||
set.insert(process_value(item)?);
|
||||
}
|
||||
Ok(set_value)
|
||||
}
|
||||
Value::Object(fields) if fields.len() == 1 && matches!(&v["object!"], Value::Array(_)) => {
|
||||
let mut object_value = Value::new_object();
|
||||
let object = object_value.as_object_mut()?;
|
||||
for item in v["object!"].as_array()? {
|
||||
let key = process_value(&item["key"])?;
|
||||
let value = process_value(&item["value"])?;
|
||||
object.insert(key, value);
|
||||
}
|
||||
Ok(object_value)
|
||||
}
|
||||
Value::Array(items) => {
|
||||
let mut array_value = Value::new_array();
|
||||
let array = array_value.as_array_mut()?;
|
||||
for item in items.iter() {
|
||||
array.push(process_value(item)?);
|
||||
}
|
||||
Ok(array_value)
|
||||
}
|
||||
Value::Object(fields) => {
|
||||
let mut object_value = Value::new_object();
|
||||
let object = object_value.as_object_mut()?;
|
||||
for (key, value) in fields.iter() {
|
||||
object.insert(process_value(key)?, process_value(value)?);
|
||||
}
|
||||
Ok(object_value)
|
||||
}
|
||||
Value::Set(_) => bail!("unexpected set in value read from json/yaml"),
|
||||
_ => Ok(v.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Diff-friendly equality helper used by multiple YAML suites.
|
||||
pub fn match_values(computed: &Value, expected: &Value) -> Result<()> {
|
||||
if computed != expected {
|
||||
let expected_yaml = serde_yaml::to_string(expected)?;
|
||||
let computed_yaml = serde_yaml::to_string(computed)?;
|
||||
bail!("expected:\n{}computed:\n{}", expected_yaml, computed_yaml);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compare two result sets after normalizing special encodings.
|
||||
pub fn check_output(computed_results: &[Value], expected_results: &[Value]) -> Result<()> {
|
||||
if computed_results.len() != expected_results.len() {
|
||||
bail!(
|
||||
"the number of computed results ({}) and expected results ({}) is not equal",
|
||||
computed_results.len(),
|
||||
expected_results.len()
|
||||
);
|
||||
}
|
||||
|
||||
for (n, expected_result) in expected_results.iter().enumerate() {
|
||||
let expected = process_value(expected_result)?;
|
||||
if let Some(computed_result) = computed_results.get(n) {
|
||||
match_values(computed_result, &expected)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Normalise helper enum to plain vectors for downstream assertions.
|
||||
pub fn value_or_vec_to_vec(value_or_vec: ValueOrVec) -> Vec<Value> {
|
||||
match value_or_vec {
|
||||
ValueOrVec::Single(single_result) => vec![single_result],
|
||||
ValueOrVec::Many(many_result) => many_result,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user