1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
use crate::grammar::cfg::RX_NUM_SUFFIX;
use crate::parser::parol_grammar::ParolGrammar;
use crate::parser::parol_parser::parse;
use crate::GrammarConfig;
use anyhow::{Context, Result};
use parol_runtime::ParseTree;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Debug;
use std::fs;
use std::hash::Hash;
use std::path::Path;
use syntree_layout::Layouter;

pub mod str_vec;

/// Applies a key-generating function to each element of a vector and yields a vector of
/// pairs. Each pair consists of a unique key and a vector of all elements of the input
/// vector which did produce this key by applying the projection function.
/// The result vector is not sorted.
pub(crate) fn group_by<P, T, K>(data: &[T], projection: P) -> Vec<(K, Vec<T>)>
where
    P: Fn(&T) -> K,
    K: Eq + Hash,
    T: Clone,
{
    let mut grouping: HashMap<K, Vec<T>> = HashMap::new();
    data.iter()
        .fold(&mut grouping, |acc, t| {
            let key = projection(t);
            if let Some(vt) = acc.get_mut(&key) {
                vt.push(t.clone());
            } else {
                acc.insert(key, vec![t.clone()]);
            }
            acc
        })
        .drain()
        .collect()
}

/// Generates a new unique name avoiding collisions with the names given in the 'exclusions'.
/// It takes a preferred name and if it collides it adds an increasing suffix number.
/// If the preferred name already has a suffix number it starts counting up from this number.
pub(crate) fn generate_name<T>(exclusions: &[T], preferred_name: String) -> String
where
    T: AsRef<str>,
{
    fn gen_name<T>(exclusions: &[T], prefix: String, start_num: usize) -> String
    where
        T: AsRef<str>,
    {
        let mut num = start_num;
        let mut new_name = format!("{}{}", prefix, num);
        while exclusions.iter().any(|n| n.as_ref() == new_name) {
            num += 1;
            new_name = format!("{}{}", prefix, num);
        }
        new_name
    }

    if exclusions.iter().any(|n| n.as_ref() == preferred_name) {
        let (suffix_number, prefix) = {
            if let Some(match_) = RX_NUM_SUFFIX.find(&preferred_name) {
                let num = match_.as_str().parse::<usize>().unwrap_or(1);
                (num, preferred_name[0..match_.start()].to_string())
            } else {
                (0, preferred_name.clone())
            }
        };
        gen_name(exclusions, prefix, suffix_number)
    } else {
        preferred_name
    }
}

/// Generates a new function h that is the composition of the two given functions f and g.
/// The result is h = f ∘ g, i.e h(x) = g(f(x)).
/// The result type of the first function f and the input type of the second function g must be the
/// same to make the functions combinable.
pub(crate) fn combine<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
where
    F: Fn(A) -> B,
    G: Fn(B) -> C,
{
    move |x| g(f(x))
}

/// Optimized combinator on boolean functions
/// Applies short cut disjunction
pub(crate) fn short_cut_disjunction_combine<A, F, G>(f: F, g: G) -> impl Fn(&A) -> bool
where
    F: Fn(&A) -> bool,
    G: Fn(&A) -> bool,
{
    move |x| {
        let r = f(x);
        if r {
            r
        } else {
            g(x)
        }
    }
}

/// Optimized combinator on boolean functions
/// Applies short cut conjunction
pub(crate) fn short_cut_conjunction_combine<A, F, G>(f: F, g: G) -> impl Fn(&A) -> bool
where
    F: Fn(&A) -> bool,
    G: Fn(&A) -> bool,
{
    move |x| {
        let r = f(x);
        if !r {
            r
        } else {
            g(x)
        }
    }
}

// ---------------------------------------------------
// Part of the Public API
// *Changes will affect crate's version according to semver*
// ---------------------------------------------------
///
/// Utility function to parse a file with a grammar in PAR syntax.
///
pub fn obtain_grammar_config<T>(file_name: T, verbose: bool) -> Result<GrammarConfig>
where
    T: AsRef<Path> + Debug,
{
    let input = fs::read_to_string(&file_name)
        .with_context(|| format!("Can't read file {:?}", file_name))?;

    let mut parol_grammar = ParolGrammar::new();
    let _syntax_tree = parse(&input, file_name.as_ref(), &mut parol_grammar)
        .with_context(|| format!("Failed parsing file {}", file_name.as_ref().display()))?;

    if verbose {
        println!("{}", parol_grammar);
    }

    GrammarConfig::try_from(parol_grammar)
}

// ---------------------------------------------------
// Part of the Public API
// *Changes will affect crate's version according to semver*
// ---------------------------------------------------
///
/// Utility function to parse a text with a grammar in PAR syntax.
///
pub fn obtain_grammar_config_from_string(input: &str, verbose: bool) -> Result<GrammarConfig> {
    let mut parol_grammar = ParolGrammar::new();
    let _syntax_tree = parse(input, "No file", &mut parol_grammar)
        .with_context(|| format!("Failed parsing text {}", input.escape_default()))?;

    if verbose {
        println!("{}", parol_grammar);
    }

    GrammarConfig::try_from(parol_grammar)
}

// ---------------------------------------------------
// Part of the Public API
// *Changes will affect crate's version according to semver*
// ---------------------------------------------------
///
/// Utility function for generating tree layouts
///
pub fn generate_tree_layout<T>(syntax_tree: &ParseTree<'_>, input_file_name: T) -> Result<()>
where
    T: AsRef<Path>,
{
    let mut svg_full_file_name = input_file_name.as_ref().to_path_buf();
    svg_full_file_name.set_extension("svg");

    Layouter::new(syntax_tree)
        .with_file_path(&svg_full_file_name)
        .embed_with_visualize()?
        .write()
        .context("Failed writing layout")
}