/* * Copyright 2022 Arnaud Golfouse * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #[path = "../../grammars/modules/mod.rs"] mod modules; use self::modules::{GrammarFormat, Runtime}; use ielr::{ input::{Grammar, Node, Symbol}, output::{Action, Lookahead, Table}, }; use std::{ collections::HashMap, fmt::{self, Write as _}, path::Path, }; #[test] fn successes() { let directory = Path::new("grammars/successes"); let mut successes_count = 0; let mut failures: HashMap = HashMap::new(); let mut output = String::from("\n\n"); for file in directory.read_dir().unwrap() { let file = file.unwrap(); if !file.metadata().unwrap().is_file() { continue; } let success_name = file.path().display().to_string(); write!(&mut output, "test success {success_name} ... ").unwrap(); let file = std::fs::read_to_string(file.path()).unwrap(); let grammar_format = match modules::read_grammar(&file) { Ok(res) => res, Err(err) => { output.push_str("FAILED\n"); failures.insert(success_name, err); continue; } }; match ielr::compute_table( grammar_format.algorithm, &grammar_format.grammar, std::iter::once(Node(0)), ) { Ok((table, statistics)) => { let mut solution_unused = None; for (index, solution) in grammar_format .grammar .get_conflict_solutions() .iter() .enumerate() { if !statistics.conflict_resolution_used.contains(&index) { solution_unused = Some(solution); break; } } if statistics.algorithm_needed != grammar_format.algorithm { failures.insert( success_name, format!( "algorithm specified is too strong !\nspecified: {:?}, used: {:?}", statistics.algorithm_needed, grammar_format.algorithm ), ); output.push_str("FAILED\n"); } else if let Some(solution_unused) = solution_unused { failures.insert( success_name, format!("unused conflict solution: {solution_unused:?}"), ); output.push_str("FAILED\n"); } else { let runtime = Runtime { table, grammar: &grammar_format.grammar, }; let mut success = true; for example in &grammar_format.examples { if !runtime.is_valid(Node(0), example) { success = false; let mut failure = String::from("failed example: '"); for &token in example { let token = &grammar_format.symbols[&Symbol::Token(token)]; failure.push_str(token); failure.push(' '); } failure = failure.trim_end().to_string(); failure.push('\''); failures.insert(success_name.clone(), failure); output.push_str("FAILED\n"); break; } } for example_fail in &grammar_format.examples_fail { if runtime.is_valid(Node(0), example_fail) { success = false; let mut failure = String::from("example succeeded: '"); for &token in example_fail { let token = &grammar_format.symbols[&Symbol::Token(token)]; failure.push_str(token); failure.push(' '); } failure = failure.trim_end().to_string(); failure.push('\''); failures.insert(success_name, failure); output.push_str("FAILED\n"); break; } } if success { successes_count += 1; output.push_str("ok\n"); } } } Err(error) => { failures.insert( success_name, DisplayError { grammar: &grammar_format.grammar, error, symbol_to_str: &grammar_format.symbols, } .to_string(), ); output.push_str("FAILED\n"); } } } if !failures.is_empty() { output.push_str("\nfailures:\n\n"); for (failure_name, failure_output) in &failures { writeln!(&mut output, "---- {failure_name} ----").unwrap(); output.push_str(failure_output); output.push_str("\n\n"); } output.push_str("failures:\n"); for name in failures.keys() { writeln!(&mut output, " {name}").unwrap(); } write!( &mut output, "\nsuccesses result: FAILED. {} passed; {} failed", successes_count, failures.len() ) .unwrap(); panic!("{output}") } } #[test] fn failures() { let directory = Path::new("grammars/failures"); let mut successes_count = 0; let mut failures: HashMap = HashMap::new(); let mut output = String::from("\n\n"); for file in directory.read_dir().unwrap() { let file = file.unwrap(); if !file.metadata().unwrap().is_file() { continue; } let success_name = file.path().display().to_string(); write!(&mut output, "test success {success_name} ... ").unwrap(); let file = std::fs::read_to_string(file.path()).unwrap(); let GrammarFormat { grammar, algorithm, symbols: _, examples, examples_fail, } = match modules::read_grammar(&file) { Ok(res) => res, Err(err) => { output.push_str("FAILED\n"); failures.insert(success_name, err); continue; } }; if !examples.is_empty() || !examples_fail.is_empty() { panic!("no examples are possible for failures") } if ielr::compute_table(algorithm, &grammar, std::iter::once(Node(0))).is_ok() { failures.insert( success_name, String::from("the algorithm succeeded, but it should have failed !"), ); output.push_str("FAILED\n"); } else { successes_count += 1; output.push_str("ok\n"); } } if failures.is_empty() { } else { output.push_str("\nfailures:\n\n"); for (failure_name, failure_output) in &failures { writeln!(&mut output, "---- {failure_name} ----").unwrap(); output.push_str(failure_output); output.push_str("\n\n"); } output.push_str("failures:\n"); for name in failures.keys() { writeln!(&mut output, " {name}").unwrap(); } write!( &mut output, "\nfailures result: FAILED. {} passed; {} failed", successes_count, failures.len() ) .unwrap(); panic!("{output}") } } #[derive(Debug)] struct DisplayError<'a> { grammar: &'a Grammar, error: ielr::output::Error, symbol_to_str: &'a HashMap, } impl fmt::Display for DisplayError<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.error { ielr::output::Error::StatesOverflow => f.write_str("the number of states overflowed"), ielr::output::Error::CycleError(cycle) => { writeln!( f, "there is a cycle in the grammar: the node {} can derive itself, by following:", self.symbol_to_str[&Symbol::Node(cycle.node)] )?; for prod in &cycle.path { let (lhs, rhs) = (prod.lhs, self.grammar.get_rhs(*prod).unwrap_or_default()); write!(f, " {}:", self.symbol_to_str[&Symbol::Node(lhs)])?; for symbol in rhs { write!(f, " {}", self.symbol_to_str[symbol])?; } writeln!(f)?; } Ok(()) } ielr::output::Error::Conflict { lalr_tables: _, state: _, conflict, } => { f.write_str("conflict on lookahead ")?; match conflict.lookahead { ielr::output::Lookahead::Eof => f.write_str("Eof"), ielr::output::Lookahead::Token(token) => { write!(f, "{}", self.symbol_to_str[&Symbol::Token(token)]) } }?; writeln!(f, ":")?; if conflict.contributions.has_shift { f.write_str(" shift")?; writeln!(f)?; } for &prod_idx in &conflict.contributions.reduces { let rhs = self.grammar.get_rhs(prod_idx).unwrap_or_default(); write!( f, " reduce {} ->", self.symbol_to_str[&Symbol::Node(prod_idx.lhs)] )?; for symbol in rhs { write!(f, " {}", self.symbol_to_str[symbol])?; } writeln!(f)?; } Ok(()) } } } } struct DisplayTable<'a> { grammar: &'a Grammar, symbol_to_str: &'a HashMap, table: Table, } impl DisplayTable<'_> { fn fmt_lookahead(&self, f: &mut fmt::Formatter<'_>, lookahead: Lookahead) -> fmt::Result { f.write_str(match lookahead { ielr::output::Lookahead::Eof => "Eof", ielr::output::Lookahead::Token(token) => &self.symbol_to_str[&Symbol::Token(token)], }) } } impl fmt::Display for DisplayTable<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Table {\n")?; for (index, state) in self.table.states.iter().enumerate() { writeln!(f, " State({}): {{", index)?; writeln!(f, " items: [")?; for item in state.all_items() { let rhs = self.grammar.get_rhs(item.production).unwrap_or_default(); write!( f, " {}:", self.symbol_to_str[&Symbol::Node(item.production.lhs)] )?; for (index, symbol) in rhs.iter().enumerate() { f.write_str(if index == item.index as usize { "∙" } else { " " })?; write!(f, "{}", self.symbol_to_str[symbol])?; } if item.index as usize == rhs.len() { f.write_str("∙")?; } f.write_str(" { ")?; for (index, lookahead) in item.get_all_lookaheads().enumerate() { if index != 0 { f.write_str(", ")?; } self.fmt_lookahead(f, lookahead)?; } writeln!(f, " }}")?; } writeln!(f, " ],")?; writeln!(f, " transitions: {{")?; for (lookahead, action) in state.action_table().iter() { if let Action::Shift(to) = action { f.write_str(" ")?; self.fmt_lookahead(f, lookahead)?; writeln!(f, ": {to:?}")?; } } for (node, to) in state.goto_table().iter() { writeln!( f, " {}: {to:?}", self.symbol_to_str[&Symbol::Node(node)] )?; } writeln!(f, " }},")?; } Ok(()) } }