// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. use crate::ast::*; use crate::builtins::*; use crate::lexer::*; use crate::*; use alloc::collections::BTreeMap; use anyhow::{bail, Result}; pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result { let mut comps: Vec<&str> = vec![]; let mut expr = Some(refr); while expr.is_some() { match expr { Some(Expr::RefDot { refr, field, .. }) => { comps.push(field.0.text()); expr = Some(refr); } Some(Expr::RefBrack { refr, index, .. }) => { if let Expr::String { span: s, .. } = index.as_ref() { comps.push(s.text()); } expr = Some(refr); } Some(Expr::Var { span: v, .. }) => { comps.push(v.text()); expr = None; } _ => bail!("internal error: not a simple ref {expr:?}"), } } if let Some(d) = document { comps.push(d); }; comps.reverse(); Ok(comps.join(".")) } pub type FunctionTable = BTreeMap>, u8, Ref)>; fn get_extra_arg_impl( expr: &Expr, module: Option<&str>, functions: &FunctionTable, ) -> Result>> { if let Expr::Call { fcn, params, .. } = expr { let full_path = get_path_string(fcn, module)?; let n_args = if let Some((_, n_args, _)) = functions.get(&full_path) { *n_args } else { let path = get_path_string(fcn, None)?; if let Some((_, n_args, _)) = functions.get(&path) { *n_args } else if let Some((_, n_args)) = BUILTINS.get(path.as_str()) { *n_args } else { return Ok(None); } }; if (n_args as usize) + 1 == params.len() { return Ok(params.last().cloned()); } } Ok(None) } pub fn get_extra_arg( expr: &Expr, module: Option<&str>, functions: &FunctionTable, ) -> Option> { get_extra_arg_impl(expr, module, functions).unwrap_or_default() } pub fn gather_functions(modules: &[Ref]) -> Result { let mut table = FunctionTable::new(); for module in modules { let module_path = get_path_string(&module.package.refr, Some("data"))?; for rule in &module.policy { if let Rule::Spec { span, head: RuleHead::Func { refr, args, .. }, .. } = rule.as_ref() { let full_path = get_path_string(refr, Some(module_path.as_str()))?; if let Some((functions, arity, _)) = table.get_mut(&full_path) { if args.len() as u8 != *arity { bail!(span.error( format!("{full_path} was previously defined with {arity} arguments.") .as_str() )); } functions.push(rule.clone()); } else { table.insert( full_path, (vec![rule.clone()], args.len() as u8, module.clone()), ); } } } } Ok(table) } pub fn get_root_var(mut expr: &Expr) -> Result { let empty = expr.span().source_str().clone_empty(); loop { match expr { Expr::Var { span: v, .. } => return Ok(v.source_str()), Expr::RefDot { refr, .. } | Expr::RefBrack { refr, .. } => expr = refr, _ => return Ok(empty), } } }