use std::collections::HashMap;
use crate::engine::grammar::SPG;
use crate::engine::grammar::utils::parse_inference_rule;
use crate::typing::TypingRule;
#[allow(clippy::type_complexity)] pub fn load(
blocks: &[&str],
) -> Result<(HashMap<String, TypingRule>, Vec<(String, String)>), String> {
let mut rules = HashMap::new();
let mut rewrites = Vec::new();
for block in blocks {
let lines: Vec<&str> = block
.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with("//"))
.collect();
if lines.is_empty() {
continue;
}
if lines.iter().any(|l| l.contains("::=")) {
continue;
}
if lines.iter().any(|l| l.contains('⇝') || l.contains("~>")) {
for line in &lines {
let Some(pair) = split_rewrite(line) else {
return Err(format!("malformed rewrite rule: '{line}'"));
};
rewrites.push(pair);
}
continue;
}
let (premises, conclusion, name) = parse_inference_rule(&lines)?;
let rule = TypingRule::new(premises, conclusion, name.clone())?;
rules.insert(name, rule);
}
Ok((rules, rewrites))
}
#[must_use]
pub fn normalizer(g: &SPG) -> crate::typing::Normalizer {
let rules = g
.rewrites
.iter()
.filter_map(|(l, r)| {
Some(crate::typing::RewriteRule {
lhs: crate::typing::Term::parse(g, l).ok()?,
rhs: crate::typing::Term::parse(g, r).ok()?,
})
})
.collect();
crate::typing::Normalizer::from_rules(rules)
}
#[must_use]
pub fn type_trees(g: &SPG) -> crate::typing::domain::Trees {
let mut trees = crate::typing::domain::Trees::new();
for rule in g.rules.values() {
let bindings = g.rule_bindings(&rule.name);
for te in rule.type_exprs() {
if !trees.contains_key(te)
&& let Ok(ty) = crate::typing::TyExpr::build(g, te, &bindings)
{
trees.insert(te.clone(), ty);
}
}
}
trees
}
pub fn check(g: &SPG) -> Result<(), String> {
for rule in g.rules.values() {
let bindings = g.rule_bindings(&rule.name);
for te in rule.type_exprs() {
crate::typing::TyExpr::build(g, te, &bindings)
.map_err(|e| format!("rule '{}': {e}", rule.name))?;
}
}
for (l, r) in &g.rewrites {
let (lhs, rhs) = (
crate::typing::Term::parse(g, l)?,
crate::typing::Term::parse(g, r)?,
);
let lv = lhs.vars();
if !rhs.vars().iter().all(|v| lv.contains(v)) {
return Err(format!("rewrite '{l} ⇝ {r}' invents a variable"));
}
}
Ok(())
}
fn split_rewrite(line: &str) -> Option<(String, String)> {
for sep in ["⇝", "~>"] {
if let Some((l, r)) = line.split_once(sep) {
let (l, r) = (l.trim(), r.trim());
if !l.is_empty() && !r.is_empty() {
return Some((l.to_string(), r.to_string()));
}
}
}
None
}
pub fn save(g: &SPG) -> String {
let mut out = String::new();
if g.rules.is_empty() {
return String::new();
}
out.push_str("// --- Rules ---\n");
let mut rule_list: Vec<_> = g.rules.values().collect();
rule_list.sort_by_key(|r| &r.name);
for rule in rule_list {
let premises: Vec<String> = rule.premises.iter().map(ToString::to_string).collect();
out.push_str(&premises.join(", "));
out.push('\n');
let concl_str = rule.conclusion.to_string();
let line = "-".repeat(std::cmp::max(20, concl_str.len() + 5));
out.push_str(&format!("{} ({})\n", line, rule.name));
out.push_str(&concl_str);
out.push_str("\n\n");
}
out
}