egglang/scope/
mod.rs

1pub(crate) mod functions;
2pub(crate) mod object;
3
4pub use functions::FunctionDefinition;
5
6use crate::{
7	error::{EggError, EggResult},
8	expression::Value,
9};
10use alloc::{collections::BTreeMap, format};
11use arcstr::ArcStr;
12use core::f32::consts;
13
14/// A [`Scope`] is responsible for keeping track of script state.
15///
16/// This includes storing variables, which are plain [`Values`](Value).
17/// Function and Objects values are simple indexes|references to [`FunctionDefinitions`](functions::FunctionDefinition) and [`BTreeMaps`](BTreeMap), stored in `ScopeExtras`.
18///
19/// `ScopeExtras` is only attached to the Global Scope, meaning Functions and Objects are always global, even if defined in a script function.
20///
21/// The [`default`](Default) scope comes with several constants built-in:
22/// ```json
23/// {
24///      "Boolean": "__TYPE__BOOLEAN",
25///      "Function": "__TYPE__FUNCTION",
26///      "Nil": "__CONSTANT__NIL",
27///      "Number": "__TYPE__NUMBER",
28///      "Object": "__TYPE__OBJECT",
29///      "String": "__TYPE__STRING",
30///      "PI": 3.1415927,
31///      "E": 2.7182817,
32///      "TAU": 6.2831855,
33///      "false": false,
34///      "true": true
35///  }
36/// ```
37#[derive(Debug)]
38#[allow(private_interfaces)]
39pub enum Scope {
40	Global { source: BTreeMap<ArcStr, Value>, extras: ScopeExtras },
41	Local { overlay: BTreeMap<ArcStr, Value>, source: *mut Scope },
42}
43
44impl Default for Scope {
45	fn default() -> Scope {
46		let mut source = BTreeMap::new();
47
48		source.insert("true".into(), true.into());
49		source.insert("false".into(), false.into());
50
51		// Globals identifying type
52		source.insert("Number".into(), Value::String(arcstr::literal!("__TYPE__NUMBER")));
53		source.insert("String".into(), Value::String(arcstr::literal!("__TYPE__STRING")));
54		source.insert("Nil".into(), Value::String(arcstr::literal!("__CONSTANT__NIL")));
55		source.insert("Boolean".into(), Value::String(arcstr::literal!("__TYPE__BOOLEAN")));
56		source.insert("Function".into(), Value::String(arcstr::literal!("__TYPE__FUNCTION")));
57		source.insert("Object".into(), Value::String(arcstr::literal!("__TYPE__OBJECT")));
58
59		// Float constants
60		source.insert("PI".into(), consts::PI.into());
61		source.insert("TAU".into(), consts::TAU.into());
62		source.insert("E".into(), consts::E.into());
63
64		Scope::Global { source, extras: Default::default() }
65	}
66}
67
68impl Scope {
69	/// Check if a variable exists anywhere in the scope chain.
70	pub fn exists(&self, key: &str) -> bool {
71		match self {
72			Scope::Global { source, .. } => source.contains_key(key),
73			Scope::Local { overlay, source: parent, .. } => overlay.contains_key(key) || unsafe { parent.as_ref().map(|p| p.exists(key)).unwrap_or(false) },
74		}
75	}
76
77	/// Check if a variable exists in the current scope. Has similar behaviour to [`exists`](Scope::exists) for the Global Scope.
78	/// Used to check if a variable is defined in the current scope, and not in the parent scope.
79	pub fn exists_locally(&self, key: &str) -> bool {
80		match self {
81			Scope::Global { source, .. } => source.contains_key(key),
82			Scope::Local { overlay, .. } => overlay.contains_key(key),
83		}
84	}
85	/// Fetch for a variable in the current scope and its parent scopes.
86	pub fn get(&self, key: &str) -> Option<&Value> {
87		match self {
88			Scope::Global { source, .. } => source.get(key),
89			Scope::Local { overlay, source: parent, .. } => overlay.get(key).or_else(|| unsafe { parent.as_mut().and_then(|p| p.get(key)) }),
90		}
91	}
92
93	/// Mutable fetch for a variable in the current scope and its parent scopes.
94	pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
95		match self {
96			Scope::Global { source, .. } => source.get_mut(key),
97			Scope::Local { overlay, source: parent, .. } => overlay.get_mut(key).or_else(|| unsafe { parent.as_mut().and_then(|p| p.get_mut(key)) }),
98		}
99	}
100
101	/// Insert a new variable into the current scope.
102	pub fn insert(&mut self, key: ArcStr, value: Value) -> EggResult<()> {
103		let exists_locally = self.exists_locally(&key);
104
105		match self {
106			Scope::Global { source, .. } => {
107				if exists_locally {
108					return Err(EggError::OperatorComplaint(format!("Variable {} already defined in Global Scope", key)));
109				}
110				source.insert(key, value);
111			}
112			Scope::Local { overlay, .. } => {
113				if exists_locally {
114					return Err(EggError::OperatorComplaint(format!("Variable {} already defined in Global Scope", key)));
115				}
116				overlay.insert(key, value);
117			}
118		};
119
120		Ok(())
121	}
122
123	/// Updates the value of a variable if it is in the present scope, otherwise updates it in the parent scope.
124	pub fn update(&mut self, key: ArcStr, value: Value) {
125		let was_local = matches!(self, Scope::Local { overlay, .. } if overlay.contains_key(&key));
126		self.delete(&key);
127
128		match self {
129			Scope::Global { source, .. } => {
130				source.insert(key, value);
131			}
132			Scope::Local { overlay, source } => {
133				if was_local {
134					overlay.insert(key, value);
135				} else {
136					unsafe { source.as_mut().map(|s| s.insert(key, value)) };
137				}
138			}
139		};
140	}
141
142	/// Delete a variable if it is the present scope, otherwise delete it from the parent scope.
143	pub fn delete(&mut self, key: &str) -> Option<Value> {
144		if let Some(Value::Function(index)) = self.get(key) {
145			self.delete_function(*index);
146		}
147
148		if let Some(Value::Object(tag)) = self.get(key) {
149			self.delete_object(*tag);
150		}
151
152		match self {
153			Scope::Global { source, .. } => source.remove(key),
154			Scope::Local { overlay, source: parent, .. } => unsafe { overlay.remove(key).or_else(|| parent.as_mut().and_then(|p| p.delete(key))) },
155		}
156	}
157
158	/// Create a new local scope.
159	pub(crate) fn overlay(&mut self, overlay: BTreeMap<ArcStr, Value>) -> Scope {
160		Scope::Local { overlay, source: self as _ }
161	}
162
163	/// Get extra metadata attached to the scope.
164	pub(crate) fn extras(&self) -> &ScopeExtras {
165		match self {
166			Scope::Global { extras, .. } => extras,
167			Scope::Local { source, .. } => unsafe { source.as_ref().map(|s| s.extras()).unwrap_unchecked() },
168		}
169	}
170
171	/// Get mutable extra metadata attached to the scope.
172	pub(crate) fn extras_mut(&mut self) -> &mut ScopeExtras {
173		match self {
174			Scope::Global { extras, .. } => extras,
175			Scope::Local { source, .. } => unsafe { source.as_mut().map(|s| s.extras_mut()).unwrap_unchecked() },
176		}
177	}
178}
179
180#[derive(Debug, Default)]
181pub(crate) struct ScopeExtras {
182	maps: BTreeMap<usize, BTreeMap<Value, Value>>,
183	functions: BTreeMap<usize, functions::FunctionDefinition>,
184	counter: usize,
185	_unsend: core::marker::PhantomData<*mut ()>,
186}