1
use std::collections::HashSet;
2
use std::fs;
3
use std::path::Path;
4
use std::path::PathBuf;
5

            
6
use pest::Parser;
7
use pest_derive::Parser;
8

            
9
use merc_aterm::ATerm;
10
use merc_aterm::storage::THREAD_TERM_POOL;
11
use merc_pest_consume::Error;
12
use merc_pest_consume::Node;
13
use merc_pest_consume::match_nodes;
14
use merc_utilities::MercError;
15

            
16
use crate::syntax::ConditionSyntax;
17
use crate::syntax::RewriteRuleSyntax;
18
use crate::syntax::RewriteSpecificationSyntax;
19

            
20
#[derive(Parser)]
21
#[grammar = "rec_grammar.pest"]
22
pub(crate) struct RecParser;
23

            
24
type ParseResult<T> = Result<T, Error<Rule>>;
25
type ParseNode<'i> = Node<'i, Rule, ()>;
26

            
27
/// Result of parsing a REC specification containing all extracted components
28
#[derive(Debug)]
29
struct RecSpecResult {
30
    /// Name of the specification
31
    _name: String,
32
    /// List of included files
33
    include_files: Vec<String>,
34
    /// Constructor symbols with their arities
35
    constructors: Vec<(String, usize)>,
36
    /// Variable declarations
37
    variables: Vec<String>,
38
    /// Rewrite rules
39
    rewrite_rules: Vec<RewriteRuleSyntax>,
40
    /// Terms to evaluate
41
    eval_terms: Vec<ATerm>,
42
}
43

            
44
/// Load a REC specification from a specified file.
45
///
46
/// Files referenced by the header (`REC-SPEC name : include ...`) are resolved
47
/// relative to the including file's directory and loaded recursively.
48
pub fn load_rec_from_file(file: &Path) -> Result<(RewriteSpecificationSyntax, Vec<ATerm>), MercError> {
49
    let contents = fs::read_to_string(file)?;
50
    parse_rec(&contents, Some(file))
51
}
52

            
53
/// Load and join multiple REC specifications.
54
///
55
/// Header include directives are *not* resolved here: there is no base
56
/// directory to resolve them against, so every required specification must be
57
/// passed explicitly in `specs`.
58
64
pub fn load_rec_from_strings(specs: &[&str]) -> Result<(RewriteSpecificationSyntax, Vec<ATerm>), MercError> {
59
64
    let mut rewrite_spec = RewriteSpecificationSyntax::default();
60
64
    let mut terms = vec![];
61

            
62
94
    for spec in specs {
63
94
        let (include_spec, include_terms) = parse_rec(spec, None)?;
64
94
        rewrite_spec.merge(&include_spec);
65
94
        terms.extend_from_slice(&include_terms);
66
    }
67

            
68
64
    Ok((rewrite_spec, terms))
69
64
}
70

            
71
/// Parses a REC specification. REC files can import other REC files.
72
/// Returns a RewriteSpec containing all the rewrite rules and a list of terms that need to be rewritten.
73
95
fn parse_rec(contents: &str, path: Option<&Path>) -> Result<(RewriteSpecificationSyntax, Vec<ATerm>), MercError> {
74
95
    let mut visited = HashSet::new();
75
95
    if let Some(p) = path {
76
        visited.insert(p.to_path_buf());
77
95
    }
78
95
    parse_rec_impl(contents, path, &mut visited)
79
95
}
80

            
81
/// Recursive worker for [parse_rec]. `visited` records the canonical paths of
82
/// the files already loaded so that cyclic or diamond includes are not loaded
83
/// twice (and cannot recurse forever).
84
95
fn parse_rec_impl(
85
95
    contents: &str,
86
95
    path: Option<&Path>,
87
95
    visited: &mut HashSet<PathBuf>,
88
95
) -> Result<(RewriteSpecificationSyntax, Vec<ATerm>), MercError> {
89
    // Initialize return result
90
95
    let mut rewrite_spec = RewriteSpecificationSyntax::default();
91
95
    let mut terms = vec![];
92

            
93
    // Use Pest parser (generated automatically from the grammar.pest file)
94
95
    let mut parse_result = RecParser::parse(Rule::rec_spec, contents)?;
95
95
    let root = parse_result.next().ok_or("Could not parse REC specification")?;
96
95
    let parse_node = ParseNode::new(root);
97

            
98
    // Parse using the consumed-based implementation
99
95
    let result = RecParser::rec_spec(parse_node)?;
100

            
101
95
    rewrite_spec.rewrite_rules = result.rewrite_rules;
102
95
    rewrite_spec.constructors = result.constructors;
103
95
    rewrite_spec.variables = result.variables;
104

            
105
95
    if !result.eval_terms.is_empty() {
106
64
        terms.extend_from_slice(&result.eval_terms);
107
65
    }
108

            
109
    // REC files can import other REC files. Import all referenced by the header.
110
95
    for file in result.include_files {
111
30
        if let Some(p) = &path {
112
            let include_path = p
113
                .parent()
114
                .ok_or_else(|| format!("REC file {} has no parent directory", p.display()))?;
115
            let file_name = PathBuf::from(file.to_lowercase() + ".rec");
116
            let load_file = include_path.join(file_name);
117

            
118
            // Skip files we have already loaded to break include cycles and
119
            // avoid duplicating rules on diamond-shaped include graphs.
120
            if !visited.insert(load_file.clone()) {
121
                continue;
122
            }
123

            
124
            let contents = fs::read_to_string(&load_file)
125
                .map_err(|e| format!("failed to read included REC file {}: {e}", load_file.display()))?;
126
            // Resolve nested includes relative to the included file itself.
127
            let (include_spec, include_terms) = parse_rec_impl(&contents, Some(&load_file), visited)?;
128

            
129
            // Add rewrite rules and terms to the result.
130
            terms.extend_from_slice(&include_terms);
131
            rewrite_spec.merge(&include_spec);
132
30
        }
133
    }
134

            
135
95
    Ok((rewrite_spec, terms))
136
95
}
137

            
138
#[merc_pest_consume::parser]
139
impl RecParser {
140
    /// Parse a REC specification, returns structured result with all components
141
    fn rec_spec(spec: ParseNode) -> ParseResult<RecSpecResult> {
142
        // Extract all sections of the REC file
143
        match_nodes!(spec.into_children();
144
            [header((name, include_files)), _sorts, cons(constructors), _opns, vars(variables), rules(rewrite_rules), eval(eval_terms), EOI(_)] => {
145
                Ok(RecSpecResult {
146
                    _name: name,
147
                    include_files,
148
                    constructors,
149
                    variables,
150
                    rewrite_rules,
151
                    eval_terms,
152
                })
153
            },
154
            [header((name, include_files)), _sorts, cons(constructors), _opns, vars(variables), rules(rewrite_rules), EOI(_)] => {
155
                Ok(RecSpecResult {
156
                    _name: name,
157
                    include_files,
158
                    constructors,
159
                    variables,
160
                    rewrite_rules,
161
                    eval_terms: Vec::new(),
162
                })
163
            }
164
        )
165
    }
166

            
167
    /// Extracts data from parsed header of REC spec. Returns name and include files.
168
    fn header(header: ParseNode) -> ParseResult<(String, Vec<String>)> {
169
        match_nodes!(header.into_children();
170
            [identifier(name), identifier(include_files)..] => {
171
                Ok((name, include_files.collect()))
172
            }
173
        )
174
    }
175

            
176
    /// Extracts data from parsed constructor section, derives the arity of symbols. Types are ignored.
177
    fn cons(cons: ParseNode) -> ParseResult<Vec<(String, usize)>> {
178
        let mut constructors = Vec::new();
179

            
180
        match_nodes!(cons.into_children();
181
            [cons_decl(decls)..] => {
182
                constructors.extend(decls);
183
                Ok(constructors)
184
            }
185
        )
186
    }
187

            
188
    /// Parse a constructor declaration
189
    fn cons_decl(decl: ParseNode) -> ParseResult<(String, usize)> {
190
        match_nodes!(decl.into_children();
191
            [identifier(symbol), identifier(params).., identifier(_)] => {
192
                Ok((symbol, params.len()))
193
            }
194
        )
195
    }
196

            
197
    /// Extracts data from parsed rewrite rules. Returns list of rewrite rules
198
    fn rules(rules: ParseNode) -> ParseResult<Vec<RewriteRuleSyntax>> {
199
        match_nodes!(rules.into_children();
200
            [rewrite_rule(rule_nodes)..] => {
201
                Ok(rule_nodes.collect())
202
            }
203
        )
204
    }
205

            
206
    /// Parse a rewrite rule
207
    fn rewrite_rule(rule: ParseNode) -> ParseResult<RewriteRuleSyntax> {
208
        match_nodes!(rule.into_children();
209
            [term(lhs), term(rhs), condition(conditions)..] => {
210
                Ok(RewriteRuleSyntax {
211
                    lhs,
212
                    rhs,
213
                    conditions: conditions.collect(),
214
                })
215
            },
216
            [term(lhs), term(rhs)] => {
217
                Ok(RewriteRuleSyntax {
218
                    lhs,
219
                    rhs,
220
                    conditions: vec![],
221
                })
222
            }
223
        )
224
    }
225

            
226
    /// Parse a single rewrite rule
227
    fn single_rewrite_rule(rule: ParseNode) -> ParseResult<RewriteRuleSyntax> {
228
        match_nodes!(rule.into_children();
229
            [rewrite_rule(rule), EOI(_)] => {
230
                Ok(rule)
231
            },
232
        )
233
    }
234

            
235
    /// Parse a condition in a rewrite rule
236
    fn condition(condition: ParseNode) -> ParseResult<ConditionSyntax> {
237
        match_nodes!(condition.into_children();
238
            [term(lhs), comparison(equality), term(rhs)] => {
239
                Ok(ConditionSyntax {
240
                    lhs,
241
                    rhs,
242
                    equality,
243
                })
244
            }
245
        )
246
    }
247

            
248
    /// Parse a comparison operator
249
    fn comparison(comparison: ParseNode) -> ParseResult<bool> {
250
        match comparison.as_str() {
251
            "=" => Ok(true),
252
            "<>" => Ok(false),
253
            // The `comparison` grammar rule only matches "=" or "<>".
254
            other => unreachable!("unexpected comparison operator {other:?}"),
255
        }
256
    }
257

            
258
    /// Extracts data from the variable VARS block. Types are ignored.
259
    fn vars(vars: ParseNode) -> ParseResult<Vec<String>> {
260
        let mut variables = vec![];
261

            
262
        match_nodes!(vars.into_children();
263
            [var_decl(var_lists)..] => {
264
                for var_list in var_lists {
265
                    variables.extend(var_list);
266
                }
267
                Ok(variables)
268
            }
269
        )
270
    }
271

            
272
    /// Parse a variable declaration
273
    fn var_decl(var_decl: ParseNode) -> ParseResult<Vec<String>> {
274
        match_nodes!(var_decl.into_children();
275
            [identifier(vars).., identifier(_type)] => {
276
                // The last identifier is the type, so we exclude it
277
                Ok(vars.collect())
278
            }
279
        )
280
    }
281

            
282
    /// Extracts data from parsed EVAL section, returns a list of terms that need to be rewritten.
283
    fn eval(eval: ParseNode) -> ParseResult<Vec<ATerm>> {
284
        match_nodes!(eval.into_children();
285
            [term(terms)..] => {
286
                Ok(terms.collect())
287
            }
288
        )
289
    }
290

            
291
    /// Parse a term
292
    fn term(term: ParseNode) -> ParseResult<ATerm> {
293
        match_nodes!(term.into_children();
294
            [identifier(head_symbol), args(arguments)] => {
295
7678
                THREAD_TERM_POOL.with(|tp| {
296
7678
                    let symbol = tp.create_symbol(&head_symbol, arguments.len());
297
7678
                    Ok(tp.create_term_iter(&symbol, arguments))
298
7678
                })
299
            },
300
            [identifier(head_symbol)] => {
301
8378
                THREAD_TERM_POOL.with(|tp| {
302
8378
                    let symbol = tp.create_symbol(&head_symbol, 0);
303
8378
                    Ok(tp.create_constant(&symbol))
304
8378
                })
305
            }
306
        )
307
    }
308

            
309
    /// Parse arguments of a term
310
    fn args(args: ParseNode) -> ParseResult<Vec<ATerm>> {
311
        match_nodes!(args.into_children();
312
            [term(term_args)..] => {
313
                Ok(term_args.collect())
314
            }
315
        )
316
    }
317

            
318
    /// Parse an identifier
319
    fn identifier(id: ParseNode) -> ParseResult<String> {
320
        Ok(id.as_str().to_string())
321
    }
322

            
323
    /// Ignored rules
324
    fn EOI(_eof: ParseNode) -> ParseResult<()> {
325
        Ok(())
326
    }
327

            
328
    fn sorts(_sorts: ParseNode) -> ParseResult<()> {
329
        Ok(())
330
    }
331

            
332
    fn opns(_opns: ParseNode) -> ParseResult<()> {
333
        Ok(())
334
    }
335
}
336

            
337
#[cfg(test)]
338
mod tests {
339
    use super::*;
340

            
341
    #[test]
342
1
    fn test_raw_parsing() {
343
1
        assert!(RecParser::parse(Rule::single_term, "f(a").is_err());
344
1
        assert!(RecParser::parse(Rule::single_term, "f()").is_err());
345
1
        assert!(RecParser::parse(Rule::single_term, "f(a,)").is_err());
346
1
        assert!(RecParser::parse(Rule::single_term, "f").is_ok());
347
1
        assert!(RecParser::parse(Rule::single_term, "f(a)").is_ok());
348
1
        assert!(RecParser::parse(Rule::single_term, "f(a,b)").is_ok());
349
1
        assert!(RecParser::parse(Rule::single_rewrite_rule, "f(a,b) = g(x)").is_ok());
350
1
        assert!(RecParser::parse(Rule::single_rewrite_rule, "f(a,b) = g(x) if x = a").is_ok());
351
1
        assert!(RecParser::parse(Rule::single_rewrite_rule, "f(a,b) = g(x) if x<> a").is_ok());
352
1
        assert!(RecParser::parse(Rule::single_rewrite_rule, "f(a,b) = g(x) if x <= a").is_err());
353
1
        assert!(RecParser::parse(Rule::single_rewrite_rule, "f(a,b) = ").is_err());
354
1
    }
355

            
356
    #[test]
357
1
    fn test_parsing_rewrite_rule() {
358
1
        let expected = RewriteRuleSyntax {
359
1
            lhs: ATerm::from_string("f(x,b)").unwrap(),
360
1
            rhs: ATerm::from_string("g(x)").unwrap(),
361
1
            conditions: vec![
362
1
                ConditionSyntax {
363
1
                    lhs: ATerm::from_string("x").unwrap(),
364
1
                    rhs: ATerm::from_string("a").unwrap(),
365
1
                    equality: true,
366
1
                },
367
1
                ConditionSyntax {
368
1
                    lhs: ATerm::from_string("b").unwrap(),
369
1
                    rhs: ATerm::from_string("b").unwrap(),
370
1
                    equality: true,
371
1
                },
372
1
            ],
373
1
        };
374

            
375
1
        let mut parse_result =
376
1
            RecParser::parse(Rule::single_rewrite_rule, "f(x,b) = g(x) if x = a and-if b = b").unwrap();
377
1
        let node = ParseNode::new(parse_result.next().unwrap());
378
1
        let actual = RecParser::single_rewrite_rule(node).unwrap();
379

            
380
1
        assert_eq!(actual, expected);
381
1
    }
382

            
383
    #[test]
384
1
    fn test_variable_parsing() {
385
1
        let mut parse_result = RecParser::parse(Rule::var_decl, "X Y Val Max : Nat").unwrap();
386
1
        let node = ParseNode::new(parse_result.next().unwrap());
387
1
        let result = RecParser::var_decl(node).unwrap();
388

            
389
1
        assert_eq!(result, vec!["X", "Y", "Val", "Max"]);
390
1
    }
391

            
392
    #[test]
393
1
    fn test_parsing_rec() {
394
1
        assert!(
395
1
            RecParser::parse(
396
1
                Rule::rec_spec,
397
1
                include_str!("../../../examples/REC/rec/missionaries.rec")
398
1
            )
399
1
            .is_ok()
400
        );
401
1
    }
402

            
403
    #[test]
404
1
    fn loading_rec() {
405
1
        let _ = parse_rec(include_str!("../../../examples/REC/rec/missionaries.rec"), None);
406
1
    }
407
}