use aorist_util::{get_raw_objects_of_type, read_file}; use codegen::Scope; use serde_yaml::Value; use std::collections::{HashMap, HashSet}; use std::env; use std::fs; use std::path::Path; type ConstraintTuple = (String, String, Option, Option); fn process_constraints_py(raw_objects: &Vec>) { let constraints = get_raw_objects_of_type(raw_objects, "Constraint".into()); let mut scope_py = Scope::new(); let fun = scope_py .new_fn("constraints_module") .vis("pub") .ret("PyResult<()>") .arg("_py", "Python") .arg("m", "&PyModule"); for attribute in constraints { let name = attribute.get("name").unwrap().as_str().unwrap().to_string(); let export = format!("m.add_class::<{}>().unwrap();", name,); fun.line(&export); } let out_dir = env::var_os("OUT_DIR").unwrap(); let dest_path_py = Path::new(&out_dir).join("python.rs"); fun.line("Ok(())"); fs::write(&dest_path_py, scope_py.to_string()).unwrap(); } fn get_constraint_dependencies( constraints: &Vec>, ) -> HashMap> { let mut dependencies: HashMap> = HashMap::new(); for constraint in constraints { let name = constraint .get("name") .unwrap() .as_str() .unwrap() .to_string(); let root = constraint .get("root") .unwrap() .as_str() .unwrap() .to_string(); let title = match constraint.get("title") { Some(x) => Some(x.as_str().unwrap().to_string()), None => None, }; let body = match constraint.get("body") { Some(x) => Some(x.as_str().unwrap().to_string()), None => None, }; let required = match constraint.get("requires") { None => Vec::new(), Some(required) => required .clone() .as_sequence() .unwrap() .iter() .map(|x| x.as_str().unwrap().to_string()) .collect::>(), }; dependencies.insert((name, root, title, body), required); } let constraint_names: HashSet = dependencies.keys().map(|x| x.0.clone()).collect(); for dep in dependencies.values() { for elem in dep { if !constraint_names.contains(elem) { panic!("Cannot find definition for required constraint {}", elem); } } } dependencies } fn compute_topological_sort( dependencies: &HashMap>, ) -> Vec { let mut g: HashMap> = dependencies .iter() .map(|(k, v)| { ( k.clone(), v.clone().into_iter().collect::>(), ) }) .collect(); let mut leaf_name = g .iter() .filter(|(_, v)| v.len() == 0) .map(|(k, _)| k) .next(); let mut order: Vec<_> = Vec::new(); while let Some(val) = leaf_name { let key = val.clone(); println!("key: {}, {}", key.0, key.1); g.remove(&key); for (k, x) in g.iter_mut() { println!( "node: {}, dependencies: {}", k.0, x.clone().into_iter().collect::>().join(", ") ); x.remove(&key.0); } order.push(key); leaf_name = g .iter() .filter(|(_, v)| v.len() == 0) .map(|(k, _)| k) .next(); if g.len() == 0 { break; } } if g.len() > 0 { panic!("Cycles in constraint dependencies are not allowed!"); } order } fn process_constraints(raw_objects: &Vec>) { let constraints = get_raw_objects_of_type(raw_objects, "Constraint".into()); let mut scope = Scope::new(); scope.import("uuid", "Uuid"); for constraint in &constraints { scope.import( "aorist_core", constraint.get("root").unwrap().as_str().unwrap(), ); } let dependencies = get_constraint_dependencies(&constraints); let order = compute_topological_sort(&dependencies); let program_required = constraints .iter() .map(|x| { ( x.get("name").unwrap().as_str().unwrap().to_string(), match x.get("requiresProgram") { Some(Value::Bool(ref val)) => *val, None => false, _ => panic!("requiresProgram needs to be a bool"), }, ) }) .collect::>(); let attach_if = constraints .iter() .map(|x| { ( x.get("name").unwrap().as_str().unwrap().to_string(), match x.get("attachIf") { Some(Value::String(ref val)) => Some(val.to_string()), None => None, _ => panic!("attachIf needs to be a string containing a Rust closure"), }, ) }) .collect::>>(); let constraint_closures = constraints .iter() .map(|x| { ( x.get("name").unwrap().as_str().unwrap().to_string(), match x.get("requiredConstraintsClosure") { Some(Value::String(ref val)) => Some(val.to_string()), None => None, _ => panic!( "requiredConstraintsClosure needs to be a string containing a Rust closure" ), }, ) }) .collect::>>(); let mut satisfiable = Vec::new(); for (name, root, title, body) in &order { let required = dependencies .get(&(name.clone(), root.clone(), title.clone(), body.clone())) .unwrap(); let requires_program = program_required.get(name).unwrap(); if *requires_program { satisfiable.push(name.clone()); } let should_add = attach_if.get(name).unwrap(); let get_required = constraint_closures.get(name).unwrap(); let formatted_title = match title { Some(x) => format!("Some(\"{}\".to_string())", x), None => "None".to_string(), }; let formatted_body = match body { Some(x) => format!("Some(\"{}\".to_string())", x), None => "None".to_string(), }; let should_add = match should_add { None => "|_, _| true".to_string(), Some(x) => x.to_string(), }; let get_required = match get_required { None => "|_, _| Vec::new()".to_string(), Some(x) => x.to_string(), }; let define = match required.len() { 0 => format!( "define_constraint!({}, {}, Satisfy{}, {}, Constraint, {}, {}, {}, {});", name, requires_program, name, root, formatted_title, formatted_body, should_add, get_required ), _ => format!( "define_constraint!({}, {}, Satisfy{}, {}, Constraint, {}, {}, {}, {}, {});", name, requires_program, name, root, formatted_title, formatted_body, should_add, get_required, required.join(", ") ), }; scope.raw(&define); } let out_dir = env::var_os("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("constraints.rs"); scope.raw(&format!( "register_constraint_new!(AoristConstraint, 'a, {});", order .iter() .map(|x| x.0.clone()) .collect::>() .join("\n, ") )); fs::write(&dest_path, scope.to_string()).unwrap(); } fn main() { let raw_objects = read_file("constraints.yaml"); process_constraints(&raw_objects); process_constraints_py(&raw_objects); }