Skip to main content

typst_library/foundations/
mod.rs

1//! Foundational types and functions.
2
3pub mod calc;
4pub mod ops;
5pub mod repr;
6pub mod sys;
7
8mod args;
9mod array;
10mod auto;
11mod bool;
12mod bytes;
13mod cast;
14mod content;
15mod context;
16mod datetime;
17mod decimal;
18mod dict;
19mod duration;
20mod fields;
21mod float;
22mod func;
23mod int;
24mod label;
25mod module;
26mod none;
27mod path;
28#[path = "plugin.rs"]
29mod plugin_;
30mod scope;
31mod selector;
32mod str;
33mod styles;
34mod symbol;
35#[path = "target.rs"]
36mod target_;
37mod ty;
38mod value;
39mod version;
40
41pub use self::args::*;
42pub use self::array::*;
43pub use self::auto::*;
44pub use self::bytes::*;
45pub use self::cast::*;
46pub use self::content::*;
47pub use self::context::*;
48pub use self::datetime::*;
49pub use self::decimal::*;
50pub use self::dict::*;
51pub use self::duration::*;
52pub use self::fields::*;
53pub use self::float::*;
54pub use self::func::*;
55pub use self::int::*;
56pub use self::label::*;
57pub use self::module::*;
58pub use self::none::*;
59pub use self::path::*;
60pub use self::plugin_::*;
61pub use self::repr::Repr;
62pub use self::scope::*;
63pub use self::selector::*;
64pub use self::str::*;
65pub use self::styles::*;
66pub use self::symbol::*;
67pub use self::target_::*;
68pub use self::ty::*;
69pub use self::value::*;
70pub use self::version::*;
71pub use typst_macros::{scope, ty};
72
73#[rustfmt::skip]
74#[doc(hidden)]
75pub use {
76    ecow::{eco_format, eco_vec},
77    indexmap::IndexMap,
78    smallvec::SmallVec,
79};
80
81use comemo::{Track, TrackedMut};
82use ecow::EcoString;
83use typst_syntax::{RootedPath, Spanned, SyntaxMode};
84
85use crate::diag::{SourceResult, StrResult, bail};
86use crate::engine::Engine;
87use crate::introspection::EmptyIntrospector;
88use crate::routines::SpanMode;
89
90/// Hook up all `foundations` definitions.
91pub(super) fn define(global: &mut Scope, inputs: Dict) {
92    global.start_category(crate::Category::Foundations);
93    global.define_type::<bool>();
94    global.define_type::<i64>();
95    global.define_type::<f64>();
96    global.define_type::<Str>();
97    global.define_type::<Label>();
98    global.define_type::<Bytes>();
99    global.define_type::<Content>();
100    global.define_type::<Array>();
101    global.define_type::<Dict>();
102    global.define_type::<Func>();
103    global.define_type::<Args>();
104    global.define_type::<Type>();
105    global.define_type::<Module>();
106    global.define_type::<Regex>();
107    global.define_type::<Selector>();
108    global.define_type::<Datetime>();
109    global.define_type::<Decimal>();
110    global.define_type::<Symbol>();
111    global.define_type::<Duration>();
112    global.define_type::<Version>();
113    global.define_type::<RootedPath>();
114    global.define_func::<repr::repr>();
115    global.define_func::<panic>();
116    global.define_func::<assert>();
117    global.define_func::<eval>();
118    global.define_func::<plugin>();
119    global.define_func::<target>();
120    global.define("calc", calc::module());
121    global.define("sys", sys::module(inputs));
122    global.reset_category();
123}
124
125/// Fails with an error.
126///
127/// Arguments are displayed to the user (not rendered in the document) as
128/// strings, converting with `repr` if necessary.
129///
130/// = Example <example>
131/// The code below produces the error `panicked with: this is wrong`.
132/// ```typ
133/// #panic("this is wrong")
134/// ```
135#[func(keywords = ["error"])]
136pub fn panic(
137    /// The values to panic with and display to the user.
138    #[variadic]
139    values: Vec<Value>,
140) -> StrResult<Never> {
141    let mut msg = EcoString::from("panicked");
142    if !values.is_empty() {
143        msg.push_str(" with: ");
144        for (i, value) in values.iter().enumerate() {
145            if i > 0 {
146                msg.push_str(", ");
147            }
148            match value {
149                Value::Str(s) => msg.push_str(s),
150                _ => msg.push_str(&value.repr()),
151            }
152        }
153    }
154    Err(msg)
155}
156
157/// Ensures that a condition is fulfilled.
158///
159/// Fails with an error if the condition is not fulfilled. Does not produce any
160/// output in the document.
161///
162/// If you wish to test equality between two values, see @assert.eq and
163/// @assert.ne.
164///
165/// = Example <example>
166/// ```typ
167/// #assert(1 < 2, message: "math broke")
168/// ```
169#[func(scope)]
170pub fn assert(
171    /// The condition that must be true for the assertion to pass.
172    condition: bool,
173    /// The error message when the assertion fails.
174    #[named]
175    message: Option<EcoString>,
176) -> StrResult<NoneValue> {
177    if !condition {
178        if let Some(message) = message {
179            bail!("assertion failed: {message}");
180        } else {
181            bail!("assertion failed");
182        }
183    }
184    Ok(NoneValue)
185}
186
187#[scope]
188impl assert {
189    /// Ensures that two values are equal.
190    ///
191    /// Fails with an error if the first value is not equal to the second. Does
192    /// not produce any output in the document.
193    ///
194    /// ```typ
195    /// #assert.eq(10, 10)
196    /// ```
197    #[func(title = "Assert Equal")]
198    pub fn eq(
199        /// The first value to compare.
200        left: Value,
201        /// The second value to compare.
202        right: Value,
203        /// An optional message to display on error instead of the
204        /// representations of the compared values.
205        #[named]
206        message: Option<EcoString>,
207    ) -> StrResult<NoneValue> {
208        if left != right {
209            if let Some(message) = message {
210                bail!("equality assertion failed: {message}");
211            } else {
212                bail!(
213                    "equality assertion failed: value {} was not equal to {}",
214                    left.repr(),
215                    right.repr(),
216                );
217            }
218        }
219        Ok(NoneValue)
220    }
221
222    /// Ensures that two values are not equal.
223    ///
224    /// Fails with an error if the first value is equal to the second. Does not
225    /// produce any output in the document.
226    ///
227    /// ```typ
228    /// #assert.ne(3, 4)
229    /// ```
230    #[func(title = "Assert Not Equal")]
231    pub fn ne(
232        /// The first value to compare.
233        left: Value,
234        /// The second value to compare.
235        right: Value,
236        /// An optional message to display on error instead of the
237        /// representations of the compared values.
238        #[named]
239        message: Option<EcoString>,
240    ) -> StrResult<NoneValue> {
241        if left == right {
242            if let Some(message) = message {
243                bail!("inequality assertion failed: {message}");
244            } else {
245                bail!(
246                    "inequality assertion failed: value {} was equal to {}",
247                    left.repr(),
248                    right.repr(),
249                );
250            }
251        }
252        Ok(NoneValue)
253    }
254}
255
256/// Evaluates a string as Typst code.
257///
258/// This function should only be used as a last resort.
259///
260/// = Example <example>
261/// ```example
262/// #eval("1 + 1") \
263/// #eval("(1, 2, 3, 4)").len() \
264/// #eval("*Markup!*", mode: "markup") \
265/// ```
266#[func(title = "Evaluate")]
267pub fn eval(
268    engine: &mut Engine,
269    /// A string of Typst code to evaluate.
270    source: Spanned<String>,
271    /// The @reference:syntax:modes[syntactical mode] in which the string is
272    /// parsed.
273    ///
274    /// ```example
275    /// #eval("= Heading", mode: "markup")
276    /// #eval("1_2^3", mode: "math")
277    /// ```
278    #[named]
279    #[default(SyntaxMode::Code)]
280    mode: SyntaxMode,
281    /// A scope of definitions that are made available.
282    ///
283    /// ```example
284    /// #eval("x + 1", scope: (x: 2)) \
285    /// #eval(
286    ///   "abc/xyz",
287    ///   mode: "math",
288    ///   scope: (
289    ///     abc: $a + b + c$,
290    ///     xyz: $x + y + z$,
291    ///   ),
292    /// )
293    /// ```
294    #[named]
295    #[default]
296    scope: Dict,
297) -> SourceResult<Value> {
298    let Spanned { v: text, span } = source;
299    let dict = scope;
300    let mut scope = Scope::new();
301    for (key, value) in dict {
302        scope.bind(key.into(), Binding::new(value, span));
303    }
304
305    (engine.library.routines.eval_string)(
306        engine.world,
307        engine.library,
308        TrackedMut::reborrow_mut(&mut engine.sink),
309        // We create a new, detached introspector for string evaluation. Passing
310        // the real introspector should not have any consequences with
311        // `Context::none`, but also no benefits. We might want to pass through
312        // the context and introspector in the future, to allow introspection
313        // when calling `eval` from within a context expression, but this should
314        // be well-considered.
315        EmptyIntrospector.track(),
316        Context::none().track(),
317        &text,
318        SpanMode::Uniform(span),
319        mode,
320        scope,
321    )
322}