datex_core/compiler/
scope.rs

1use crate::ast::VariableKind;
2use crate::compiler::precompiler::{AstMetadata, PrecompilerScopeStack};
3use crate::compiler::{Variable, VariableRepresentation, context::VirtualSlot};
4use itertools::Itertools;
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::rc::Rc;
8
9#[derive(Debug, Clone, Default)]
10pub struct PrecompilerData {
11    // precompiler ast metadata
12    pub ast_metadata: Rc<RefCell<AstMetadata>>,
13    // precompiler scope stack
14    pub precompiler_scope_stack: RefCell<PrecompilerScopeStack>,
15}
16
17#[derive(Debug, Clone)]
18pub struct CompilationScope {
19    /// List of variables, mapped by name to their slot address and type.
20    variables: HashMap<String, Variable>,
21    /// parent scope, accessible from a child scope
22    parent_scope: Option<Box<CompilationScope>>,
23    /// scope of a parent context, e.g. when inside a block scope for remote execution calls or function bodies
24    external_parent_scope: Option<Box<CompilationScope>>,
25    next_slot_address: u32,
26
27    // ------- Data only relevant for the root scope (FIXME: refactor?) -------
28    /// optional precompiler data, only on the root scope
29    pub precompiler_data: Option<PrecompilerData>,
30    /// If once is true, the scope can only be used for compilation once.
31    /// E.g. for a REPL, this needs to be false, so that the scope can be reused
32    pub once: bool,
33    /// If was_used is true, the scope has been used for compilation and should not be reused if once is true.
34    pub was_used: bool,
35}
36
37impl Default for CompilationScope {
38    fn default() -> Self {
39        CompilationScope {
40            variables: HashMap::new(),
41            parent_scope: None,
42            external_parent_scope: None,
43            next_slot_address: 0,
44            precompiler_data: Some(PrecompilerData::default()),
45            once: false,
46            was_used: false,
47        }
48    }
49}
50
51impl CompilationScope {
52    pub fn new(once: bool) -> CompilationScope {
53        CompilationScope {
54            once,
55            ..CompilationScope::default()
56        }
57    }
58
59    pub fn new_with_external_parent_scope(
60        parent_context: CompilationScope,
61    ) -> CompilationScope {
62        CompilationScope {
63            external_parent_scope: Some(Box::new(parent_context)),
64            ..CompilationScope::default()
65        }
66    }
67
68    pub fn has_external_parent_scope(&self) -> bool {
69        self.external_parent_scope.is_some()
70    }
71
72    pub fn register_variable_slot(&mut self, variable: Variable) {
73        self.variables.insert(variable.name.clone(), variable);
74    }
75
76    pub fn get_next_virtual_slot(&mut self) -> u32 {
77        let slot_address = self.next_slot_address;
78        self.next_slot_address += 1;
79        slot_address
80    }
81
82    // Returns the virtual slot address for a variable in this scope or potentially in the parent scope.
83    // The returned tuple contains the slot address, variable type, and a boolean indicating if it
84    // is a local variable (false) or from a parent scope (true).
85    pub fn resolve_variable_name_to_virtual_slot(
86        &self,
87        name: &str,
88    ) -> Option<(VirtualSlot, VariableKind)> {
89        if let Some(variable) = self.variables.get(name) {
90            let slot = match variable.representation {
91                VariableRepresentation::Constant(slot) => slot,
92                VariableRepresentation::VariableReference {
93                    container_slot,
94                    ..
95                } => container_slot,
96                VariableRepresentation::VariableSlot(slot) => slot,
97            };
98            Some((slot, variable.kind))
99        } else if let Some(external_parent) = &self.external_parent_scope {
100            external_parent
101                .resolve_variable_name_to_virtual_slot(name)
102                .map(|(virt_slot, var_type)| (virt_slot.downgrade(), var_type))
103        } else if let Some(parent) = &self.parent_scope {
104            parent.resolve_variable_name_to_virtual_slot(name)
105        } else {
106            None
107        }
108    }
109
110    /// Creates a new `CompileScope` that is a child of the current scope.
111    pub fn push(self) -> CompilationScope {
112        CompilationScope {
113            next_slot_address: self.next_slot_address,
114            parent_scope: Some(Box::new(self)),
115            external_parent_scope: None,
116            variables: HashMap::new(),
117            precompiler_data: None,
118            once: true,
119            was_used: false,
120        }
121    }
122
123    /// Drops the current scope and returns to the parent scope and a list
124    /// of all slot addresses that should be dropped.
125    pub fn pop(self) -> Option<(CompilationScope, Vec<VirtualSlot>)> {
126        if let Some(mut parent) = self.parent_scope {
127            // update next_slot_address for parent scope
128            parent.next_slot_address = self.next_slot_address;
129            Some((
130                *parent,
131                self.variables
132                    .keys()
133                    .flat_map(|k| self.variables[k].slots())
134                    .collect::<Vec<_>>(),
135            ))
136        } else {
137            None
138        }
139    }
140
141    pub fn pop_external(self) -> Option<CompilationScope> {
142        self.external_parent_scope
143            .map(|external_parent| *external_parent)
144    }
145}