1use crate::GrammarConfig;
2use crate::grammar::cfg::RX_NUM_SUFFIX;
3use crate::parser::parol_grammar::ParolGrammar;
4use crate::parser::parol_parser::parse;
5use anyhow::{Context, Result};
6use parol_runtime::ParseTree;
7use std::collections::HashMap;
8use std::convert::TryFrom;
9use std::fmt::Debug;
10use std::fs;
11use std::hash::Hash;
12use std::path::Path;
13use syntree_layout::Layouter;
14
15pub mod str_iter;
16pub mod str_vec;
17
18pub(crate) fn group_by<P, T, K>(data: &[T], projection: P) -> Vec<(K, Vec<T>)>
23where
24 P: Fn(&T) -> K,
25 K: Eq + Hash,
26 T: Clone,
27{
28 let mut grouping: HashMap<K, Vec<T>> = HashMap::new();
29 data.iter()
30 .fold(&mut grouping, |acc, t| {
31 let key = projection(t);
32 if let Some(vt) = acc.get_mut(&key) {
33 vt.push(t.clone());
34 } else {
35 acc.insert(key, vec![t.clone()]);
36 }
37 acc
38 })
39 .drain()
40 .collect()
41}
42
43pub(crate) fn generate_name<T>(
47 exclusions: impl Iterator<Item = T> + Clone,
48 preferred_name: String,
49) -> String
50where
51 T: AsRef<str>,
52{
53 fn gen_name<T>(
54 exclusions: impl Iterator<Item = T> + Clone,
55 prefix: String,
56 start_num: usize,
57 ) -> String
58 where
59 T: AsRef<str>,
60 {
61 let mut num = start_num;
62 let mut new_name = format!("{prefix}{num}");
63 while exclusions.clone().any(|n| n.as_ref() == new_name) {
64 num += 1;
65 new_name = format!("{prefix}{num}");
66 }
67 new_name
68 }
69
70 if exclusions.clone().any(|n| n.as_ref() == preferred_name) {
71 let (suffix_number, prefix) = {
72 if let Some(match_) = RX_NUM_SUFFIX.find(&preferred_name) {
73 let num = match_.as_str().parse::<usize>().unwrap_or(1);
74 (num, preferred_name[0..match_.start()].to_string())
75 } else {
76 (0, preferred_name.clone())
77 }
78 };
79 gen_name(exclusions, prefix, suffix_number)
80 } else {
81 preferred_name
82 }
83}
84
85pub(crate) fn combine<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
90where
91 F: Fn(A) -> B,
92 G: Fn(B) -> C,
93{
94 move |x| g(f(x))
95}
96
97pub(crate) fn short_cut_disjunction_combine<A, F, G>(f: F, g: G) -> impl Fn(&A) -> bool
100where
101 F: Fn(&A) -> bool,
102 G: Fn(&A) -> bool,
103{
104 move |x| {
105 let r = f(x);
106 if r { r } else { g(x) }
107 }
108}
109
110pub(crate) fn short_cut_conjunction_combine<A, F, G>(f: F, g: G) -> impl Fn(&A) -> bool
113where
114 F: Fn(&A) -> bool,
115 G: Fn(&A) -> bool,
116{
117 move |x| {
118 let r = f(x);
119 if !r { r } else { g(x) }
120 }
121}
122
123pub fn obtain_grammar_config<T>(file_name: T, verbose: bool) -> Result<GrammarConfig>
131where
132 T: AsRef<Path> + Debug,
133{
134 let input =
135 fs::read_to_string(&file_name).with_context(|| format!("Can't read file {file_name:?}"))?;
136
137 let mut parol_grammar = ParolGrammar::new();
138 let _syntax_tree = parse(&input, file_name.as_ref(), &mut parol_grammar)
139 .with_context(|| format!("Failed parsing file {}", file_name.as_ref().display()))?;
140
141 if verbose {
142 println!("{parol_grammar}");
143 }
144
145 GrammarConfig::try_from(parol_grammar)
146}
147
148pub fn obtain_grammar_config_from_string(input: &str, verbose: bool) -> Result<GrammarConfig> {
156 let mut parol_grammar = ParolGrammar::new();
157 let _syntax_tree = parse(input, "No file", &mut parol_grammar)
158 .with_context(|| format!("Failed parsing text {}", input.escape_default()))?;
159
160 if verbose {
161 println!("{parol_grammar}");
162 }
163
164 GrammarConfig::try_from(parol_grammar)
165}
166
167pub fn generate_tree_layout<T>(
175 syntax_tree: &ParseTree,
176 input: &str,
177 input_file_name: T,
178) -> Result<()>
179where
180 T: AsRef<Path>,
181{
182 let mut svg_full_file_name = input_file_name.as_ref().to_path_buf();
183 svg_full_file_name.set_extension("svg");
184
185 Layouter::new(syntax_tree)
186 .with_file_path(&svg_full_file_name)
187 .embed_with_source_and_display(input)?
188 .write()
189 .context("Failed writing layout")
190}