lace_sans_io/logic/
lacegram.rs1use super::dependency_graph::DependencyGraph;
7use super::limits::{DatalogLimits, PreparationError, RuleCollector};
8use crate::types::{Lacegram, Rule};
9
10mod parser;
11mod syntax;
12mod validation;
13
14use parser::head_name;
15pub use parser::{parse_rule, render_rule_line};
16use validation::check_safety;
17pub use validation::predicate_variable_kinds;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct CanonicalLacegram {
25 pub text: String,
26 pub program: Lacegram,
27}
28
29pub fn canonicalize_lacegram_source(
36 text: &str,
37 limits: DatalogLimits,
38) -> Result<CanonicalLacegram, PreparationError> {
39 if text.contains('\r') {
40 return Err("lacegram contains CR".into());
41 }
42 let mut rules = RuleCollector::new(limits);
43 for line in text.lines() {
44 let line = line.trim();
45 if line.is_empty() || line.starts_with('#') {
46 continue;
47 }
48 rules.push(parse_rule(line)?)?;
49 }
50 prepare_lacegram_rules(rules.into_rules(), limits)
51}
52
53pub fn canonicalize_lacegram_rules(
54 rules: Vec<Rule>,
55 limits: DatalogLimits,
56) -> Result<CanonicalLacegram, String> {
57 prepare_lacegram_rules(rules, limits).map_err(Into::into)
58}
59
60pub fn prepare_lacegram_rules(
62 rules: Vec<Rule>,
63 limits: DatalogLimits,
64) -> Result<CanonicalLacegram, PreparationError> {
65 if rules.is_empty() {
66 return Err("lacegram has no rules".into());
67 }
68 limits.validate_rules(&rules)?;
69 let mut parsed = rules
70 .into_iter()
71 .map(|rule| (render_rule_line(&rule), rule))
72 .collect::<Vec<_>>();
73 parsed.sort_by(|(left, _), (right, _)| {
74 head_name(left).cmp(head_name(right)).then(left.cmp(right))
75 });
76 parsed.dedup_by(|(left, _), (right, _)| left == right);
77 let text = parsed
78 .iter()
79 .map(|(line, _)| line.as_str())
80 .collect::<Vec<_>>()
81 .join("\n");
82 let rules = parsed.into_iter().map(|(_, rule)| rule).collect();
83 let program = compile_rules(rules, limits)?;
84 Ok(CanonicalLacegram { text, program })
85}
86
87pub fn parse_lacegram(text: &str, limits: DatalogLimits) -> Result<Lacegram, String> {
93 let canonical = canonicalize_lacegram_source(text, limits)?;
94 if canonical.text != text {
95 return Err("lacegram text is not canonical".into());
96 }
97 Ok(canonical.program)
98}
99
100fn compile_rules(rules: Vec<Rule>, limits: DatalogLimits) -> Result<Lacegram, String> {
101 validate_rules(&rules)?;
102 let strata = DependencyGraph::new(&rules).strata(rules)?;
103 Ok(Lacegram { strata, limits })
104}
105
106pub(crate) fn validate_rules(rules: &[Rule]) -> Result<(), String> {
108 for rule in rules {
109 syntax::validate_rule_syntax(rule)?;
110 }
111 check_safety(rules)
112}
113
114#[cfg(test)]
115mod limit_tests;
116#[cfg(test)]
117mod tests;