typst_library/foundations/
scope.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};

use ecow::{eco_format, EcoString};
use indexmap::map::Entry;
use indexmap::IndexMap;
use typst_syntax::Span;

use crate::diag::{bail, DeprecationSink, HintedStrResult, HintedString, StrResult};
use crate::foundations::{
    Element, Func, IntoValue, NativeElement, NativeFunc, NativeFuncData, NativeType,
    Type, Value,
};
use crate::{Category, Library};

/// A stack of scopes.
#[derive(Debug, Default, Clone)]
pub struct Scopes<'a> {
    /// The active scope.
    pub top: Scope,
    /// The stack of lower scopes.
    pub scopes: Vec<Scope>,
    /// The standard library.
    pub base: Option<&'a Library>,
}

impl<'a> Scopes<'a> {
    /// Create a new, empty hierarchy of scopes.
    pub fn new(base: Option<&'a Library>) -> Self {
        Self { top: Scope::new(), scopes: vec![], base }
    }

    /// Enter a new scope.
    pub fn enter(&mut self) {
        self.scopes.push(std::mem::take(&mut self.top));
    }

    /// Exit the topmost scope.
    ///
    /// This panics if no scope was entered.
    pub fn exit(&mut self) {
        self.top = self.scopes.pop().expect("no pushed scope");
    }

    /// Try to access a binding immutably.
    pub fn get(&self, var: &str) -> HintedStrResult<&Binding> {
        std::iter::once(&self.top)
            .chain(self.scopes.iter().rev())
            .find_map(|scope| scope.get(var))
            .or_else(|| {
                self.base.and_then(|base| match base.global.scope().get(var) {
                    Some(binding) => Some(binding),
                    None if var == "std" => Some(&base.std),
                    None => None,
                })
            })
            .ok_or_else(|| unknown_variable(var))
    }

    /// Try to access a binding mutably.
    pub fn get_mut(&mut self, var: &str) -> HintedStrResult<&mut Binding> {
        std::iter::once(&mut self.top)
            .chain(&mut self.scopes.iter_mut().rev())
            .find_map(|scope| scope.get_mut(var))
            .ok_or_else(|| {
                match self.base.and_then(|base| base.global.scope().get(var)) {
                    Some(_) => cannot_mutate_constant(var),
                    _ if var == "std" => cannot_mutate_constant(var),
                    _ => unknown_variable(var),
                }
            })
    }

    /// Try to access a binding immutably in math.
    pub fn get_in_math(&self, var: &str) -> HintedStrResult<&Binding> {
        std::iter::once(&self.top)
            .chain(self.scopes.iter().rev())
            .find_map(|scope| scope.get(var))
            .or_else(|| {
                self.base.and_then(|base| match base.math.scope().get(var) {
                    Some(binding) => Some(binding),
                    None if var == "std" => Some(&base.std),
                    None => None,
                })
            })
            .ok_or_else(|| {
                unknown_variable_math(
                    var,
                    self.base.is_some_and(|base| base.global.scope().get(var).is_some()),
                )
            })
    }

    /// Check if an std variable is shadowed.
    pub fn check_std_shadowed(&self, var: &str) -> bool {
        self.base.is_some_and(|base| base.global.scope().get(var).is_some())
            && std::iter::once(&self.top)
                .chain(self.scopes.iter().rev())
                .any(|scope| scope.get(var).is_some())
    }
}

/// A map from binding names to values.
#[derive(Default, Clone)]
pub struct Scope {
    map: IndexMap<EcoString, Binding>,
    deduplicate: bool,
    category: Option<Category>,
}

/// Scope construction.
impl Scope {
    /// Create a new empty scope.
    pub fn new() -> Self {
        Default::default()
    }

    /// Create a new scope with duplication prevention.
    pub fn deduplicating() -> Self {
        Self { deduplicate: true, ..Default::default() }
    }

    /// Enter a new category.
    pub fn start_category(&mut self, category: Category) {
        self.category = Some(category);
    }

    /// Reset the category.
    pub fn reset_category(&mut self) {
        self.category = None;
    }

    /// Define a native function through a Rust type that shadows the function.
    #[track_caller]
    pub fn define_func<T: NativeFunc>(&mut self) -> &mut Binding {
        let data = T::data();
        self.define(data.name, Func::from(data))
    }

    /// Define a native function with raw function data.
    #[track_caller]
    pub fn define_func_with_data(
        &mut self,
        data: &'static NativeFuncData,
    ) -> &mut Binding {
        self.define(data.name, Func::from(data))
    }

    /// Define a native type.
    #[track_caller]
    pub fn define_type<T: NativeType>(&mut self) -> &mut Binding {
        let data = T::data();
        self.define(data.name, Type::from(data))
    }

    /// Define a native element.
    #[track_caller]
    pub fn define_elem<T: NativeElement>(&mut self) -> &mut Binding {
        let data = T::data();
        self.define(data.name, Element::from(data))
    }

    /// Define a built-in with compile-time known name and returns a mutable
    /// reference to it.
    ///
    /// When the name isn't compile-time known, you should instead use:
    /// - `Vm::bind` if you already have [`Binding`]
    /// - `Vm::define`  if you only have a [`Value`]
    /// - [`Scope::bind`](Self::bind) if you are not operating in the context of
    ///   a `Vm` or if you are binding to something that is not an AST
    ///   identifier (e.g. when constructing a dynamic
    ///   [`Module`](super::Module))
    #[track_caller]
    pub fn define(&mut self, name: &'static str, value: impl IntoValue) -> &mut Binding {
        #[cfg(debug_assertions)]
        if self.deduplicate && self.map.contains_key(name) {
            panic!("duplicate definition: {name}");
        }

        let mut binding = Binding::detached(value);
        binding.category = self.category;
        self.bind(name.into(), binding)
    }
}

/// Scope manipulation and access.
impl Scope {
    /// Inserts a binding into this scope and returns a mutable reference to it.
    ///
    /// Prefer `Vm::bind` if you are operating in the context of a `Vm`.
    pub fn bind(&mut self, name: EcoString, binding: Binding) -> &mut Binding {
        match self.map.entry(name) {
            Entry::Occupied(mut entry) => {
                entry.insert(binding);
                entry.into_mut()
            }
            Entry::Vacant(entry) => entry.insert(binding),
        }
    }

    /// Try to access a binding immutably.
    pub fn get(&self, var: &str) -> Option<&Binding> {
        self.map.get(var)
    }

    /// Try to access a binding mutably.
    pub fn get_mut(&mut self, var: &str) -> Option<&mut Binding> {
        self.map.get_mut(var)
    }

    /// Iterate over all definitions.
    pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Binding)> {
        self.map.iter()
    }
}

impl Debug for Scope {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.write_str("Scope ")?;
        f.debug_map()
            .entries(self.map.iter().map(|(k, v)| (k, v.read())))
            .finish()
    }
}

impl Hash for Scope {
    fn hash<H: Hasher>(&self, state: &mut H) {
        state.write_usize(self.map.len());
        for item in &self.map {
            item.hash(state);
        }
        self.deduplicate.hash(state);
        self.category.hash(state);
    }
}

/// Defines the associated scope of a Rust type.
pub trait NativeScope {
    /// The constructor function for the type, if any.
    fn constructor() -> Option<&'static NativeFuncData>;

    /// Get the associated scope for the type.
    fn scope() -> Scope;
}

/// A bound value with metadata.
#[derive(Debug, Clone, Hash)]
pub struct Binding {
    /// The bound value.
    value: Value,
    /// The kind of binding, determines how the value can be accessed.
    kind: BindingKind,
    /// A span associated with the binding.
    span: Span,
    /// The category of the binding.
    category: Option<Category>,
    /// A deprecation message for the definition.
    deprecation: Option<&'static str>,
}

/// The different kinds of slots.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
enum BindingKind {
    /// A normal, mutable binding.
    Normal,
    /// A captured copy of another variable.
    Captured(Capturer),
}

impl Binding {
    /// Create a new binding with a span marking its definition site.
    pub fn new(value: impl IntoValue, span: Span) -> Self {
        Self {
            value: value.into_value(),
            span,
            kind: BindingKind::Normal,
            category: None,
            deprecation: None,
        }
    }

    /// Create a binding without a span.
    pub fn detached(value: impl IntoValue) -> Self {
        Self::new(value, Span::detached())
    }

    /// Marks this binding as deprecated, with the given `message`.
    pub fn deprecated(&mut self, message: &'static str) -> &mut Self {
        self.deprecation = Some(message);
        self
    }

    /// Read the value.
    pub fn read(&self) -> &Value {
        &self.value
    }

    /// Read the value, checking for deprecation.
    ///
    /// As the `sink`
    /// - pass `()` to ignore the message.
    /// - pass `(&mut engine, span)` to emit a warning into the engine.
    pub fn read_checked(&self, sink: impl DeprecationSink) -> &Value {
        if let Some(message) = self.deprecation {
            sink.emit(message);
        }
        &self.value
    }

    /// Try to write to the value.
    ///
    /// This fails if the value is a read-only closure capture.
    pub fn write(&mut self) -> StrResult<&mut Value> {
        match self.kind {
            BindingKind::Normal => Ok(&mut self.value),
            BindingKind::Captured(capturer) => bail!(
                "variables from outside the {} are \
                 read-only and cannot be modified",
                match capturer {
                    Capturer::Function => "function",
                    Capturer::Context => "context expression",
                }
            ),
        }
    }

    /// Create a copy of the binding for closure capturing.
    pub fn capture(&self, capturer: Capturer) -> Self {
        Self {
            kind: BindingKind::Captured(capturer),
            ..self.clone()
        }
    }

    /// A span associated with the stored value.
    pub fn span(&self) -> Span {
        self.span
    }

    /// A deprecation message for the value, if any.
    pub fn deprecation(&self) -> Option<&'static str> {
        self.deprecation
    }

    /// The category of the value, if any.
    pub fn category(&self) -> Option<Category> {
        self.category
    }
}

/// What the variable was captured by.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Capturer {
    /// Captured by a function / closure.
    Function,
    /// Captured by a context expression.
    Context,
}

/// The error message when trying to mutate a variable from the standard
/// library.
#[cold]
fn cannot_mutate_constant(var: &str) -> HintedString {
    eco_format!("cannot mutate a constant: {}", var).into()
}

/// The error message when a variable wasn't found.
#[cold]
fn unknown_variable(var: &str) -> HintedString {
    let mut res = HintedString::new(eco_format!("unknown variable: {}", var));

    if var.contains('-') {
        res.hint(eco_format!(
            "if you meant to use subtraction, \
             try adding spaces around the minus sign{}: `{}`",
            if var.matches('-').count() > 1 { "s" } else { "" },
            var.replace('-', " - ")
        ));
    }

    res
}

/// The error message when a variable wasn't found it math.
#[cold]
fn unknown_variable_math(var: &str, in_global: bool) -> HintedString {
    let mut res = HintedString::new(eco_format!("unknown variable: {}", var));

    if matches!(var, "none" | "auto" | "false" | "true") {
        res.hint(eco_format!(
            "if you meant to use a literal, \
             try adding a hash before it: `#{var}`",
        ));
    } else if in_global {
        res.hint(eco_format!(
            "`{var}` is not available directly in math, \
             try adding a hash before it: `#{var}`",
        ));
    } else {
        res.hint(eco_format!(
            "if you meant to display multiple letters as is, \
             try adding spaces between each letter: `{}`",
            var.chars().flat_map(|c| [' ', c]).skip(1).collect::<EcoString>()
        ));
        res.hint(eco_format!(
            "or if you meant to display this as text, \
             try placing it in quotes: `\"{var}\"`"
        ));
    }

    res
}