typst_library/foundations/
scope.rs

1use std::fmt::{self, Debug, Formatter};
2use std::hash::{Hash, Hasher};
3
4use ecow::{eco_format, EcoString};
5use indexmap::map::Entry;
6use indexmap::IndexMap;
7use typst_syntax::Span;
8
9use crate::diag::{bail, DeprecationSink, HintedStrResult, HintedString, StrResult};
10use crate::foundations::{
11    Element, Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType,
12    Type, Value,
13};
14use crate::{Category, Library};
15
16/// A stack of scopes.
17#[derive(Debug, Default, Clone)]
18pub struct Scopes<'a> {
19    /// The active scope.
20    pub top: Scope,
21    /// The stack of lower scopes.
22    pub scopes: Vec<Scope>,
23    /// The standard library.
24    pub base: Option<&'a Library>,
25}
26
27impl<'a> Scopes<'a> {
28    /// Create a new, empty hierarchy of scopes.
29    pub fn new(base: Option<&'a Library>) -> Self {
30        Self { top: Scope::new(), scopes: vec![], base }
31    }
32
33    /// Enter a new scope.
34    pub fn enter(&mut self) {
35        self.scopes.push(std::mem::take(&mut self.top));
36    }
37
38    /// Exit the topmost scope.
39    ///
40    /// This panics if no scope was entered.
41    pub fn exit(&mut self) {
42        self.top = self.scopes.pop().expect("no pushed scope");
43    }
44
45    /// Try to access a binding immutably.
46    pub fn get(&self, var: &str) -> HintedStrResult<&Binding> {
47        std::iter::once(&self.top)
48            .chain(self.scopes.iter().rev())
49            .find_map(|scope| scope.get(var))
50            .or_else(|| {
51                self.base.and_then(|base| match base.global.scope().get(var) {
52                    Some(binding) => Some(binding),
53                    None if var == "std" => Some(&base.std),
54                    None => None,
55                })
56            })
57            .ok_or_else(|| unknown_variable(var))
58    }
59
60    /// Try to access a binding mutably.
61    pub fn get_mut(&mut self, var: &str) -> HintedStrResult<&mut Binding> {
62        std::iter::once(&mut self.top)
63            .chain(&mut self.scopes.iter_mut().rev())
64            .find_map(|scope| scope.get_mut(var))
65            .ok_or_else(|| {
66                match self.base.and_then(|base| base.global.scope().get(var)) {
67                    Some(_) => cannot_mutate_constant(var),
68                    _ if var == "std" => cannot_mutate_constant(var),
69                    _ => unknown_variable(var),
70                }
71            })
72    }
73
74    /// Try to access a binding immutably in math.
75    pub fn get_in_math(&self, var: &str) -> HintedStrResult<&Binding> {
76        std::iter::once(&self.top)
77            .chain(self.scopes.iter().rev())
78            .find_map(|scope| scope.get(var))
79            .or_else(|| {
80                self.base.and_then(|base| match base.math.scope().get(var) {
81                    Some(binding) => Some(binding),
82                    None if var == "std" => Some(&base.std),
83                    None => None,
84                })
85            })
86            .ok_or_else(|| {
87                unknown_variable_math(
88                    var,
89                    self.base.is_some_and(|base| base.global.scope().get(var).is_some()),
90                )
91            })
92    }
93
94    /// Check if an std variable is shadowed.
95    pub fn check_std_shadowed(&self, var: &str) -> bool {
96        self.base.is_some_and(|base| base.global.scope().get(var).is_some())
97            && std::iter::once(&self.top)
98                .chain(self.scopes.iter().rev())
99                .any(|scope| scope.get(var).is_some())
100    }
101}
102
103/// A map from binding names to values.
104#[derive(Default, Clone)]
105pub struct Scope {
106    map: IndexMap<EcoString, Binding>,
107    deduplicate: bool,
108    category: Option<Category>,
109}
110
111/// Scope construction.
112impl Scope {
113    /// Create a new empty scope.
114    pub fn new() -> Self {
115        Default::default()
116    }
117
118    /// Create a new scope with duplication prevention.
119    pub fn deduplicating() -> Self {
120        Self { deduplicate: true, ..Default::default() }
121    }
122
123    /// Enter a new category.
124    pub fn start_category(&mut self, category: Category) {
125        self.category = Some(category);
126    }
127
128    /// Reset the category.
129    pub fn reset_category(&mut self) {
130        self.category = None;
131    }
132
133    /// Define a native function through a Rust type that shadows the function.
134    #[track_caller]
135    pub fn define_func<T: NativeFunc>(&mut self) -> &mut Binding {
136        let data = T::data();
137        self.define(data.name, Func::from(data))
138    }
139
140    /// Define a native function with raw function data.
141    #[track_caller]
142    pub fn define_func_with_data(
143        &mut self,
144        data: &'static NativeFuncData,
145    ) -> &mut Binding {
146        self.define(data.name, Func::from(data))
147    }
148
149    /// Define a native type.
150    #[track_caller]
151    pub fn define_type<T: NativeType>(&mut self) -> &mut Binding {
152        let data = T::data();
153        self.define(data.name, Type::from(data))
154    }
155
156    /// Define a native element.
157    #[track_caller]
158    pub fn define_elem<T: NativeElement>(&mut self) -> &mut Binding {
159        let data = T::data();
160        self.define(data.name, Element::from(data))
161    }
162
163    /// Define a built-in with compile-time known name and returns a mutable
164    /// reference to it.
165    ///
166    /// When the name isn't compile-time known, you should instead use:
167    /// - `Vm::bind` if you already have [`Binding`]
168    /// - `Vm::define`  if you only have a [`Value`]
169    /// - [`Scope::bind`](Self::bind) if you are not operating in the context of
170    ///   a `Vm` or if you are binding to something that is not an AST
171    ///   identifier (e.g. when constructing a dynamic
172    ///   [`Module`](super::Module))
173    #[track_caller]
174    pub fn define(&mut self, name: &'static str, value: impl IntoValue) -> &mut Binding {
175        #[cfg(debug_assertions)]
176        if self.deduplicate && self.map.contains_key(name) {
177            panic!("duplicate definition: {name}");
178        }
179
180        let mut binding = Binding::detached(value);
181        binding.category = self.category;
182        self.bind(name.into(), binding)
183    }
184}
185
186/// Scope manipulation and access.
187impl Scope {
188    /// Inserts a binding into this scope and returns a mutable reference to it.
189    ///
190    /// Prefer `Vm::bind` if you are operating in the context of a `Vm`.
191    pub fn bind(&mut self, name: EcoString, binding: Binding) -> &mut Binding {
192        match self.map.entry(name) {
193            Entry::Occupied(mut entry) => {
194                entry.insert(binding);
195                entry.into_mut()
196            }
197            Entry::Vacant(entry) => entry.insert(binding),
198        }
199    }
200
201    /// Try to access a binding immutably.
202    pub fn get(&self, var: &str) -> Option<&Binding> {
203        self.map.get(var)
204    }
205
206    /// Try to access a binding mutably.
207    pub fn get_mut(&mut self, var: &str) -> Option<&mut Binding> {
208        self.map.get_mut(var)
209    }
210
211    /// Iterate over all definitions.
212    pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Binding)> {
213        self.map.iter()
214    }
215}
216
217impl Debug for Scope {
218    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
219        f.write_str("Scope ")?;
220        f.debug_map()
221            .entries(self.map.iter().map(|(k, v)| (k, v.read())))
222            .finish()
223    }
224}
225
226impl Hash for Scope {
227    fn hash<H: Hasher>(&self, state: &mut H) {
228        state.write_usize(self.map.len());
229        for item in &self.map {
230            item.hash(state);
231        }
232        self.deduplicate.hash(state);
233        self.category.hash(state);
234    }
235}
236
237/// Defines the associated scope of a Rust type.
238pub trait NativeScope {
239    /// The constructor function for the type, if any.
240    fn constructor() -> Option<&'static NativeFuncData>;
241
242    /// Get the associated scope for the type.
243    fn scope() -> Scope;
244}
245
246/// A bound value with metadata.
247#[derive(Debug, Clone, Hash)]
248pub struct Binding {
249    /// The bound value.
250    value: Value,
251    /// The kind of binding, determines how the value can be accessed.
252    kind: BindingKind,
253    /// A span associated with the binding.
254    span: Span,
255    /// The category of the binding.
256    category: Option<Category>,
257    /// A deprecation message for the definition.
258    deprecation: Option<&'static str>,
259}
260
261/// The different kinds of slots.
262#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
263enum BindingKind {
264    /// A normal, mutable binding.
265    Normal,
266    /// A captured copy of another variable.
267    Captured(Capturer),
268}
269
270impl Binding {
271    /// Create a new binding with a span marking its definition site.
272    pub fn new(value: impl IntoValue, span: Span) -> Self {
273        Self {
274            value: value.into_value(),
275            span,
276            kind: BindingKind::Normal,
277            category: None,
278            deprecation: None,
279        }
280    }
281
282    /// Create a binding without a span.
283    pub fn detached(value: impl IntoValue) -> Self {
284        Self::new(value, Span::detached())
285    }
286
287    /// Marks this binding as deprecated, with the given `message`.
288    pub fn deprecated(&mut self, message: &'static str) -> &mut Self {
289        self.deprecation = Some(message);
290        self
291    }
292
293    /// Read the value.
294    pub fn read(&self) -> &Value {
295        &self.value
296    }
297
298    /// Read the value, checking for deprecation.
299    ///
300    /// As the `sink`
301    /// - pass `()` to ignore the message.
302    /// - pass `(&mut engine, span)` to emit a warning into the engine.
303    pub fn read_checked(&self, mut sink: impl DeprecationSink) -> &Value {
304        if let Some(message) = self.deprecation {
305            sink.emit(message);
306        }
307        &self.value
308    }
309
310    /// Try to write to the value.
311    ///
312    /// This fails if the value is a read-only closure capture.
313    pub fn write(&mut self) -> StrResult<&mut Value> {
314        match self.kind {
315            BindingKind::Normal => Ok(&mut self.value),
316            BindingKind::Captured(capturer) => bail!(
317                "variables from outside the {} are \
318                 read-only and cannot be modified",
319                match capturer {
320                    Capturer::Function => "function",
321                    Capturer::Context => "context expression",
322                }
323            ),
324        }
325    }
326
327    /// Create a copy of the binding for closure capturing.
328    pub fn capture(&self, capturer: Capturer) -> Self {
329        Self {
330            kind: BindingKind::Captured(capturer),
331            ..self.clone()
332        }
333    }
334
335    /// A span associated with the stored value.
336    pub fn span(&self) -> Span {
337        self.span
338    }
339
340    /// A deprecation message for the value, if any.
341    pub fn deprecation(&self) -> Option<&'static str> {
342        self.deprecation
343    }
344
345    /// The category of the value, if any.
346    pub fn category(&self) -> Option<Category> {
347        self.category
348    }
349}
350
351/// What the variable was captured by.
352#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
353pub enum Capturer {
354    /// Captured by a function / closure.
355    Function,
356    /// Captured by a context expression.
357    Context,
358}
359
360/// The error message when trying to mutate a variable from the standard
361/// library.
362#[cold]
363fn cannot_mutate_constant(var: &str) -> HintedString {
364    eco_format!("cannot mutate a constant: {}", var).into()
365}
366
367/// The error message when a variable wasn't found.
368#[cold]
369fn unknown_variable(var: &str) -> HintedString {
370    let mut res = HintedString::new(eco_format!("unknown variable: {}", var));
371
372    if var.contains('-') {
373        res.hint(eco_format!(
374            "if you meant to use subtraction, \
375             try adding spaces around the minus sign{}: `{}`",
376            if var.matches('-').count() > 1 { "s" } else { "" },
377            var.replace('-', " - ")
378        ));
379    }
380
381    res
382}
383
384/// The error message when a variable wasn't found it math.
385#[cold]
386fn unknown_variable_math(var: &str, in_global: bool) -> HintedString {
387    let mut res = HintedString::new(eco_format!("unknown variable: {}", var));
388
389    if matches!(var, "none" | "auto" | "false" | "true") {
390        res.hint(eco_format!(
391            "if you meant to use a literal, \
392             try adding a hash before it: `#{var}`",
393        ));
394    } else if in_global {
395        res.hint(eco_format!(
396            "`{var}` is not available directly in math, \
397             try adding a hash before it: `#{var}`",
398        ));
399    } else {
400        res.hint(eco_format!(
401            "if you meant to display multiple letters as is, \
402             try adding spaces between each letter: `{}`",
403            var.chars().flat_map(|c| [' ', c]).skip(1).collect::<EcoString>()
404        ));
405        res.hint(eco_format!(
406            "or if you meant to display this as text, \
407             try placing it in quotes: `\"{var}\"`"
408        ));
409    }
410
411    res
412}