parol/utils/
mod.rs

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
18/// Applies a key-generating function to each element of a vector and yields a vector of
19/// pairs. Each pair consists of a unique key and a vector of all elements of the input
20/// vector which did produce this key by applying the projection function.
21/// The result vector is not sorted.
22pub(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
43/// Generates a new unique name avoiding collisions with the names given in the 'exclusions'.
44/// It takes a preferred name and if it collides it adds an increasing suffix number.
45/// If the preferred name already has a suffix number it starts counting up from this number.
46pub(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
85/// Generates a new function h that is the composition of the two given functions f and g.
86/// The result is h = f ∘ g, i.e h(x) = g(f(x)).
87/// The result type of the first function f and the input type of the second function g must be the
88/// same to make the functions combinable.
89pub(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
97/// Optimized combinator on boolean functions
98/// Applies short cut disjunction
99pub(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
110/// Optimized combinator on boolean functions
111/// Applies short cut conjunction
112pub(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
123// ---------------------------------------------------
124// Part of the Public API
125// *Changes will affect crate's version according to semver*
126// ---------------------------------------------------
127///
128/// Utility function to parse a file with a grammar in PAR syntax.
129///
130pub 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
148// ---------------------------------------------------
149// Part of the Public API
150// *Changes will affect crate's version according to semver*
151// ---------------------------------------------------
152///
153/// Utility function to parse a text with a grammar in PAR syntax.
154///
155pub 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
167// ---------------------------------------------------
168// Part of the Public API
169// *Changes will affect crate's version according to semver*
170// ---------------------------------------------------
171///
172/// Utility function for generating tree layouts
173///
174pub 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}