diff --git a/core/engine/src/model/policy.rs b/core/engine/src/model/policy.rs index 18a1199a..dbe92822 100644 --- a/core/engine/src/model/policy.rs +++ b/core/engine/src/model/policy.rs @@ -55,7 +55,7 @@ impl PolicyRuleCondition { pub fn to_vec(&self) -> Vec<&str> { match self { PolicyRuleCondition::Simple { expression } => once(expression.as_str()) - .chain(self.to_vec().into_iter()) + // .chain(self.to_vec().into_iter()) .collect(), PolicyRuleCondition::All { items } | PolicyRuleCondition::Any { items } diff --git a/core/engine/src/policy/compiler.rs b/core/engine/src/policy/compiler.rs index 640136b0..b4b90529 100644 --- a/core/engine/src/policy/compiler.rs +++ b/core/engine/src/policy/compiler.rs @@ -1,19 +1,22 @@ use crate::model::{PolicyBundle, PolicyDecisionTable, PolicyDocument, PolicyRule}; +use ahash::{HashMap, HashMapExt}; use bumpalo::Bump; +use petgraph::algo::toposort; +use petgraph::graph::DiGraph; use std::sync::Arc; -use zen_expression::intellisense::{DependencyProvider, DependencyProviderResponse, IntelliSense}; +use zen_expression::intellisense::{DependencyProviderResponse, IntelliSense}; use zen_expression::lexer::Lexer; -use zen_expression::parser::Parser; -use zen_expression::ExpressionKind; pub struct PolicyCompiler {} // Ultimately, backed by an array of execution steps needed to be performed +#[derive(Debug, Clone)] pub enum PolicyExecutionStep { DecisionTable(Arc), Rule(Arc), } +#[derive(Debug, Clone)] struct PolicyCompilerStep { execution_step: PolicyExecutionStep, dependency_provider: DependencyProviderResponse, @@ -22,15 +25,36 @@ struct PolicyCompilerStep { // Computes the execution order for Decision Tables and Rules impl PolicyCompiler { fn compile_bundle(policy_bundle: PolicyBundle) { - let mut arena = Bump::new(); - let is = IntelliSense::new(); + let steps = policy_bundle + .documents + .iter() + .flat_map(|d| match d.as_ref() { + PolicyDocument::DecisionTable { content } => vec![PolicyCompilerStep { + dependency_provider: Default::default(), + execution_step: PolicyExecutionStep::DecisionTable(content.clone()), + }], + PolicyDocument::RuleSet { content } => content + .rules + .iter() + .map(|r| Self::compile_rule(r.clone())) + .collect::>(), + }) + .collect::>(); - for document in policy_bundle.documents.iter() { - match document.as_ref() { - PolicyDocument::DecisionTable { content } => {} - PolicyDocument::RuleSet { content } => {} - } - } + println!("{:#?}", steps); + + let sorted_steps = Self::sort_steps_by_dependencies(steps); + println!("{:#?}", sorted_steps); + + let sorted_expressions = sorted_steps + .iter() + .map(|s| match &s.execution_step { + PolicyExecutionStep::DecisionTable(_) => String::from("decisionTable"), + PolicyExecutionStep::Rule(r) => r.outcome.clone(), + }) + .collect::>(); + + println!("{:#?}", sorted_expressions); } fn compile_rule(rule: Arc) -> PolicyCompilerStep { @@ -39,25 +63,79 @@ impl PolicyCompiler { let outcome_str = rule.outcome.as_str(); is.dependencies(outcome_str).unwrap_or_default() }; - - if let Some(condition) = &rule.conditions { - - } - - rule.conditions.iter().fold(outcome, |acc, c| { - let rule_str = - }) + + let Some(condition) = &rule.conditions else { + return PolicyCompilerStep { + execution_step: PolicyExecutionStep::Rule(rule.clone()), + dependency_provider: outcome, + }; + }; + + let complete_outcome = condition.to_vec().iter().fold(outcome, |acc, c| { + let condition_provider = is.dependencies(c); + acc.merge(condition_provider.unwrap_or_default()) + }); PolicyCompilerStep { - execution_step: PolicyExecutionStep::Rule(rule), - dependency_provider: outcome_deps.unwrap_or_default(), + execution_step: PolicyExecutionStep::Rule(rule.clone()), + dependency_provider: complete_outcome, } } - fn compile_decision_table( - decision_table: Arc, - c: &mut InternalCompiler, - ) -> PolicyCompilerStep { + // fn compile_decision_table( + // decision_table: Arc, + // c: &mut InternalCompiler, + // ) -> PolicyCompilerStep { + // } + + fn sort_steps_by_dependencies(steps: Vec) -> Vec { + // Create a directed graph + let mut graph = DiGraph::::new(); + let mut nodes = Vec::new(); + let mut provides_map: HashMap<_, _> = HashMap::new(); + + // Create nodes for each step + for (idx, step) in steps.iter().enumerate() { + let node_idx = graph.add_node(idx); + nodes.push(node_idx); + + // Map provided values to step indices + for provided in &step.dependency_provider.provides { + provides_map.insert(provided.clone(), idx); + } + } + + // Add edges based on dependencies + for (idx, step) in steps.iter().enumerate() { + for dependency in &step.dependency_provider.dependencies { + if let Some(&provider_idx) = provides_map.get(dependency) { + if provider_idx != idx { + // Avoid self-dependencies + graph.add_edge(nodes[provider_idx], nodes[idx], ()); + } + } + } + } + + // Perform topological sort + match toposort(&graph, None) { + Ok(sorted_indices) => { + // Reconstruct the sorted steps + sorted_indices + .into_iter() + .map(|node_idx| { + let step_idx = graph[node_idx]; + steps[step_idx].clone() + }) + .collect() + } + Err(_cycle) => { + // Handle cycles in the dependency graph + // For simplicity, return the original order + eprintln!("Warning: Dependency cycle detected in policy steps"); + steps + } + } } } @@ -65,3 +143,17 @@ struct InternalCompiler<'arena> { arena: &'arena mut Bump, lexer: Lexer<'arena>, } + +#[cfg(test)] +mod test { + use crate::model::PolicyBundle; + use crate::policy::compiler::PolicyCompiler; + + #[test] + fn initial() { + let policy_bundle: PolicyBundle = + serde_json::from_str(include_str!("policy.json")).unwrap(); + + let s = PolicyCompiler::compile_bundle(policy_bundle); + } +} diff --git a/core/engine/src/policy/policy.json b/core/engine/src/policy/policy.json index ba458236..3aea7024 100644 --- a/core/engine/src/policy/policy.json +++ b/core/engine/src/policy/policy.json @@ -1,9 +1,9 @@ { - "files": [ + "documents": [ { - "type": "policy", + "type": "ruleSet", "content": { - "expressions": [ + "rules": [ { "name": "Eligibility Determination", "outcome": "customer.eligible = true", @@ -121,6 +121,14 @@ "type": "simple", "expression": "application.type == 'LOAN'" } + }, + { + "name": "Final Term Calculation", + "outcome": "some_new_field = 123", + "conditions": { + "type": "simple", + "expression": "customer.finalTerm == 0" + } } ] } diff --git a/core/expression/src/intellisense/dependency/mod.rs b/core/expression/src/intellisense/dependency/mod.rs index 34a4bc53..cc211efa 100644 --- a/core/expression/src/intellisense/dependency/mod.rs +++ b/core/expression/src/intellisense/dependency/mod.rs @@ -8,7 +8,7 @@ use bumpalo::Bump; use serde::Serialize; use std::rc::Rc; -#[derive(Debug, Serialize, Default)] +#[derive(Debug, Serialize, Default, Clone)] #[serde(rename_all = "camelCase")] pub struct DependencyProviderResponse { pub provides: HashSet>, @@ -78,6 +78,15 @@ impl<'arena> DependencyProvider<'arena> { DependencyProviderResponse::from(provider) } + fn duplicate(&self) -> DependencyProvider<'arena> { + Self { + provides: Default::default(), + dependencies: Default::default(), + resolution: DependencyResolution::Static, + arena: self.arena, + } + } + fn set_dynamic(&mut self) { self.resolution = DependencyResolution::Dynamic; } @@ -95,8 +104,17 @@ impl<'arena> DependencyProvider<'arena> { continue; }; + // Will contain only dependencies, which need to be filtered for existing provides + let mut local_provider = self.duplicate(); + local_provider.determine(v, scope); + self.dependencies.extend( + local_provider + .dependencies + .into_iter() + .filter(|d| !self.provides.contains(d)), + ); + self.provides.insert(Rc::from(*key)); - self.determine(v, scope); } D::None diff --git a/core/expression/src/parser/parser.rs b/core/expression/src/parser/parser.rs index 7b151782..7dda253e 100644 --- a/core/expression/src/parser/parser.rs +++ b/core/expression/src/parser/parser.rs @@ -700,7 +700,9 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { let (identifier_with_postfix, combination) = self.with_postfix(identifier_node, &expression_parser); if let Some(&TokenKind::Operator(Operator::Assign)) = self.current_kind() { - if combination != PostfixCombination::Property { + if combination != PostfixCombination::Property + && combination != PostfixCombination::Inherit + { return self.error_with_node( AstNodeError::Custom { span: identifier_with_postfix.span().unwrap_or_default(), @@ -945,7 +947,9 @@ impl<'arena, 'token_ref, Flavor> Parser<'arena, 'token_ref, Flavor> { }); self.next(); let (key_node, combination) = self.with_postfix(identifier_node, expression_parser); - if combination != PostfixCombination::Property { + if combination != PostfixCombination::Property + && combination != PostfixCombination::Inherit + { return self.error_with_node( AstNodeError::Custom { span: key_node.span().unwrap_or_default(),