use std::fs::File;
use std::io::prelude::*;
use std::collections::HashMap;
type Rule = Vec<Token>;
#[derive(Debug)]
enum Token {
NONTERMINAL(char),
TERMINAL(char),
NULL,
}
pub struct NonTerminals {
nullable_map: HashMap<char, bool>,
first_map: HashMap<char, Vec<char>>,
follow_map: HashMap<char, Vec<char>>,
rule_map: HashMap<char, Vec<Rule>>,
}
impl NonTerminals {
pub fn init(grammar: String) -> Self {
let mut nullable_map = HashMap::new();
let mut first_map = HashMap::new();
let mut follow_map = HashMap::new();
let mut rule_map = HashMap::new();
for line in grammar.split("\n") {
let mut side = line.split("->");
let mut lhs = side.next().expect("Missing left hand side").trim().chars();
let nonterminal = lhs.next();
match nonterminal {
Some(c) if c.is_ascii_uppercase() => {
nullable_map.entry(c).or_insert(false);
first_map.entry(c).or_insert(Vec::new());
follow_map.entry(c).or_insert(Vec::new());
rule_map.entry(c).or_insert(Vec::new());
}
_ => panic!("Something with the left hand side of a rule went very wrong"),
}
if let Some(_) = lhs.next() {
panic!("left hand side too long");
}
let mut rhs = side.next().expect("Missing right hand side").trim().chars();
let rule = rhs
.filter(|c| (c.is_ascii() || *c == '0') && !c.is_whitespace())
.map(|c| match c {
'0' => Token::NULL,
_ if c.is_uppercase() => Token::NONTERMINAL(c),
_ => Token::TERMINAL(c),
})
.collect();
rule_map
.entry(nonterminal.unwrap())
.and_modify(|rule_set| rule_set.push(rule));
}
NonTerminals {
nullable_map,
first_map,
follow_map,
rule_map,
}
}
pub fn calculate_null_set(&mut self) {
let mut changed = true;
while changed {
changed = false;
for (non_terminal, rules) in self.rule_map.iter() {
for rule in rules {
let nullable = rule.iter().all(|token| match token {
Token::NONTERMINAL(c) => *self.nullable_map.get(c).unwrap(),
Token::TERMINAL(_) => false,
Token::NULL => true,
});
if nullable == true && nullable != *self.nullable_map.get(non_terminal).unwrap()
{
self.nullable_map.insert(*non_terminal, true);
changed = true;
}
}
}
}
}
pub fn calculate_first_set(&mut self) {
let mut changed = true;
while changed {
changed = false;
for (non_terminal, rules) in self.rule_map.iter() {
for rule in rules {
for token in rule {
match token {
Token::NONTERMINAL(c) => {
let potential_new_firsts =
(*self.first_map.get(c).unwrap()).clone();
for f in potential_new_firsts {
if !self.first_map.get(non_terminal).unwrap().contains(&f) {
self.first_map
.entry(*non_terminal)
.and_modify(|first_set| first_set.push(f));
changed = true;
}
}
if !*self.nullable_map.get(c).unwrap() {
break;
}
}
Token::TERMINAL(f) => {
if !self.first_map.get(non_terminal).unwrap().contains(f) {
self.first_map
.entry(*non_terminal)
.and_modify(|first_set| first_set.push(*f));
changed = true;
}
break;
}
Token::NULL => (),
};
}
}
}
}
}
pub fn calculate_follow_set(&mut self) {
let mut changed = true;
while changed {
changed = false;
for (nonterminal, _) in self.rule_map.iter() {
for (lhs_nonterminal, rules) in self.rule_map.iter() {
for rule in rules {
let mut state = 0;
let mut nullable_til_end = false;
for token in rule {
match token {
Token::NONTERMINAL(c) => {
if state == 0 && c == nonterminal {
state += 1;
nullable_til_end = true;
} else if state == 1 && c != nonterminal {
for f in self.first_map.get(c).unwrap() {
if !self.follow_map.get(nonterminal).unwrap().contains(&f) {
self.follow_map
.entry(*nonterminal)
.and_modify(|follow_set| follow_set.push(*f));
changed = true;
}
}
if !*self.nullable_map.get(c).unwrap() {
state += 1;
nullable_til_end = false;
}
} else {
continue;
}
}
Token::TERMINAL(f) => {
nullable_til_end = false;
if state == 0 {
continue;
} else if state == 1 {
if !self.follow_map.get(nonterminal).unwrap().contains(&f) {
self.follow_map
.entry(*nonterminal)
.and_modify(|follow_set| follow_set.push(*f));
changed = true;
}
state += 1;
} else {
break;
}
}
Token::NULL => (),
};
}
if nullable_til_end && lhs_nonterminal != nonterminal {
let potential_new_follows =
(*self.follow_map.get(lhs_nonterminal).unwrap()).clone();
for f in potential_new_follows {
if !(*self.follow_map.get(nonterminal).unwrap()).contains(&f) {
self.follow_map
.entry(*nonterminal)
.and_modify(|follow_set| follow_set.push(f));
changed = true;
}
}
}
}
}
}
}
}
pub fn print_rules(&self) {
for (non_terminal, rule) in self.rule_map.iter() {
println!("{}\n{:?}", non_terminal, rule);
}
}
pub fn print_results(&self) {
for (nonterminal, _) in self.rule_map.iter() {
println!("{}", nonterminal);
println!("Nullable: {}", self.nullable_map.get(nonterminal).unwrap());
println!("First: {:?}", self.first_map.get(nonterminal).unwrap());
println!("Follow: {:?}", self.follow_map.get(nonterminal).unwrap());
}
}
}
pub fn open_file(mut args: std::env::Args) -> String {
if args.len() > 2 {
println!("Program only supports 1 file");
}
args.next();
let filename = match args.next() {
Some(arg) => arg,
None => panic!("Specify file with grammars"),
};
let mut f = File::open(filename).expect("File not found");
let mut contents = String::new();
f.read_to_string(&mut contents).expect("Error reading file");
contents
}