yaspar-ir 2.7.2

This crate provides a few representations of SMT scripts and other functionalities.
Documentation
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::allocator::{
    CommandAllocator, LocalVarAllocator, ObjectAllocatorExt, SortAllocator, StrAllocator,
    TermAllocator,
};
use crate::ast::alg::{SigIndex, VarBinding};
use crate::ast::ctx::checked::ScopedSortApi;
use crate::ast::ctx::{
    Arena, Command, ConstructorDec, Context, DatatypeDec, DatatypeDef, Sig, Sort, Str, TC, TCEnv,
    Theory,
};
use crate::ast::{
    DatatypeFunction, FunctionDef, FunctionMeta, FunctionMetaDefined, Identifier, Index, Local,
    SymbolQuote, Typecheck, alg,
};
use crate::locenv::{LocEnv, sanitize_bindings};
use crate::raw::instance::HasArena;
use crate::traits::{AllocatableString, Contains};
use std::borrow::Borrow;
use std::collections::{HashMap, HashSet};

struct DatatypeDefs {
    /// The names of datatypes to be defined
    new_dts: Vec<Str>,
    dt_defs: HashMap<Str, DatatypeDef>,
}

/// A builder context for declaring one or more datatypes (`declare-datatype(s)`).
///
/// Created via [`Context::build_datatypes`]. Datatype names and their sort parameters are
/// declared upfront. For each datatype, call [`build_datatype`](Self::build_datatype) to obtain
/// a [`DtDeclContext`], add constructors inside it, and finalize with
/// [`DtDeclContext::typed_datatype`]. Once all datatypes are defined, call
/// [`typed_declare_datatypes`](Self::typed_declare_datatypes) to produce the command.
///
/// The context validates non-emptiness (every datatype must have at least one non-recursive
/// constructor path), constructor/selector name uniqueness, and sort parameter scoping.
///
/// If the context is dropped without calling `typed_declare_datatypes`, no changes are made to
/// the global context (the operation is transactional).
///
/// For simple enums without fields, use [`Context::typed_enum`] as a shorthand.
pub struct DatatypeContext<'a> {
    context: &'a mut Context,
    /// Invariant: almost always Some; it is only None when we confirm we have managed to build
    /// datatypes properly
    defs: Option<DatatypeDefs>,
    undefined_dts: HashSet<Str>,
    additional_symbols: HashSet<Str>,
}

/// A builder context for declaring the constructors of a single datatype within a [`DatatypeContext`].
///
/// Created via [`DatatypeContext::build_datatype`]. Sort parameters (e.g. `X` in `List X`) are
/// in scope and can be referenced via [`ScopedSortApi::wf_sort`]. Add constructors with
/// [`build_datatype_constructor`](Self::build_datatype_constructor) or
/// [`build_datatype_constructor_nullary`](Self::build_datatype_constructor_nullary), then
/// finalize with [`typed_datatype`](Self::typed_datatype).
pub struct DtDeclContext<'a, 'b> {
    parent: &'b mut DatatypeContext<'a>,
    params: Vec<VarBinding<Str, ()>>,
    current: Str,
    ctors: Vec<ConstructorDec>,
    additional_symbols: HashSet<Str>,
}

impl DatatypeDefs {
    fn get_datatype_defs(mut self) -> Vec<DatatypeDef> {
        let mut dt_defs = vec![];
        for name in self.new_dts {
            dt_defs.push(self.dt_defs.remove(&name).unwrap());
        }
        dt_defs
    }

    fn find_empty_dt_name(&self) -> Option<Str> {
        check_dt_emptiness(&self.dt_defs)
    }
}

impl<'a> DatatypeContext<'a> {
    pub(crate) fn new<T, P, S>(context: &'a mut Context, dt_names_and_sorts: T) -> TC<Self>
    where
        T: IntoIterator<Item = (S, P)>,
        P: IntoIterator<Item = S>,
        S: AllocatableString<Arena>,
    {
        context.check_logic()?;
        context.check_support_theory(Theory::Datatypes)?;
        let mut new_dts = vec![];
        let mut dt_defs = HashMap::new();
        for (name, sort_names) in dt_names_and_sorts {
            let symbol = name.allocate(context.arena());
            context.can_add_sort(&symbol).map_err(|_| {
                format!(
                    "sort {}{} cannot be added to the sort table!",
                    symbol.sym_quote(),
                    name.display_meta_data()
                )
            })?;
            new_dts.push(symbol.clone());
            let params = sort_names
                .into_iter()
                .map(|x| x.allocate(context.arena()))
                .collect::<Vec<_>>();
            sanitize_bindings(&params, |v| v.clone())?;
            dt_defs.insert(
                symbol.clone(),
                DatatypeDef {
                    name: symbol,
                    dec: DatatypeDec {
                        params,
                        constructors: vec![],
                    },
                },
            );
        }
        if new_dts.is_empty() {
            return Err("TC: should define at least one datatype!".into());
        }

        let undefined_dts = new_dts.iter().cloned().collect::<HashSet<_>>();
        Self {
            context,
            defs: Some(DatatypeDefs { new_dts, dt_defs }),
            undefined_dts,
            additional_symbols: Default::default(),
        }
        .initial_insert_sorts()
    }

    fn initial_insert_sorts(self) -> TC<Self> {
        for (name, df) in self.defs.as_ref().map(|ds| ds.dt_defs.iter()).unwrap() {
            self.context.add_sort(name.clone(), df.dec.params.len())?;
        }
        Ok(self)
    }

    fn remove_sorts(&mut self) {
        if let Some(ds) = &self.defs {
            for name in &ds.new_dts {
                self.context.remove_sort(name)
            }
        }
    }

    fn insert_dt_sorts(&mut self) {
        for (name, df) in self.defs.as_ref().map(|ds| ds.dt_defs.iter()).unwrap() {
            // we know the insertion will be successful as it happens after initial_insert_sorts
            self.context
                .add_dt_sort(name.clone(), df.dec.clone())
                .unwrap();
        }
    }

    /// Return the undefined datatypes
    pub fn undefined_datatypes(&self) -> &HashSet<Str> {
        &self.undefined_dts
    }

    /// Create a context to build an individual datatype
    pub fn build_datatype<'b, S>(&'b mut self, name: S) -> TC<DtDeclContext<'a, 'b>>
    where
        S: AllocatableString<Arena>,
    {
        DtDeclContext::new(self, name)
    }

    /// Invoke this method if all datatypes are built.
    ///
    /// If all datatypes are well-formed, then return a command for building the datatypes.
    //noinspection RsUnwrap
    pub fn typed_declare_datatypes(mut self) -> TC<Command> {
        if !self.undefined_dts.is_empty() {
            return Err(format!(
                "TC: remaining undefined datatypes: {}",
                self.undefined_dts
                    .iter()
                    .map(|s| s.sym_quote())
                    .collect::<Vec<_>>()
                    .join(", ")
            ));
        }
        if let Some(s) = self.defs.as_ref().and_then(|ds| ds.find_empty_dt_name()) {
            return Err(format!(
                "TC: datatype {} could be an empty sort! a base case is required!",
                s.sym_quote()
            ));
        }
        // at this point, there is no empty datatype; everything is good
        // we first remove the corresponding temporary uninterpreted sorts to insert the right sorts
        // to the context.
        self.remove_sorts();
        // we insert the datatype sorts
        self.insert_dt_sorts();
        let defs = std::mem::take(&mut self.defs).unwrap();
        // self.defs has been replaced with None, so Drop is a noop.
        let mut dt_defs = defs.get_datatype_defs();

        extend_symbols_about_datatypes(&dt_defs, self.context);
        if dt_defs.len() == 1 {
            let df = dt_defs.pop().unwrap();
            Ok(self.context.declare_datatype(df.name, df.dec))
        } else {
            Ok(self.context.declare_datatypes(dt_defs))
        }
    }
}

// if the datatype creation is not successful, we should clean up the [Context]
impl Drop for DatatypeContext<'_> {
    fn drop(&mut self) {
        self.remove_sorts();
    }
}

impl HasArena for DatatypeContext<'_> {
    #[inline]
    fn arena(&mut self) -> &mut Arena {
        self.context.arena()
    }
}

impl<'a, 'b> DtDeclContext<'a, 'b> {
    fn new<S>(parent: &'b mut DatatypeContext<'a>, s: S) -> TC<Self>
    where
        S: AllocatableString<Arena>,
    {
        let sym = s.allocate(parent.arena());
        if !parent.undefined_dts.contains(&sym) {
            return Err(format!(
                "TC: {}{} is not a remaining datatype to be defined!",
                sym.sym_quote(),
                s.display_meta_data()
            ));
        }
        let dt = parent
            .defs
            .as_ref()
            .map(|ds| ds.dt_defs.get(&sym).unwrap())
            .unwrap();
        let params = dt
            .dec
            .params
            .iter()
            .map(|s| VarBinding(s.clone(), 0, ()))
            .collect::<Vec<_>>();
        Ok(Self {
            parent,
            params,
            current: sym,
            ctors: vec![],
            additional_symbols: Default::default(),
        })
    }

    fn check_name<S>(&mut self, name: S, additional: &mut HashSet<Str>) -> TC<Str>
    where
        S: AllocatableString<Arena>,
    {
        let symbol = name.allocate(self.arena());
        self.parent.context.can_add_symbol(&symbol)?;
        if self.additional_symbols.contains(&symbol)
            || self.parent.additional_symbols.contains(&symbol)
            || additional.contains(&symbol)
        {
            Err(format!(
                "symbol {}{} is already defined!",
                symbol.sym_quote(),
                name.display_meta_data()
            ))
        } else {
            additional.insert(symbol.clone());
            Ok(symbol)
        }
    }

    /// Create a constructor with a given list of arguments
    pub fn build_datatype_constructor<T, S>(&mut self, ctor: S, args: T) -> TC<()>
    where
        T: IntoIterator<Item = (S, Sort)>,
        S: AllocatableString<Arena>,
    {
        let mut added_names = HashSet::new();
        let ctor = self.check_name(ctor, &mut added_names)?;
        let mut is_sym = "is-".to_string();
        is_sym.push_str(ctor.inner());
        let is_sym = self.allocate_symbol(&is_sym);
        self.check_name(is_sym, &mut added_names)?;
        let mut new_args = vec![];
        for (name, sort) in args {
            let name = self.check_name(name, &mut added_names)?;
            let mut env = self
                .parent
                .context
                .get_sort_tcenv()
                .convert_to_new_local(LocEnv::Cons {
                    car: &self.params,
                    cdr: &LocEnv::Nil,
                });
            if !wf_sort(
                &self.parent.defs.as_ref().unwrap().new_dts,
                &sort,
                &mut env,
                true,
            ) {
                return Err(format!(
                    "TC: sort {sort} is not well-formed in datatype {}.",
                    self.current.sym_quote(),
                ));
            }
            new_args.push(VarBinding(name, 0, sort));
        }
        self.ctors.push(ConstructorDec {
            ctor,
            args: new_args,
        });
        self.additional_symbols.extend(added_names);
        Ok(())
    }

    /// c.f. [Self::build_datatype_constructor]
    pub fn build_datatype_constructor_nullary<S>(&mut self, ctor: S) -> TC<()>
    where
        S: AllocatableString<Arena>,
    {
        self.build_datatype_constructor::<[_; 0], S>(ctor, [])
    }

    /// Build a constructor given a declaration
    pub fn build_datatype_constructor_declaration<S, So>(
        &mut self,
        dec: &alg::ConstructorDec<S, So>,
    ) -> TC<()>
    where
        S: AllocatableString<Arena>,
        So: Typecheck<Self, Out = Sort>,
    {
        let args = dec
            .args
            .iter()
            .map(|a| {
                let so = a.2.type_check(self)?;
                Ok((&a.0, so))
            })
            .collect::<TC<Vec<_>>>()?;
        self.build_datatype_constructor(&dec.ctor, args)
    }

    /// Build all constructors when given a sequence of constructors
    pub fn build_datatype_constructor_declarations<S, So>(
        &mut self,
        decs: impl IntoIterator<Item: Borrow<alg::ConstructorDec<S, So>>>,
    ) -> TC<()>
    where
        S: AllocatableString<Arena>,
        So: Typecheck<Self, Out = Sort>,
    {
        for dec in decs {
            self.build_datatype_constructor_declaration(dec.borrow())?;
        }
        Ok(())
    }

    /// Define the current datatype
    pub fn typed_datatype(self) -> TC<()> {
        if self.ctors.is_empty() {
            return Err(format!(
                "TC: datatype {} has no constructor!",
                self.current.sym_quote()
            ));
        }
        self.parent
            .additional_symbols
            .extend(self.additional_symbols);
        self.parent.undefined_dts.remove(&self.current);
        self.parent
            .defs
            .as_mut()
            .and_then(|ds| ds.dt_defs.get_mut(&self.current))
            .unwrap()
            .dec
            .constructors = self.ctors;
        Ok(())
    }
}

impl HasArena for DtDeclContext<'_, '_> {
    #[inline]
    fn arena(&mut self) -> &mut Arena {
        self.parent.context.arena()
    }
}
impl ScopedSortApi for DtDeclContext<'_, '_> {
    #[inline]
    fn get_sort_tcenv(&mut self) -> TCEnv<'_, '_, ()> {
        self.parent
            .context
            .get_sort_tcenv()
            .convert_to_new_local(LocEnv::Cons {
                car: &self.params,
                cdr: &LocEnv::Nil,
            })
    }
}

/// Check whether a given sort is well-formed in a data type definition
///
/// Well-formedness condition requires that sorts in `names` can only appear as `top` symbols.
///
/// * names: the names of the datatypes being defined.
/// * top: whether the given sort is a top symbol
pub(crate) fn wf_sort(names: &[Str], sort: &Sort, env: &mut TCEnv<()>, top: bool) -> bool {
    if env.local.lookup(sort.sort_name()).is_some() {
        // if the sort is a locally bound sort variable, then it's well-formed.
        true
    } else {
        // otherwise, we know sort name is globally bound.
        if names.contains(sort.sort_name()) && !top {
            // now we are checking a sort that is being defined
            // a datatype can only appear as a top symbol
            false
        } else {
            // the current sort is a top symbol; it could be an existing symbol or a datatype
            // we must check the well-formedness of all sub-symbols
            for sub in &sort.1 {
                if !wf_sort(names, sub, env, false) {
                    return false;
                }
            }
            true
        }
    }
}

/// The actuator that computes the non-emptiness of the `current` datatype using DFS
///
/// * `nonemptiness` is the cached result of non-emptiness
/// * `defs` is a mapping from names to definitions.
/// * `stack` is a visiting stack for DFS
/// * `current` is the current datatype being looked at
///
/// Return true if the `current` datatype is non-empty.
fn check_emptiness_once<D: Borrow<DatatypeDef>>(
    nonemptiness: &mut HashMap<Str, Option<bool>>,
    defs: &HashMap<Str, D>,
    stack: &mut Vec<Str>,
    current: &Str,
) -> bool {
    if let Some(Some(r)) = nonemptiness.get(current) {
        // the non-emptiness of def has been determined, so there is nothing to work on.
        return *r;
    }

    let def = defs.get(current).unwrap().borrow(); // unwrap because we only handle datatypes
    // now we are working on def
    stack.push(def.name.clone());

    let params = &def.dec.params;
    let mut def_nonemptiness = false;
    for ctor in &def.dec.constructors {
        if def_nonemptiness {
            break;
        }
        let mut ctor_nonemptiness = true;
        for x in &ctor.args {
            if !ctor_nonemptiness {
                break;
            }
            if params.contains(x.2.sort_name()) {
                // a parameter is always non-empty.
                continue;
            }
            if let Some(r) = nonemptiness.get(x.2.sort_name()) {
                // now we are handling a recursively defined datatype
                if let Some(r) = r {
                    // the non-emptiness has been determined for x.2
                    ctor_nonemptiness = ctor_nonemptiness && *r;
                } else {
                    // the non-emptiness has not been determined for x.2
                    if stack.contains(x.2.sort_name()) {
                        // now we hit a loop, so we assume this sort is empty
                        ctor_nonemptiness = false
                    } else {
                        // now we have not seen this sort, so we must recursively compute its non-emptiness
                        let r = check_emptiness_once(nonemptiness, defs, stack, x.2.sort_name());
                        ctor_nonemptiness = ctor_nonemptiness && r;
                    }
                }
            }
        }
        def_nonemptiness = def_nonemptiness || ctor_nonemptiness;
    }
    nonemptiness.insert(current.clone(), Some(def_nonemptiness));
    stack.pop();

    def_nonemptiness
}

/// Check the (non-)emptiness of given datatype definitions.
///
/// The well-foundness condition for datatypes requires that datatypes must inductively have a
/// base case. This is checked by a DFS to detect a base case.
///
/// Return the name of the empty datatype, if exists.
pub(crate) fn check_dt_emptiness<D: Borrow<DatatypeDef>>(def_map: &HashMap<Str, D>) -> Option<Str> {
    let mut nonemptiness = HashMap::new();
    for k in def_map.keys() {
        nonemptiness.insert(k.clone(), None);
    }
    let mut stack = vec![];
    for name in def_map.keys() {
        let def = def_map.get(name).unwrap().borrow();
        if !check_emptiness_once(&mut nonemptiness, def_map, &mut stack, &def.name) {
            return Some(name.clone());
        }
    }
    None
}

/// Assuming correct datatypes, extend the symbol table
///
/// Sorts have been inserted, so we only insert symbols
pub(crate) fn extend_symbols_about_datatypes(defs: &[DatatypeDef], env: &mut Context) {
    let bool_sort = env.bool_sort();
    let is_symb = env.allocate_symbol("is");
    let x_var = env.allocate_symbol("x");
    for def in defs {
        for ctor in &def.dec.constructors {
            // 1. insert constructor
            let current_sort = env.sort_n_params(def.name.clone(), def.dec.params.clone());
            let sig = Sig::ParFunc(
                vec![],
                def.dec.params.clone(),
                ctor.args.iter().map(|v| v.2.clone()).collect(),
                current_sort.clone(),
            );
            env.insert_symbol(
                ctor.ctor.clone(),
                sig,
                FunctionMeta::Datatype {
                    dt_name: def.name.clone(),
                    kind: DatatypeFunction::Constructor,
                },
            );

            // 2. insert selectors
            for sel in &ctor.args {
                let sig = Sig::ParFunc(
                    vec![],
                    def.dec.params.clone(),
                    vec![current_sort.clone()],
                    sel.2.clone(),
                );
                env.insert_symbol(
                    sel.0.clone(),
                    sig,
                    FunctionMeta::Datatype {
                        dt_name: def.name.clone(),
                        kind: DatatypeFunction::Selector,
                    },
                );
            }

            // 3. insert (_ is X) testers
            let sig = Sig::ParFunc(
                vec![SigIndex::Symbol(ctor.ctor.clone())],
                def.dec.params.clone(),
                vec![current_sort.clone()],
                bool_sort.clone(),
            );
            env.push_symbol(
                is_symb.clone(),
                sig,
                FunctionMeta::Datatype {
                    dt_name: def.name.clone(),
                    kind: DatatypeFunction::Tester,
                },
            );

            // 4. insert is-X testers
            //    we expand is-X to (_ is X) by defining the former in terms of the latter
            let mut is_sym = "is-".to_string();
            is_sym.push_str(ctor.ctor.inner());
            let is_sym = env.allocate_symbol(&is_sym);
            let sig = Sig::ParFunc(
                vec![],
                def.dec.params.clone(),
                vec![current_sort.clone()],
                bool_sort.clone(),
            );
            let qid = Identifier {
                symbol: is_symb.clone(),
                indices: vec![Index::Symbol(ctor.ctor.clone())],
            }
            .into();
            let id = env.new_local();
            let x_loc = env.local(Local {
                id,
                symbol: x_var.clone(),
                sort: Some(current_sort.clone()),
            });
            let body = env.app(qid, vec![x_loc], Some(bool_sort.clone()));
            env.insert_symbol(
                is_sym.clone(),
                sig,
                FunctionMeta::Datatype {
                    dt_name: def.name.clone(),
                    kind: DatatypeFunction::TesterDefined(FunctionMetaDefined {
                        rec_deps: Default::default(),
                        def: FunctionDef {
                            name: is_sym.clone(),
                            sort_params: def.dec.params.clone(),
                            vars: vec![VarBinding(x_var.clone(), id, current_sort.clone())],
                            out_sort: bool_sort.clone(),
                            body,
                        },
                    }),
                },
            );
        }
    }
}