datex_core/compiler/
scope.rs

1use crate::ast::expressions::VariableKind;
2use crate::collections::HashMap;
3use crate::compiler::precompiler::precompiled_ast::RichAst;
4use crate::compiler::precompiler::scope_stack::PrecompilerScopeStack;
5use crate::compiler::{Variable, VariableRepresentation, context::VirtualSlot};
6use crate::runtime::execution::context::ExecutionMode;
7use core::cell::RefCell;
8
9#[derive(Debug, Default, Clone)]
10pub struct PrecompilerData {
11    // precompiler ast metadata
12    pub rich_ast: RichAst,
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    /// The execution mode of the scope.
31    /// When the mode is set to Unbounded, the outer statements block will be an unbounded statement block.
32    pub execution_mode: ExecutionMode,
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            execution_mode: ExecutionMode::Static,
46            was_used: false,
47        }
48    }
49}
50
51impl CompilationScope {
52    pub fn new(execution_mode: ExecutionMode) -> CompilationScope {
53        CompilationScope {
54            execution_mode,
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 mark_as_last_execution(&mut self) {
69        match self.execution_mode {
70            ExecutionMode::Static => {
71                panic!(
72                    "mark_as_last_execution can only be called for Unbounded execution modes"
73                );
74            }
75            ExecutionMode::Unbounded { .. } => {
76                self.execution_mode =
77                    ExecutionMode::Unbounded { has_next: false };
78            }
79            _ => {}
80        }
81    }
82
83    pub fn has_external_parent_scope(&self) -> bool {
84        self.external_parent_scope.is_some()
85    }
86
87    pub fn register_variable_slot(&mut self, variable: Variable) {
88        self.variables.insert(variable.name.clone(), variable);
89    }
90
91    pub fn get_next_virtual_slot(&mut self) -> u32 {
92        let slot_address = self.next_slot_address;
93        self.next_slot_address += 1;
94        slot_address
95    }
96
97    // Returns the virtual slot address for a variable in this scope or potentially in the parent scope.
98    // The returned tuple contains the slot address, variable type, and a boolean indicating if it
99    // is a local variable (false) or from a parent scope (true).
100    pub fn resolve_variable_name_to_virtual_slot(
101        &self,
102        name: &str,
103    ) -> Option<(VirtualSlot, VariableKind)> {
104        if let Some(variable) = self.variables.get(name) {
105            let slot = match variable.representation {
106                VariableRepresentation::Constant(slot) => slot,
107                VariableRepresentation::VariableReference {
108                    container_slot,
109                    ..
110                } => container_slot,
111                VariableRepresentation::VariableSlot(slot) => slot,
112            };
113            Some((slot, variable.kind))
114        } else if let Some(external_parent) = &self.external_parent_scope {
115            external_parent
116                .resolve_variable_name_to_virtual_slot(name)
117                .map(|(virt_slot, var_type)| (virt_slot.downgrade(), var_type))
118        } else if let Some(parent) = &self.parent_scope {
119            parent.resolve_variable_name_to_virtual_slot(name)
120        } else {
121            None
122        }
123    }
124
125    /// Creates a new `CompileScope` that is a child of the current scope.
126    pub fn push(self) -> CompilationScope {
127        CompilationScope {
128            next_slot_address: self.next_slot_address,
129            parent_scope: Some(Box::new(self)),
130            external_parent_scope: None,
131            variables: HashMap::new(),
132            precompiler_data: None,
133            execution_mode: ExecutionMode::Static,
134            was_used: false,
135        }
136    }
137
138    /// Drops the current scope and returns to the parent scope and a list
139    /// of all slot addresses that should be dropped.
140    pub fn pop(self) -> Option<(CompilationScope, Vec<VirtualSlot>)> {
141        if let Some(mut parent) = self.parent_scope {
142            // update next_slot_address for parent scope
143            parent.next_slot_address = self.next_slot_address;
144            Some((
145                *parent,
146                self.variables
147                    .keys()
148                    .flat_map(|k| self.variables[k].slots())
149                    .collect::<Vec<_>>(),
150            ))
151        } else {
152            None
153        }
154    }
155
156    pub fn pop_external(self) -> Option<CompilationScope> {
157        self.external_parent_scope
158            .map(|external_parent| *external_parent)
159    }
160}