datex_core/compiler/precompiler/
scope_stack.rs

1use crate::{
2    compiler::error::CompilerError,
3    compiler::precompiler::{
4        precompiled_ast::{AstMetadata, VariableMetadata, VariableShape},
5        scope::PrecompilerScope,
6    },
7};
8
9#[derive(Debug, Clone)]
10pub struct PrecompilerScopeStack {
11    pub scopes: Vec<PrecompilerScope>,
12}
13
14impl Default for PrecompilerScopeStack {
15    fn default() -> Self {
16        PrecompilerScopeStack {
17            scopes: vec![PrecompilerScope::default()],
18        }
19    }
20}
21
22impl PrecompilerScopeStack {
23    pub fn push_scope(&mut self) {
24        self.scopes.push(PrecompilerScope::new_with_realm_index(
25            self.scopes.last().map_or(0, |s| s.realm_index),
26        ));
27    }
28
29    pub fn pop_scope(&mut self) {
30        if !self.scopes.is_empty() {
31            self.scopes.pop();
32        } else {
33            unreachable!("Cannot pop scope from an empty scope stack");
34        }
35    }
36
37    /// increment the current scope's realm index (e.g. inside a remote execution call or function body)
38    pub fn increment_realm_index(&mut self) {
39        if let Some(scope) = self.scopes.last_mut() {
40            scope.realm_index += 1;
41        } else {
42            unreachable!("Scope stack must always have at least one scope");
43        }
44    }
45
46    pub fn current_realm_index(&self) -> usize {
47        self.scopes.last().map_or(0, |s| s.realm_index)
48    }
49
50    pub fn add_new_variable(
51        &mut self,
52        name: String,
53        id: usize,
54        kind: VariableShape,
55    ) -> VariableMetadata {
56        let current_realm_index =
57            self.scopes.last().map_or(0, |s| s.realm_index);
58        let var_metadata = VariableMetadata {
59            is_cross_realm: false,
60            original_realm_index: current_realm_index,
61            shape: kind,
62            var_type: None,
63            name: name.clone(),
64        };
65        self.set_variable(name, id);
66        var_metadata
67    }
68
69    pub fn get_variable_and_update_metadata(
70        &self,
71        name: &str,
72        metadata: &mut AstMetadata,
73    ) -> Result<usize, CompilerError> {
74        // try to resolve local variable
75        if let Some(var_id) = self.get_variable(name) {
76            let var_metadata = metadata.variable_metadata_mut(var_id).unwrap();
77            // if the original realm index is not the current realm index, mark it as cross-realm
78            if var_metadata.original_realm_index != self.current_realm_index() {
79                var_metadata.is_cross_realm = true;
80            }
81            Ok(var_id)
82        } else {
83            Err(CompilerError::UndeclaredVariable(name.to_string()))
84        }
85    }
86
87    pub fn set_variable(&mut self, name: String, id: usize) {
88        self.get_active_scope_mut()
89            .variable_ids_by_name
90            .insert(name, id);
91    }
92
93    fn get_active_scope_index(&self) -> usize {
94        // get the second last scope or the last one if there is only one scope
95        if self.scopes.len() > 1 {
96            self.scopes.len() - 2
97        } else {
98            0
99        }
100    }
101
102    pub fn get_active_scope(&self) -> &PrecompilerScope {
103        self.scopes.get(self.get_active_scope_index()).unwrap()
104    }
105
106    pub fn get_active_scope_mut(&mut self) -> &mut PrecompilerScope {
107        let index = self.get_active_scope_index();
108        self.scopes.get_mut(index).unwrap()
109    }
110
111    pub fn get_variable(&self, name: &str) -> Option<usize> {
112        for scope in self.scopes.iter().rev() {
113            if let Some(id) = scope.variable_ids_by_name.get(name) {
114                return Some(*id);
115            }
116        }
117        None
118    }
119    pub fn has_variable(&self, name: &str) -> bool {
120        self.get_variable(name).is_some()
121    }
122
123    pub fn metadata<'a>(
124        &self,
125        name: &str,
126        metadata: &'a AstMetadata,
127    ) -> Option<&'a VariableMetadata> {
128        if let Some(var_id) = self.get_variable(name) {
129            metadata.variable_metadata(var_id)
130        } else {
131            None
132        }
133    }
134    pub fn variable_kind(
135        &self,
136        name: &str,
137        metadata: &AstMetadata,
138    ) -> Option<VariableShape> {
139        if let Some(var_id) = self.get_variable(name) {
140            metadata.variable_metadata(var_id).map(|v| v.shape)
141        } else {
142            None
143        }
144    }
145}