use crate::parser::{Factor, ParolGrammar};
use crate::transformation::transform_productions;
use crate::{Cfg, GrammarConfig, ScannerConfig, Symbol, Terminal, TerminalKind, generators};
use anyhow::{Result, bail};
use super::parol_grammar::{LookaheadExpression, ScannerStateSwitch};
pub(crate) fn try_to_convert(parol_grammar: ParolGrammar) -> Result<GrammarConfig> {
let st = parol_grammar.start_symbol;
let non_terminals =
parol_grammar
.productions
.iter()
.fold(Vec::<String>::new(), |mut acc, p| {
if !acc.contains(&p.lhs) {
acc.push(p.lhs.clone());
}
acc
});
let pr = transform_productions(parol_grammar.productions, parol_grammar.grammar_type)?;
let cfg = Cfg { st, pr };
let title = parol_grammar.title;
let comment = parol_grammar.comment;
debug_assert_eq!(parol_grammar.scanner_configurations[0].name, "INITIAL");
let line_comments = parol_grammar.scanner_configurations[0]
.line_comments
.clone();
let block_comments = parol_grammar.scanner_configurations[0]
.block_comments
.clone();
let auto_newline = !parol_grammar.scanner_configurations[0].auto_newline_off;
let auto_ws = !parol_grammar.scanner_configurations[0].auto_ws_off;
let allow_unmatched = parol_grammar.scanner_configurations[0].allow_unmatched;
let lookahead_size = 1;
let scanner_config = ScannerConfig::default()
.with_line_comments(line_comments)
.with_block_comments(block_comments)
.with_auto_newline(auto_newline)
.with_auto_ws(auto_ws)
.with_allow_unmatched(allow_unmatched);
let mut grammar_config = GrammarConfig::new(cfg, lookahead_size)
.with_title(title)
.with_non_terminals(non_terminals)
.with_comment(comment)
.with_grammar_type(parol_grammar.grammar_type)
.add_scanner(scanner_config);
for u in parol_grammar.user_type_definitions {
grammar_config = grammar_config.add_user_type_def(u.0, u.1.to_string());
}
for n in parol_grammar.nt_type_definitions {
grammar_config = grammar_config.add_nt_type_def(n.0, n.1.to_string());
}
grammar_config.t_type_def = parol_grammar.t_type_def.map(|t| t.to_string());
for s in 1..parol_grammar.scanner_configurations.len() {
grammar_config = grammar_config.add_scanner(try_from_scanner_config(
&parol_grammar.scanner_configurations[s],
s,
)?);
}
let terminal_resolver = grammar_config.cfg.get_terminal_index_function();
let scanner_resolver = |name: &str| -> Option<usize> {
parol_grammar
.scanner_configurations
.iter()
.position(|sc| sc.name == name)
};
let pr_copy = grammar_config.cfg.pr.clone();
let terminal_finder =
move |name: &str| -> Option<(String, TerminalKind, Option<LookaheadExpression>)> {
pr_copy.iter().find_map(|p| {
if p.0.get_n_ref().unwrap() == name && p.1.len() == 1 {
match &p.1[0] {
Symbol::T(Terminal::Trm(t, k, _, _, _, _, l)) => {
Some((t.to_owned(), *k, l.clone()))
}
_ => None,
}
} else {
None
}
})
};
grammar_config
.scanner_configurations
.iter_mut()
.try_for_each(|sc| {
insert_transitions(
sc,
&parol_grammar.scanner_configurations,
&terminal_resolver,
scanner_resolver,
&terminal_finder,
)
})?;
Ok(grammar_config)
}
fn insert_transitions(
sc: &mut generators::ScannerConfig,
scanner_configurations: &[crate::parser::parol_grammar::ScannerConfig],
terminal_resolver: &impl crate::grammar::cfg::TerminalIndexFn,
scanner_resolver: impl Fn(&str) -> Option<usize>,
terminal_finder: impl Fn(&str) -> Option<(String, TerminalKind, Option<LookaheadExpression>)>,
) -> Result<()> {
if let Some(source_configuration) = scanner_resolver(&sc.scanner_name) {
let mut transitions = Vec::new();
scanner_configurations[source_configuration]
.transitions
.iter()
.try_for_each(|(token, scanner_switch_kind)| {
if let Some((txt, kind, la)) = terminal_finder(token.text()) {
match scanner_switch_kind {
ScannerStateSwitch::Switch(m, location) => {
if scanner_resolver(m).is_some() {
transitions.push((
terminal_resolver.terminal_index(&txt, kind, &la),
ScannerStateSwitch::Switch(m.to_string(), location.clone()),
));
} else {
bail!(
"Target scanner configuration {} not found",
scanner_switch_kind
);
}
}
ScannerStateSwitch::SwitchPush(m, location) => {
if scanner_resolver(m).is_some() {
transitions.push((
terminal_resolver.terminal_index(&txt, kind, &la),
ScannerStateSwitch::SwitchPush(m.to_string(), location.clone()),
));
} else {
bail!(
"Target scanner configuration {} not found",
scanner_switch_kind
);
}
}
ScannerStateSwitch::SwitchPop(location) => {
transitions.push((
terminal_resolver.terminal_index(&txt, kind, &la),
ScannerStateSwitch::SwitchPop(location.clone()),
));
}
}
} else {
bail!("Terminal {} not found", token);
}
Ok(())
})?;
transitions.sort_by(|a, b| a.0.cmp(&b.0));
sc.transitions = transitions;
} else {
bail!("Scanner configuration {} not found", sc.scanner_name);
}
Ok(())
}
fn try_from_scanner_config(
sc: &crate::parser::parol_grammar::ScannerConfig,
scanner_state: usize,
) -> Result<ScannerConfig> {
let scanner_config = ScannerConfig::new(sc.name.clone(), scanner_state)
.with_line_comments(sc.line_comments.clone())
.with_block_comments(sc.block_comments.clone())
.with_auto_newline(!sc.auto_newline_off)
.with_auto_ws(!sc.auto_ws_off)
.with_allow_unmatched(sc.allow_unmatched);
Ok(scanner_config)
}
pub(crate) fn try_from_factor(factor: Factor) -> Result<Symbol> {
match factor {
Factor::NonTerminal(n, a, u, m) => {
Ok(Symbol::N(n, a, u, m))
}
Factor::Terminal(t, k, s, a, u, m, l) => {
Ok(Symbol::T(Terminal::Trm(t, k, s, a, u, m, l)))
}
_ => bail!("Unexpected type of factor: {}", factor),
}
}