datex_core/compiler/
scope.rs

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