Skip to main content

conjure_cp_core/ast/
symbol_table.rs

1//! The symbol table.
2//!
3//! See the item documentation for [`SymbolTable`] for more details.
4
5use crate::bug;
6use crate::representation::{Representation, get_repr_rule};
7
8use super::comprehension::Comprehension;
9use super::serde::RcRefCellAsId;
10use std::cell::RefCell;
11use std::collections::BTreeSet;
12use std::collections::btree_map::Entry;
13use std::collections::{BTreeMap, VecDeque};
14use std::hash::{Hash, Hasher};
15use std::rc::Rc;
16use std::sync::atomic::{AtomicU32, Ordering};
17
18use super::declaration::{DeclarationPtr, serde::DeclarationPtrFull};
19use super::serde::{DefaultWithId, HasId, ObjId};
20use itertools::{Itertools as _, izip};
21use serde::{Deserialize, Serialize};
22use serde_with::serde_as;
23use tracing::trace;
24use uniplate::Tree;
25use uniplate::{Biplate, Uniplate};
26
27use super::name::Name;
28use super::{DomainPtr, Expression, GroundDomain, Moo, ReturnType, SubModel, Typeable};
29use derivative::Derivative;
30
31// Count symbol tables per thread / model.
32//
33// We run tests in parallel and having the id's be per thread keeps them more deterministic in the
34// JSON output. If this were not thread local, ids would be given to symbol tables differently in
35// each test run (depending on how the threads were scheduled). These id changes would result in
36// all the generated tests "changing" each time `ACCEPT=true cargo test` is ran.
37//
38// SAFETY: Symbol tables use Rc<RefCell<<>>, so a model is not thread-safe anyways.
39thread_local! {
40static ID_COUNTER: AtomicU32 = const { AtomicU32::new(0) };
41}
42
43/// The global symbol table, mapping names to their definitions.
44///
45/// Names in the symbol table are unique, including between different types of object stored in the
46/// symbol table. For example, you cannot have a letting and decision variable with the same name.
47///
48/// # Symbol Kinds
49///
50/// The symbol table tracks the following types of symbol:
51///
52/// ## Decision Variables
53///
54/// ```text
55/// find NAME: DOMAIN
56/// ```
57///
58/// See [`DecisionVariable`](super::DecisionVariable).
59///
60/// ## Lettings
61///
62/// Lettings define constants, of which there are two types:
63///
64///   + **Constant values**: `letting val be A`, where A is an [`Expression`].
65///
66///     A can be any integer, boolean, or matrix expression.
67///     A can include references to other lettings, model parameters, and, unlike Savile Row,
68///     decision variables.
69///
70///   + **Constant domains**: `letting Domain be domain D`, where D is a [`Domain`].
71///
72///     D can include references to other lettings and model parameters, and, unlike Savile Row,
73///     decision variables.
74///
75/// Unless otherwise stated, these follow the semantics specified in section 2.2.2 of the Savile
76/// Row manual (version 1.9.1 at time of writing).
77#[derive(Derivative)]
78#[derivative(PartialEq)]
79#[derive(Debug, Eq)]
80#[serde_as]
81#[derive(Serialize, Deserialize)]
82pub struct SymbolTable {
83    #[serde_as(as = "Vec<(_,DeclarationPtrFull)>")]
84    table: BTreeMap<Name, DeclarationPtr>,
85
86    /// A unique id for this symbol table, for serialisation and debugging.
87    #[derivative(PartialEq = "ignore")] // eq by value not id.
88    id: ObjId,
89
90    #[serde_as(as = "Option<RcRefCellAsId>")]
91    parent: Option<Rc<RefCell<SymbolTable>>>,
92
93    next_machine_name: RefCell<i32>,
94}
95
96impl Hash for SymbolTable {
97    fn hash<H: Hasher>(&self, state: &mut H) {
98        self.id.hash(state);
99    }
100}
101
102impl SymbolTable {
103    /// Creates an empty symbol table.
104    pub fn new() -> SymbolTable {
105        SymbolTable::new_inner(None)
106    }
107
108    /// Creates an empty symbol table with the given parent.
109    pub fn with_parent(parent: Rc<RefCell<SymbolTable>>) -> SymbolTable {
110        SymbolTable::new_inner(Some(parent))
111    }
112
113    fn new_inner(parent: Option<Rc<RefCell<SymbolTable>>>) -> SymbolTable {
114        let id = ID_COUNTER.with(|x| x.fetch_add(1, Ordering::Relaxed));
115        trace!(
116            "new symbol table: id = {id}  parent_id = {}",
117            parent
118                .as_ref()
119                .map(|x| x.borrow().id().to_string())
120                .unwrap_or(String::from("none"))
121        );
122        SymbolTable {
123            id: ObjId {
124                type_name: SymbolTable::TYPE_NAME.into(),
125                object_id: id,
126            },
127            table: BTreeMap::new(),
128            next_machine_name: RefCell::new(0),
129            parent,
130        }
131    }
132
133    /// Looks up the declaration with the given name in the current scope only.
134    ///
135    /// Returns `None` if there is no declaration with that name in the current scope.
136    pub fn lookup_local(&self, name: &Name) -> Option<DeclarationPtr> {
137        self.table.get(name).cloned()
138    }
139
140    /// Looks up the declaration with the given name, checking all enclosing scopes.
141    ///
142    /// Returns `None` if there is no declaration with that name in scope.
143    pub fn lookup(&self, name: &Name) -> Option<DeclarationPtr> {
144        self.lookup_local(name).or_else(|| {
145            self.parent
146                .as_ref()
147                .and_then(|parent| (*parent).borrow().lookup(name))
148        })
149    }
150
151    /// Inserts a declaration into the symbol table.
152    ///
153    /// Returns `None` if there is already a symbol with this name in the local scope.
154    pub fn insert(&mut self, declaration: DeclarationPtr) -> Option<()> {
155        let name = declaration.name().clone();
156        if let Entry::Vacant(e) = self.table.entry(name) {
157            e.insert(declaration);
158            Some(())
159        } else {
160            None
161        }
162    }
163
164    /// Updates or adds a declaration in the immediate local scope.
165    pub fn update_insert(&mut self, declaration: DeclarationPtr) {
166        let name = declaration.name().clone();
167        self.table.insert(name, declaration);
168    }
169
170    /// Looks up the return type for name if it has one and is in scope.
171    pub fn return_type(&self, name: &Name) -> Option<ReturnType> {
172        self.lookup(name).map(|x| x.return_type())
173    }
174
175    /// Looks up the return type for name if has one and is in the local scope.
176    pub fn return_type_local(&self, name: &Name) -> Option<ReturnType> {
177        self.lookup_local(name).map(|x| x.return_type())
178    }
179
180    /// Looks up the domain of name if it has one and is in scope.
181    ///
182    /// This method can return domain references: if a ground domain is always required, use
183    /// [`SymbolTable::resolve_domain`].
184    pub fn domain(&self, name: &Name) -> Option<DomainPtr> {
185        if let Name::WithRepresentation(name, _) = name {
186            self.lookup(name)?.domain()
187        } else {
188            self.lookup(name)?.domain()
189        }
190    }
191
192    /// Looks up the domain of name, resolving domain references to ground domains.
193    ///
194    /// See [`SymbolTable::domain`].
195    pub fn resolve_domain(&self, name: &Name) -> Option<Moo<GroundDomain>> {
196        self.domain(name)?.resolve()
197    }
198
199    /// Iterates over entries in the local symbol table only.
200    pub fn into_iter_local(self) -> LocalIntoIter {
201        LocalIntoIter {
202            inner: self.table.into_iter(),
203        }
204    }
205
206    /// Extends the symbol table with the given symbol table, updating the gensym counter if
207    /// necessary.
208    pub fn extend(&mut self, other: SymbolTable) {
209        if other.table.keys().count() > self.table.keys().count() {
210            let new_vars = other.table.keys().collect::<BTreeSet<_>>();
211            let old_vars = self.table.keys().collect::<BTreeSet<_>>();
212
213            for added_var in new_vars.difference(&old_vars) {
214                let mut next_var = self.next_machine_name.borrow_mut();
215                if let Name::Machine(m) = *added_var
216                    && *m >= *next_var
217                {
218                    *next_var = *m + 1;
219                }
220            }
221        }
222
223        self.table.extend(other.table);
224    }
225
226    /// Creates a new variable in this symbol table with a unique name, and returns its
227    /// declaration.
228    pub fn gensym(&mut self, domain: &DomainPtr) -> DeclarationPtr {
229        let num = *self.next_machine_name.borrow();
230        *(self.next_machine_name.borrow_mut()) += 1;
231        let decl = DeclarationPtr::new_var(Name::Machine(num), domain.clone());
232        self.insert(decl.clone());
233        decl
234    }
235
236    /// Gets the parent of this symbol table as a mutable reference.
237    ///
238    /// This function provides no sanity checks.
239    pub fn parent_mut_unchecked(&mut self) -> &mut Option<Rc<RefCell<SymbolTable>>> {
240        &mut self.parent
241    }
242
243    /// Gets the representation `representation` for `name`.
244    ///
245    /// # Returns
246    ///
247    /// + `None` if `name` does not exist, is not a decision variable, or does not have that representation.
248    pub fn get_representation(
249        &self,
250        name: &Name,
251        representation: &[&str],
252    ) -> Option<Vec<Box<dyn Representation>>> {
253        // TODO: move representation stuff to declaration / variable to avoid cloning? (we have to
254        // move inside of an rc here, so cannot return borrows)
255        //
256        // Also would prevent constant "does exist" "is var" checks.
257        //
258        // The reason it is not there now is because I'm getting serde issues...
259        //
260        // Also might run into issues putting get_or_add into declaration/variable, as that
261        // requires us to mutably borrow both the symbol table, and the variable inside the symbol
262        // table..
263
264        let decl = self.lookup(name)?;
265        let var = &decl.as_var()?;
266
267        var.representations
268            .iter()
269            .find(|x| &x.iter().map(|r| r.repr_name()).collect_vec()[..] == representation)
270            .cloned()
271    }
272
273    /// Gets all initialised representations for `name`.
274    ///
275    /// # Returns
276    ///
277    /// + `None` if `name` does not exist, or is not a decision variable.
278    pub fn representations_for(&self, name: &Name) -> Option<Vec<Vec<Box<dyn Representation>>>> {
279        let decl = self.lookup(name)?;
280        decl.as_var().map(|x| x.representations.clone())
281    }
282
283    /// Gets the representation `representation` for `name`, creating it if it does not exist.
284    ///
285    /// If the representation does not exist, this method initialises the representation in this
286    /// symbol table, adding the representation to `name`, and the declarations for the represented
287    /// variables to the symbol table.
288    ///
289    /// # Usage
290    ///
291    /// Representations for variable references should be selected and created by the
292    /// `select_representation` rule. Therefore, this method should not be used in other rules.
293    /// Consider using [`get_representation`](`SymbolTable::get_representation`) instead.
294    ///
295    /// # Returns
296    ///
297    /// + `None` if `name` does not exist, is not a decision variable, or cannot be given that
298    ///   representation.
299    pub fn get_or_add_representation(
300        &mut self,
301        name: &Name,
302        representation: &[&str],
303    ) -> Option<Vec<Box<dyn Representation>>> {
304        // Lookup the declaration reference
305        let mut decl = self.lookup(name)?;
306
307        if let Some(var) = decl.as_var()
308            && let Some(existing_reprs) = var
309                .representations
310                .iter()
311                .find(|x| &x.iter().map(|r| r.repr_name()).collect_vec()[..] == representation)
312                .cloned()
313        {
314            return Some(existing_reprs); // Found: return early
315        }
316        // Representation not found
317
318        // TODO: nested representations logic...
319        if representation.len() != 1 {
320            bug!("nested representations not implemented")
321        }
322        let repr_name_str = representation[0];
323        let repr_init_fn = get_repr_rule(repr_name_str)?;
324
325        let reprs = vec![repr_init_fn(name, self)?];
326
327        // Get mutable access to the variable part
328        let mut var = decl.as_var_mut()?;
329
330        for repr_instance in &reprs {
331            repr_instance
332                .declaration_down()
333                .ok()?
334                .into_iter()
335                .for_each(|x| self.update_insert(x));
336        }
337
338        var.representations.push(reprs.clone());
339
340        Some(reprs)
341    }
342}
343
344impl IntoIterator for SymbolTable {
345    type Item = (Name, DeclarationPtr);
346
347    type IntoIter = IntoIter;
348
349    /// Iterates over symbol table entries in scope.
350    fn into_iter(self) -> Self::IntoIter {
351        IntoIter {
352            inner: self.table.into_iter(),
353            parent: self.parent,
354        }
355    }
356}
357
358/// Iterator over symbol table entries in the current scope only.
359pub struct LocalIntoIter {
360    // iterator over the btreemap
361    inner: std::collections::btree_map::IntoIter<Name, DeclarationPtr>,
362}
363
364impl Iterator for LocalIntoIter {
365    type Item = (Name, DeclarationPtr);
366
367    fn next(&mut self) -> Option<Self::Item> {
368        self.inner.next()
369    }
370}
371
372/// Iterator over all symbol table entries in scope.
373pub struct IntoIter {
374    // iterator over the current scopes' btreemap
375    inner: std::collections::btree_map::IntoIter<Name, DeclarationPtr>,
376
377    // the parent scope
378    parent: Option<Rc<RefCell<SymbolTable>>>,
379}
380
381impl Iterator for IntoIter {
382    type Item = (Name, DeclarationPtr);
383
384    fn next(&mut self) -> Option<Self::Item> {
385        let mut val = self.inner.next();
386
387        // Go up the tree until we find a parent symbol table with declarations to iterate over.
388        //
389        // Note that the parent symbol table may be empty - this is why this is a loop!
390        while val.is_none() {
391            let parent = self.parent.clone()?;
392            let parent_ref = (*parent).borrow();
393            self.parent.clone_from(&parent_ref.parent);
394            self.inner = parent_ref.table.clone().into_iter();
395
396            val = self.inner.next();
397        }
398
399        val
400    }
401}
402
403impl HasId for SymbolTable {
404    const TYPE_NAME: &'static str = "SymbolTable";
405    fn id(&self) -> ObjId {
406        self.id.clone()
407    }
408}
409
410impl DefaultWithId for SymbolTable {
411    fn default_with_id(id: ObjId) -> Self {
412        Self {
413            table: BTreeMap::new(),
414            id,
415            parent: None,
416            next_machine_name: RefCell::new(0),
417        }
418    }
419}
420
421impl Clone for SymbolTable {
422    fn clone(&self) -> Self {
423        Self {
424            table: self.table.clone(),
425            id: ObjId {
426                type_name: SymbolTable::TYPE_NAME.into(),
427                object_id: ID_COUNTER.with(|x| x.fetch_add(1, Ordering::Relaxed)),
428            },
429            parent: self.parent.clone(),
430            next_machine_name: self.next_machine_name.clone(),
431        }
432    }
433}
434
435impl Default for SymbolTable {
436    fn default() -> Self {
437        Self::new_inner(None)
438    }
439}
440
441impl SymbolTable {
442    #[doc(hidden)]
443    // FIXME: remove me once we get SymbolTablePtr
444
445    /// Mainly for uniplate usage
446    pub fn clone_with_same_id(&self) -> Self {
447        // Biplate<Symboltable> instances need to ensure that symbol tables keep their ids.
448        //
449        // this is because we use uniplate to gather symbol table ids in
450        // SerdeModel::collect_stable_id_mappings(),
451        Self {
452            table: self.table.clone(),
453            id: self.id.clone(),
454            parent: self.parent.clone(),
455            next_machine_name: self.next_machine_name.clone(),
456        }
457    }
458}
459
460impl Uniplate for SymbolTable {
461    fn uniplate(&self) -> (Tree<Self>, Box<dyn Fn(Tree<Self>) -> Self>) {
462        // do not recurse up parents, that would be weird?
463        let self2 = self.clone_with_same_id();
464        (Tree::Zero, Box::new(move |_| self2.clone_with_same_id()))
465    }
466}
467
468impl Biplate<Expression> for SymbolTable {
469    fn biplate(&self) -> (Tree<Expression>, Box<dyn Fn(Tree<Expression>) -> Self>) {
470        let (child_trees, ctxs): (VecDeque<_>, Vec<_>) = self
471            .table
472            .values()
473            .map(Biplate::<Expression>::biplate)
474            .unzip();
475
476        let tree = Tree::Many(child_trees);
477
478        let self2 = self.clone();
479        let ctx = Box::new(move |tree| {
480            let Tree::Many(exprs) = tree else {
481                panic!("unexpected children structure");
482            };
483
484            let mut self3 = self2.clone();
485            let self3_iter = self3.table.iter_mut();
486            for (ctx, tree, (_, decl)) in izip!(&ctxs, exprs, self3_iter) {
487                // update declaration inside the pointer instead of creating a new one, so all
488                // things referencing this keep referencing this.
489                *decl = ctx(tree)
490            }
491
492            self3
493        });
494
495        (tree, ctx)
496    }
497}
498
499impl Biplate<Comprehension> for SymbolTable {
500    fn biplate(
501        &self,
502    ) -> (
503        Tree<Comprehension>,
504        Box<dyn Fn(Tree<Comprehension>) -> Self>,
505    ) {
506        let (expr_tree, expr_ctx) = <SymbolTable as Biplate<Expression>>::biplate(self);
507
508        let (exprs, recons_expr_tree) = expr_tree.list();
509
510        let (comprehension_tree, comprehension_ctx) =
511            <VecDeque<Expression> as Biplate<Comprehension>>::biplate(&exprs);
512
513        let ctx = Box::new(move |x| {
514            // 1. turn comprehension tree into a list of expressions
515            let exprs = comprehension_ctx(x);
516
517            // 2. turn list of expressions into an expression tree
518            let expr_tree = recons_expr_tree(exprs);
519
520            // 3. turn expression tree into a symbol table
521            expr_ctx(expr_tree)
522        });
523
524        (comprehension_tree, ctx)
525    }
526}
527
528impl Biplate<SubModel> for SymbolTable {
529    // walk into expressions
530    fn biplate(&self) -> (Tree<SubModel>, Box<dyn Fn(Tree<SubModel>) -> Self>) {
531        let (expr_tree, expr_ctx) = <SymbolTable as Biplate<Expression>>::biplate(self);
532
533        let (exprs, recons_expr_tree) = expr_tree.list();
534
535        let (submodel_tree, submodel_ctx) =
536            <VecDeque<Expression> as Biplate<SubModel>>::biplate(&exprs);
537
538        let ctx = Box::new(move |x| {
539            // 1. turn submodel tree into a list of expressions
540            let exprs = submodel_ctx(x);
541
542            // 2. turn list of expressions into an expression tree
543            let expr_tree = recons_expr_tree(exprs);
544
545            // 3. turn expression tree into a symbol table
546            expr_ctx(expr_tree)
547        });
548        (submodel_tree, ctx)
549    }
550}