1
use std::collections::HashSet;
2
use std::fmt;
3
use std::fs::File;
4
use std::io::Write;
5
use std::path::Path;
6
use std::path::PathBuf;
7

            
8
use indoc::indoc;
9

            
10
use itertools::Itertools;
11
use merc_aterm::Term;
12
use merc_sabre::AnnouncementInnermost;
13
use merc_sabre::RewriteSpecification;
14
use merc_sabre::SetAutomaton;
15
use merc_sabre::matching::conditions::EMACondition;
16
use merc_sabre::matching::nonlinear::EquivalenceClass;
17
use merc_sabre::utilities::Config;
18
use merc_sabre::utilities::DataPosition;
19
use merc_sabre::utilities::TermStack;
20
use merc_utilities::MercError;
21

            
22
use crate::indenter::IndentFormatter;
23

            
24
/// Generates Rust code for term rewriting based on the provided specification.
25
///
26
/// Takes a rewrite specification and a source directory path, and generates the
27
/// necessary code for term rewriting using an automaton-based approach.
28
1
pub fn generate(spec: &RewriteSpecification, source_dir: &Path) -> Result<(), MercError> {
29
1
    let mut file = File::create(PathBuf::from(source_dir).join("lib.rs"))?;
30

            
31
1
    let mut formatter = IndentFormatter::new(&mut file);
32
1
    let apma = SetAutomaton::new(spec, AnnouncementInnermost::new, true);
33
1
    debug_assert!(!apma.states().is_empty(), "Automaton must have at least one state");
34

            
35
    // Emit transitions in a deterministic (state, symbol) order so the generated
36
    // source is reproducible and diffable.
37
1
    let sorted_transitions: Vec<(usize, usize, &_)> = apma
38
1
        .iter_transitions()
39
192
        .sorted_by_key(|(from, symbol, _)| (*from, *symbol))
40
1
        .collect();
41

            
42
    // Write imports and the main rewrite function
43
1
    writeln!(
44
1
        &mut formatter,
45
        indoc! {"#![allow(unused_variables)]
46
        #![allow(improper_ctypes_definitions)]
47

            
48
        use std::ffi::c_void;
49

            
50
        use merc_sabre_ffi::set_rewrite_vtable;
51
        use merc_sabre_ffi::SabreRewriteVTable;
52
        use merc_sabre_ffi::DataExpressionFFI;
53
        use merc_sabre_ffi::DataExpressionRefFFI;
54

            
55
        /// The initialisation function used to install the host vtable into the shared library.
56
        /// All term pool access is routed back into the host through it.
57
        #[unsafe(no_mangle)]
58
        pub unsafe extern \"C-unwind\" fn initialise(vtable: *mut c_void) {{
59
            unsafe {{ set_rewrite_vtable(vtable as *const SabreRewriteVTable); }}
60
        }}
61

            
62
        /// Generic rewrite function using the innermost strategy.
63
        ///
64
        /// First rewrites all arguments to normal form, reconstructs the term,
65
        /// and then tries to match the reconstructed term using the automaton.
66
        #[unsafe(no_mangle)]
67
        pub unsafe extern \"C-unwind\" fn rewrite(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
68
            match term.arity() {{
69
                0 => rewrite_arity_0(term),
70
                1 => rewrite_arity_1(term),
71
                2 => rewrite_arity_2(term),
72
                3 => rewrite_arity_3(term),
73
                4 => rewrite_arity_4(term),
74
                5 => rewrite_arity_5(term),
75
                6 => rewrite_arity_6(term),
76
                7 => rewrite_arity_7(term),
77
                _ => rewrite_arity_generic(term),
78
            }}
79
        }}
80

            
81
        /// Try to match the given term using the automaton and apply a rewrite rule.
82
        /// If no rule matches, returns the term unchanged.
83
        fn match_term(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
84
            match_0(&term.copy())
85
        }}
86

            
87
        /// Rewrite arity 0 (constant term)
88
        fn rewrite_arity_0(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
89
            match_term(&term.copy())
90
        }}
91

            
92
        /// Rewrite arity 1
93
        fn rewrite_arity_1(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
94
            unsafe {{
95
                let arg0 = rewrite(&term.data_arg(0));
96
                let symbol = term.data_function_symbol().into();
97
                let reconstructed = DataExpressionFFI::create(symbol, &[arg0.copy()]);
98
                match_term(&reconstructed.copy())
99
            }}
100
        }}
101

            
102
        /// Rewrite arity 2
103
        fn rewrite_arity_2(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
104
            unsafe {{
105
                let arg0 = rewrite(&term.data_arg(0));
106
                let arg1 = rewrite(&term.data_arg(1));
107
                let symbol = term.data_function_symbol().into();
108
                let reconstructed = DataExpressionFFI::create(symbol, &[arg0.copy(), arg1.copy()]);
109
                match_term(&reconstructed.copy())
110
            }}
111
        }}
112

            
113
        /// Rewrite arity 3
114
        fn rewrite_arity_3(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
115
            unsafe {{
116
                let arg0 = rewrite(&term.data_arg(0));
117
                let arg1 = rewrite(&term.data_arg(1));
118
                let arg2 = rewrite(&term.data_arg(2));
119
                let symbol = term.data_function_symbol().into();
120
                let reconstructed = DataExpressionFFI::create(symbol, &[arg0.copy(), arg1.copy(), arg2.copy()]);
121
                match_term(&reconstructed.copy())
122
            }}
123
        }}
124

            
125
        /// Rewrite arity 4
126
        fn rewrite_arity_4(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
127
            unsafe {{
128
                let arg0 = rewrite(&term.data_arg(0));
129
                let arg1 = rewrite(&term.data_arg(1));
130
                let arg2 = rewrite(&term.data_arg(2));
131
                let arg3 = rewrite(&term.data_arg(3));
132
                let symbol = term.data_function_symbol().into();
133
                let reconstructed = DataExpressionFFI::create(symbol, &[arg0.copy(), arg1.copy(), arg2.copy(), arg3.copy()]);
134
                match_term(&reconstructed.copy())
135
            }}
136
        }}
137

            
138
        /// Rewrite arity 5
139
        fn rewrite_arity_5(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
140
            unsafe {{
141
                let arg0 = rewrite(&term.data_arg(0));
142
                let arg1 = rewrite(&term.data_arg(1));
143
                let arg2 = rewrite(&term.data_arg(2));
144
                let arg3 = rewrite(&term.data_arg(3));
145
                let arg4 = rewrite(&term.data_arg(4));
146
                let symbol = term.data_function_symbol().into();
147
                let reconstructed = DataExpressionFFI::create(symbol, &[arg0.copy(), arg1.copy(), arg2.copy(), arg3.copy(), arg4.copy()]);
148
                match_term(&reconstructed.copy())
149
            }}
150
        }}
151

            
152
        /// Rewrite arity 6
153
        fn rewrite_arity_6(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
154
            unsafe {{
155
                let arg0 = rewrite(&term.data_arg(0));
156
                let arg1 = rewrite(&term.data_arg(1));
157
                let arg2 = rewrite(&term.data_arg(2));
158
                let arg3 = rewrite(&term.data_arg(3));
159
                let arg4 = rewrite(&term.data_arg(4));
160
                let arg5 = rewrite(&term.data_arg(5));
161
                let symbol = term.data_function_symbol().into();
162
                let reconstructed = DataExpressionFFI::create(symbol, &[arg0.copy(), arg1.copy(), arg2.copy(), arg3.copy(), arg4.copy(), arg5.copy()]);
163
                match_term(&reconstructed.copy())
164
            }}
165
        }}
166

            
167
        /// Rewrite arity 7
168
        fn rewrite_arity_7(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
169
            unsafe {{
170
                let arg0 = rewrite(&term.data_arg(0));
171
                let arg1 = rewrite(&term.data_arg(1));
172
                let arg2 = rewrite(&term.data_arg(2));
173
                let arg3 = rewrite(&term.data_arg(3));
174
                let arg4 = rewrite(&term.data_arg(4));
175
                let arg5 = rewrite(&term.data_arg(5));
176
                let arg6 = rewrite(&term.data_arg(6));
177
                let symbol = term.data_function_symbol().into();
178
                let reconstructed = DataExpressionFFI::create(symbol, &[arg0.copy(), arg1.copy(), arg2.copy(), arg3.copy(), arg4.copy(), arg5.copy(), arg6.copy()]);
179
                match_term(&reconstructed.copy())
180
            }}
181
        }}
182

            
183
        /// Rewrite arity 8 or higher (uses vector allocation)
184
        fn rewrite_arity_generic(term: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{
185
            unsafe {{
186
                let arity = term.arity();
187
                let args: Vec<DataExpressionFFI> = (0..arity).map(|i| rewrite(&term.data_arg(i))).collect();
188
                let arg_refs: Vec<DataExpressionRefFFI> = args.iter().map(|a| a.copy()).collect();
189
                let symbol = term.data_function_symbol().into();
190
                let reconstructed = DataExpressionFFI::create(symbol, &arg_refs);
191
                match_term(&reconstructed.copy())
192
            }}
193
        }}
194
        "}
195
    )?;
196

            
197
    // Keep track of all positions that need to be read from terms, to generate getters for them later.
198
1
    let mut positions: HashSet<DataPosition> = HashSet::new();
199

            
200
    // Introduce a match function for every state of the set automaton.
201
4
    for (index, state) in apma.states().iter().enumerate() {
202
4
        writeln!(&mut formatter, "// Position {}", state.label())?;
203

            
204
4
        for goal in state.match_goals() {
205
            writeln!(&mut formatter, "// Goal {goal:?}")?;
206
        }
207

            
208
4
        writeln!(
209
4
            &mut formatter,
210
            "fn match_{index}(t: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{"
211
        )?;
212

            
213
4
        let indent = formatter.indent();
214

            
215
4
        writeln!(
216
4
            &mut formatter,
217
            "let arg = get_data_position_{}(t);",
218
4
            UnderscoreFormatter(state.label())
219
        )?;
220
4
        writeln!(&mut formatter, "let symbol = arg.data_function_symbol();")?;
221

            
222
4
        positions.insert(state.label().clone());
223

            
224
4
        writeln!(&mut formatter, "match symbol.operation_id() {{")?;
225

            
226
4
        let match_indent = formatter.indent();
227
80
        for (from, symbol, transition) in &sorted_transitions {
228
            // Consider only transitions that match the current state index
229
80
            if *from == index {
230
20
                writeln!(&mut formatter, "{symbol} => {{")?;
231

            
232
                // Indent the case block
233
20
                let case_indent = formatter.indent();
234
20
                writeln!(&mut formatter, "// Symbol {}", transition.symbol)?;
235

            
236
20
                for (ann_idx, (announcement, _annotation)) in transition.announcements.iter().enumerate() {
237
6
                    writeln!(&mut formatter, "// Announcement {announcement:?}")?;
238

            
239
6
                    writeln!(
240
6
                        &mut formatter,
241
                        "if check_equivalence_classes_{index}_{symbol}_{ann_idx}(t) && check_condition_{index}_{symbol}_{ann_idx}(t) {{",
242
                    )?;
243

            
244
6
                    let condition_indent = formatter.indent();
245
6
                    writeln!(
246
6
                        &mut formatter,
247
                        "return rewrite_term_stack_{index}_{symbol}_{ann_idx}(t)"
248
                    )?;
249
6
                    drop(condition_indent);
250

            
251
6
                    writeln!(&mut formatter, "}}")?;
252
                }
253

            
254
20
                if transition.destinations.is_empty() {
255
17
                    writeln!(&mut formatter, "t.protect()")?;
256
3
                }
257

            
258
20
                for (position, to) in &transition.destinations {
259
3
                    positions.insert(position.clone());
260

            
261
3
                    writeln!(&mut formatter, "match_{to}(&t)",)?;
262
                }
263

            
264
20
                drop(case_indent);
265
20
                writeln!(&mut formatter, "}}")?;
266
60
            }
267
        }
268

            
269
        // No match
270
4
        writeln!(&mut formatter, "_ => {{")?;
271

            
272
        // Indent the default case
273
        {
274
4
            let _default_indent = formatter.indent();
275
4
            writeln!(&mut formatter, "t.protect()")?;
276
        }
277

            
278
4
        writeln!(&mut formatter, "}}")?;
279

            
280
4
        drop(match_indent);
281
4
        writeln!(&mut formatter, "}}")?;
282

            
283
4
        drop(indent);
284
4
        writeln!(&mut formatter, "}}")?;
285
4
        writeln!(&mut formatter)?;
286
    }
287

            
288
1
    writeln!(formatter, "// term stack rewrite functions")?;
289
1
    writeln!(formatter)?;
290
20
    for (from, symbol, transition) in &sorted_transitions {
291
20
        for (annotation_index, (_announcement, annotation)) in transition.announcements.iter().enumerate() {
292
6
            generate_rewrite_term_stack(
293
6
                &mut formatter,
294
6
                *from,
295
6
                *symbol,
296
6
                annotation_index,
297
6
                &mut positions,
298
6
                &annotation.rhs_stack,
299
            )?;
300
        }
301
    }
302

            
303
1
    writeln!(formatter, "// check condition functions")?;
304
1
    writeln!(formatter)?;
305
20
    for (from, symbol, transition) in &sorted_transitions {
306
20
        for (annotation_index, (_announcement, annotation)) in transition.announcements.iter().enumerate() {
307
6
            generate_check_condition(
308
6
                &mut formatter,
309
6
                *from,
310
6
                *symbol,
311
6
                annotation_index,
312
6
                &mut positions,
313
6
                &annotation.conditions,
314
            )?;
315
        }
316
    }
317

            
318
1
    writeln!(formatter, "// equivalence classes check")?;
319
1
    writeln!(formatter)?;
320
20
    for (from, symbol, transition) in &sorted_transitions {
321
20
        for (annotation_index, (_announcement, annotation)) in transition.announcements.iter().enumerate() {
322
6
            generate_check_equivalence_classes(
323
6
                &mut formatter,
324
6
                *from,
325
6
                *symbol,
326
6
                annotation_index,
327
6
                &mut positions,
328
6
                &annotation.equivalence_classes,
329
            )?;
330
        }
331
    }
332

            
333
1
    writeln!(formatter, "// position getters")?;
334
1
    writeln!(formatter)?;
335
1
    generate_position_getters(&mut formatter, &positions)?;
336

            
337
1
    formatter.flush()?;
338

            
339
1
    Ok(())
340
1
}
341

            
342
/// Emits variable declarations and construct calls for the given TermStack into
343
/// `formatter`, prefixing every variable name with `prefix`.
344
///
345
/// Uses a virtual-stack simulation that mirrors `InnermostStack::integrate` +
346
/// `evaluate_with`: all non-output slots start as `[1 .. stack_size)`, and each
347
/// reversed-BFS construct drains the last `arity` indices as its arguments.
348
/// Returns the name of the variable that holds the final result.
349
6
fn generate_rewrite_term_stack_impl(
350
6
    formatter: &mut IndentFormatter<File>,
351
6
    prefix: &str,
352
6
    term_stack: &TermStack,
353
6
    positions: &mut HashSet<DataPosition>,
354
6
) -> Result<String, MercError> {
355
    // Declare variables bound to subterms of the matched term.
356
8
    for (position, stack_index) in &term_stack.variables {
357
8
        positions.insert(position.clone());
358
8
        writeln!(
359
8
            formatter,
360
            "let {prefix}var_{stack_index} = get_data_position_{}(t);",
361
8
            UnderscoreFormatter(position)
362
        )?;
363
    }
364

            
365
    // `integrate` allocates slots 1..stack_size for non-output entries.
366
    // Evaluation pops constructs from the config stack (LIFO = reversed BFS)
367
    // and each one consumes the last `arity` elements of the term-stack.
368
    // Simulate that here so we know which var_N holds each argument.
369
6
    let mut virtual_stack: Vec<usize> = (1..term_stack.stack_size).collect();
370

            
371
6
    let read = term_stack.innermost_stack.read();
372
10
    for config in read.iter().rev() {
373
10
        match config {
374
10
            Config::Construct(symbol, arity, stack_index) => {
375
10
                let len = virtual_stack.len();
376
10
                let arg_indices: Vec<usize> = virtual_stack.drain(len - arity..).collect();
377

            
378
10
                if *arity > 0 {
379
8
                    writeln!(
380
8
                        formatter,
381
                        "let {prefix}var_{stack_index} = match_term(&DataExpressionFFI::create(unsafe {{ DataExpressionRefFFI::from_ptr({:?}) }}, &[{}]).copy());",
382
8
                        symbol.shared().ptr().as_ptr() as *mut () as usize,
383
8
                        arg_indices
384
8
                            .iter()
385
12
                            .map(|i| format!("{prefix}var_{i}.copy()"))
386
8
                            .format(", ")
387
                    )?;
388
                } else {
389
2
                    writeln!(
390
2
                        formatter,
391
                        "let {prefix}var_{stack_index} = match_term(&DataExpressionFFI::constant(unsafe {{ DataExpressionRefFFI::from_ptr({:?}) }}).copy());",
392
2
                        symbol.shared().ptr().as_ptr() as *mut () as usize,
393
                    )?;
394
                }
395
            }
396
            Config::Term(data_expression_ref, index) => {
397
                writeln!(
398
                    formatter,
399
                    "let {prefix}var_{index} = match_term(&unsafe {{ DataExpressionRefFFI::from_ptr({:?}) }});",
400
                    data_expression_ref.shared().ptr().as_ptr() as *mut () as usize,
401
                )?;
402
            }
403
            Config::Rewrite(_) | Config::Return() => {
404
                unreachable!("The term stack never contains these configurations")
405
            }
406
        }
407
    }
408

            
409
    // Determine the result variable name.
410
6
    let result = if let Some(stack_index) = term_stack.innermost_stack.read().iter().find_map(|c| {
411
5
        if let Config::Construct(_, _, idx) = c {
412
5
            Some(*idx)
413
        } else {
414
            None
415
        }
416
5
    }) {
417
        // The root of the BFS tree (first construct) holds the final result.
418
5
        format!("{prefix}var_{stack_index}")
419
1
    } else if term_stack.stack_size == 1 && term_stack.variables.len() == 1 {
420
        // RHS is a bare variable; return the matched subterm directly.
421
1
        let (_, stack_index) = &term_stack.variables[0];
422
1
        format!("{prefix}var_{stack_index}")
423
    } else {
424
        // Should not occur for valid rewrite rules.
425
        "t".to_string()
426
    };
427

            
428
6
    Ok(result)
429
6
}
430

            
431
/// Generates a `rewrite_term_stack_{index}_{symbol}_{ann_idx}` function that
432
/// constructs the RHS of a rewrite rule and returns it in normal form.
433
6
fn generate_rewrite_term_stack(
434
6
    formatter: &mut IndentFormatter<File>,
435
6
    index: usize,
436
6
    symbol: usize,
437
6
    ann_idx: usize,
438
6
    positions: &mut HashSet<DataPosition>,
439
6
    term_stack: &TermStack,
440
6
) -> Result<(), MercError> {
441
6
    writeln!(formatter, "/// Rewriting {:?}", term_stack)?;
442
6
    writeln!(
443
6
        formatter,
444
        "fn rewrite_term_stack_{index}_{symbol}_{ann_idx}(t: &DataExpressionRefFFI<'_>) -> DataExpressionFFI {{"
445
    )?;
446

            
447
6
    let indent = formatter.indent();
448
6
    let result_var = generate_rewrite_term_stack_impl(formatter, "", term_stack, positions)?;
449
6
    writeln!(formatter, "{result_var}.protect()")?;
450
6
    drop(indent);
451

            
452
6
    writeln!(formatter, "}}")?;
453
6
    writeln!(formatter)?;
454

            
455
6
    Ok(())
456
6
}
457

            
458
/// Generates a `check_condition_{index}_{symbol}_{annotation_index}` function that
459
/// evaluates each condition's LHS and RHS, rewrites both, and returns `false`
460
/// if any condition is violated.
461
6
fn generate_check_condition(
462
6
    formatter: &mut IndentFormatter<File>,
463
6
    index: usize,
464
6
    symbol: usize,
465
6
    annotation_index: usize,
466
6
    positions: &mut HashSet<DataPosition>,
467
6
    conditions: &[EMACondition],
468
6
) -> Result<(), MercError> {
469
6
    writeln!(formatter, "/// Checking condition {:?}", conditions)?;
470
6
    writeln!(
471
6
        formatter,
472
        "fn check_condition_{index}_{symbol}_{annotation_index}(t: &DataExpressionRefFFI<'_>) -> bool {{"
473
    )?;
474

            
475
6
    let indent = formatter.indent();
476

            
477
6
    for (cond_idx, condition) in conditions.iter().enumerate() {
478
        writeln!(formatter, "// Condition {cond_idx}")?;
479
        writeln!(formatter, "{{")?;
480

            
481
        let cond_indent = formatter.indent();
482

            
483
        let lhs_var = generate_rewrite_term_stack_impl(
484
            formatter,
485
            &format!("c{cond_idx}_lhs_"),
486
            &condition.lhs_term_stack,
487
            positions,
488
        )?;
489
        let rhs_var = generate_rewrite_term_stack_impl(
490
            formatter,
491
            &format!("c{cond_idx}_rhs_"),
492
            &condition.rhs_term_stack,
493
            positions,
494
        )?;
495

            
496
        // With maximal sharing, pointer equality ↔ term equality.
497
        if condition.equality {
498
            writeln!(formatter, "if {lhs_var}.shared() != {rhs_var}.shared() {{")?;
499
        } else {
500
            writeln!(formatter, "if {lhs_var}.shared() == {rhs_var}.shared() {{")?;
501
        }
502
        {
503
            let body_indent = formatter.indent();
504
            writeln!(formatter, "return false;")?;
505
            drop(body_indent);
506
        }
507
        writeln!(formatter, "}}")?;
508

            
509
        drop(cond_indent);
510
        writeln!(formatter, "}}")?;
511
    }
512

            
513
6
    writeln!(formatter, "true")?;
514
6
    drop(indent);
515

            
516
6
    writeln!(formatter, "}}")?;
517
6
    Ok(())
518
6
}
519

            
520
/// Generates a `check_equivalence_classes_{index}_{symbol}_{annotation_index}` function
521
/// that verifies all positions belonging to the same variable refer to identical
522
/// subterms (required for non-linear patterns).
523
6
fn generate_check_equivalence_classes(
524
6
    formatter: &mut IndentFormatter<File>,
525
6
    index: usize,
526
6
    symbol: usize,
527
6
    annotation_index: usize,
528
6
    positions: &mut HashSet<DataPosition>,
529
6
    equivalence_classes: &[EquivalenceClass],
530
6
) -> Result<(), MercError> {
531
6
    writeln!(formatter, "/// Check equivalence classes {:?}", equivalence_classes)?;
532
6
    writeln!(
533
6
        formatter,
534
        "fn check_equivalence_classes_{index}_{symbol}_{annotation_index}<'a>(t: &DataExpressionRefFFI<'a>) -> bool {{",
535
    )?;
536

            
537
6
    let indent = formatter.indent();
538

            
539
6
    for ec in equivalence_classes {
540
        debug_assert!(
541
            ec.positions.len() >= 2,
542
            "An equivalence class must contain at least two positions"
543
        );
544

            
545
        writeln!(formatter, "// Variable {} must match at all positions", ec.variable)?;
546

            
547
        let first_pos = &ec.positions[0];
548
        positions.insert(first_pos.clone());
549
        writeln!(
550
            formatter,
551
            "let base = get_data_position_{}(t);",
552
            UnderscoreFormatter(first_pos)
553
        )?;
554

            
555
        for other_pos in &ec.positions[1..] {
556
            positions.insert(other_pos.clone());
557
            writeln!(
558
                formatter,
559
                "if base.shared() != get_data_position_{}(t).shared() {{ return false; }}",
560
                UnderscoreFormatter(other_pos)
561
            )?;
562
        }
563
    }
564

            
565
6
    writeln!(formatter, "true")?;
566
6
    drop(indent);
567

            
568
6
    writeln!(formatter, "}}")?;
569
6
    writeln!(formatter)?;
570
6
    Ok(())
571
6
}
572

            
573
/// Generates getter functions for all positions that must be read from terms.
574
1
fn generate_position_getters(
575
1
    formatter: &mut IndentFormatter<File>,
576
1
    positions: &HashSet<DataPosition>,
577
1
) -> Result<(), MercError> {
578
    // Emit in a deterministic order; `positions` is a `HashSet`.
579
4
    for position in positions.iter().sorted() {
580
4
        writeln!(formatter, "/// Get position {:?} from term", position)?;
581
4
        writeln!(
582
4
            formatter,
583
            "fn get_data_position_{}<'a>(t: &DataExpressionRefFFI<'a>) -> DataExpressionRefFFI<'a> {{",
584
4
            UnderscoreFormatter(position)
585
        )?;
586

            
587
4
        let indent = formatter.indent();
588

            
589
4
        if position.is_empty() {
590
1
            writeln!(formatter, "t.copy()")?;
591
        } else {
592
3
            write!(formatter, "t")?;
593

            
594
4
            for index in position.indices().iter() {
595
4
                write!(formatter, ".data_arg({})", index - 1)?; // positions are 1-indexed
596
            }
597

            
598
            // Add newline after the chain of method calls
599
3
            writeln!(formatter)?;
600
        }
601

            
602
4
        drop(indent);
603
4
        writeln!(formatter, "}}")?;
604
4
        writeln!(formatter)?;
605
    }
606

            
607
1
    Ok(())
608
1
}
609

            
610
struct UnderscoreFormatter<'a>(&'a DataPosition);
611

            
612
impl fmt::Display for UnderscoreFormatter<'_> {
613
16
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614
16
        if self.0.is_empty() {
615
2
            write!(f, "epsilon")?;
616
        } else {
617
14
            let mut first = true;
618
19
            for p in self.0.indices().iter() {
619
19
                if first {
620
14
                    write!(f, "{p}")?;
621
14
                    first = false;
622
                } else {
623
5
                    write!(f, "_{p}")?;
624
                }
625
            }
626
        }
627

            
628
16
        Ok(())
629
16
    }
630
}