diff --git a/examples/repl.rs b/examples/repl.rs index f82e6f7..57823ee 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -4,7 +4,8 @@ use std::sync::atomic::{self, AtomicBool}; use std::sync::Arc; use std::time::Instant; -use logru::ast::{Sym, VarScope}; +use logru::analysis::find_unique_variables; +use logru::ast::{Rule, Sym, VarScope}; use logru::resolve::{ArithmeticResolver, ResolverExt}; use logru::search::{query_dfs, Resolved, Resolver}; use logru::term_arena::{AppTerm, ArgRange}; @@ -193,6 +194,22 @@ fn query(state: &mut AppState, args: &str) { } } +fn analyze_rules(rules: &[Rule]) { + for rule in rules.iter().filter(|r| r.scope.is_some()) { + let orphans = find_unique_variables(&rule) + .iter() + .filter_map(|var| rule.scope.as_ref().unwrap().get_name(*var)) + .collect::>(); + if !orphans.is_empty() { + print!("Some variables appear only once in the rule:"); + for name in orphans { + print!(" {},", name); + } + println!("\nare those typos?"); + } + } +} + static COMMANDS: &[Command] = &[ Command { name: ":define", @@ -203,7 +220,15 @@ static COMMANDS: &[Command] = &[ println!("Usage:\n\t:define "); return; } - match state.universe.load_str(args) { + let res = state + .universe + .parse_rules(args) + .map(|rules| { + analyze_rules(&rules); + rules + }) + .map(|rules| state.universe.insert_rules(rules)); + match res { Ok(()) => { println!("Defined!"); } @@ -248,14 +273,24 @@ static COMMANDS: &[Command] = &[ return; } match std::fs::read_to_string(args) { - Ok(contents) => match state.universe.load_str(&contents) { - Ok(()) => { - println!("Loaded!"); - } - Err(err) => { - println!("Failed to parse: {:?}", err); + Ok(contents) => { + let res = state + .universe + .parse_rules(&contents) + .map(|rules| { + analyze_rules(&rules); + rules + }) + .map(|rules| state.universe.insert_rules(rules)); + match res { + Ok(()) => { + println!("Loaded!"); + } + Err(err) => { + println!("Failed to parse: {:?}", err); + } } - }, + } Err(err) => { println!("Failed to load: {}", err); } diff --git a/src/analysis.rs b/src/analysis.rs new file mode 100644 index 0000000..8be39c0 --- /dev/null +++ b/src/analysis.rs @@ -0,0 +1,32 @@ +//! Program analysis proceduress + +use crate::ast::{Rule, Term, Var}; +use std::collections::{HashMap, HashSet}; + +/// Finds variables occuring only once in the rule (together in head and tail). +pub fn find_unique_variables(rule: &Rule) -> HashSet { + let vars = count_variables(rule); + vars.into_iter() + .filter(|(_var, count)| *count == 1) + .map(|(var, _count)| var) + .collect() +} + +/// Counts the number of occurrences of all variables in the rule (both in head and tail). +pub fn count_variables(rule: &Rule) -> HashMap { + let mut vars = HashMap::new(); + count_in_args(&mut vars, &rule.head.args); + count_in_args(&mut vars, &rule.tail); + vars +} + +fn count_in_args(mut vars: &mut HashMap, args: &[Term]) { + for term in args { + match term { + Term::Var(var) => *vars.entry(*var).or_insert(0) += 1, + Term::Int(_) => {} + Term::Cut => {} + Term::App(appterm) => count_in_args(&mut vars, &appterm.args), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 1408e41..1307c92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -152,6 +152,7 @@ #[cfg(doctest)] pub struct ReadmeDoctests; +pub mod analysis; pub mod ast; pub mod resolve; pub mod search; diff --git a/src/textual.rs b/src/textual.rs index 341bd85..404c39c 100644 --- a/src/textual.rs +++ b/src/textual.rs @@ -11,7 +11,7 @@ pub use parser::{ParseError, ParseErrorKind}; use pretty::ScopedPrettifier; use crate::{ - ast::Query, + ast::{Query, Rule}, resolve::RuleResolver, search::{self, SolutionIter}, universe::{RuleSet, SymbolOverlay, SymbolStore}, @@ -91,12 +91,22 @@ impl TextualUniverse { } } - /// Load a set of rules from a string. - pub fn load_str(&mut self, rules: &str) -> Result<(), ParseError> { - let rules = Parser::new(&mut self.symbols).parse_rules_str(rules)?; + /// Parse a set of rules using the symbols defined in this universe. + pub fn parse_rules(&mut self, rules: &str) -> Result, ParseError> { + Parser::new(&mut self.symbols).parse_rules_str(rules) + } + + /// Insert rules previously parsed using [`Self::parse_rules`]. + pub fn insert_rules(&mut self, rules: Vec) { for rule in rules { self.rules.insert(rule); } + } + + /// Load a set of rules from a string. + pub fn load_str(&mut self, rules: &str) -> Result<(), ParseError> { + let rules = self.parse_rules(rules)?; + self.insert_rules(rules); Ok(()) }