typst_library/foundations/
args.rs1use std::fmt::{self, Debug, Formatter};
2use std::ops::Add;
3use std::slice;
4
5use comemo::Tracked;
6use ecow::{EcoString, EcoVec, eco_format, eco_vec};
7use typst_syntax::{Span, Spanned};
8
9use crate::diag::{At, SourceDiagnostic, SourceResult, StrResult, bail, error};
10use crate::engine::Engine;
11use crate::foundations::{
12 Array, Context, Dict, FromValue, Func, IntoValue, Repr, Str, Value, cast, func, repr,
13 scope, ty,
14};
15
16#[ty(scope, cast, name = "arguments")]
54#[derive(Clone, Hash)]
55pub struct Args {
56 pub span: Span,
59 pub items: EcoVec<Arg>,
61}
62
63impl Args {
64 pub fn new<T: IntoValue>(span: Span, values: impl IntoIterator<Item = T>) -> Self {
66 let items = values
67 .into_iter()
68 .map(|value| Arg {
69 span,
70 name: None,
71 value: Spanned::new(value.into_value(), span),
72 })
73 .collect();
74 Self { span, items }
75 }
76
77 pub fn spanned(mut self, span: Span) -> Self {
79 if self.span.is_detached() {
80 self.span = span;
81 }
82 self
83 }
84
85 pub fn remaining(&self) -> usize {
87 self.items.iter().filter(|slot| slot.name.is_none()).count()
88 }
89
90 pub fn insert(&mut self, index: usize, span: Span, value: Value) {
92 self.items.insert(
93 index,
94 Arg {
95 span: self.span,
96 name: None,
97 value: Spanned::new(value, span),
98 },
99 )
100 }
101
102 pub fn push(&mut self, span: Span, value: Value) {
104 self.items.push(Arg {
105 span: self.span,
106 name: None,
107 value: Spanned::new(value, span),
108 })
109 }
110
111 pub fn eat<T>(&mut self) -> SourceResult<Option<T>>
113 where
114 T: FromValue<Spanned<Value>>,
115 {
116 for (i, slot) in self.items.iter().enumerate() {
117 if slot.name.is_none() {
118 let value = self.items.remove(i).value;
119 let span = value.span;
120 return T::from_value(value).at(span).map(Some);
121 }
122 }
123 Ok(None)
124 }
125
126 pub fn consume(&mut self, n: usize) -> SourceResult<Vec<Arg>> {
128 let mut list = vec![];
129
130 let mut i = 0;
131 while i < self.items.len() && list.len() < n {
132 if self.items[i].name.is_none() {
133 list.push(self.items.remove(i));
134 } else {
135 i += 1;
136 }
137 }
138
139 if list.len() < n {
140 bail!(self.span, "not enough arguments");
141 }
142
143 Ok(list)
144 }
145
146 pub fn expect<T>(&mut self, what: &str) -> SourceResult<T>
151 where
152 T: FromValue<Spanned<Value>>,
153 {
154 match self.eat()? {
155 Some(v) => Ok(v),
156 None => bail!(self.missing_argument(what)),
157 }
158 }
159
160 fn missing_argument(&self, what: &str) -> SourceDiagnostic {
162 for item in &self.items {
163 let Some(name) = item.name.as_deref() else { continue };
164 if name == what {
165 return error!(
166 item.span,
167 "the argument `{what}` is positional";
168 hint: "try removing `{name}:`";
169 );
170 }
171 }
172
173 error!(self.span, "missing argument: {what}")
174 }
175
176 pub fn find<T>(&mut self) -> SourceResult<Option<T>>
178 where
179 T: FromValue<Spanned<Value>>,
180 {
181 for (i, slot) in self.items.iter().enumerate() {
182 if slot.name.is_none() && T::castable(&slot.value.v) {
183 let value = self.items.remove(i).value;
184 let span = value.span;
185 return T::from_value(value).at(span).map(Some);
186 }
187 }
188 Ok(None)
189 }
190
191 pub fn all<T>(&mut self) -> SourceResult<Vec<T>>
193 where
194 T: FromValue<Spanned<Value>>,
195 {
196 let mut list = vec![];
197 let mut errors = eco_vec![];
198 self.items.retain(|item| {
199 if item.name.is_some() {
200 return true;
201 }
202 let span = item.value.span;
203 let spanned = Spanned::new(std::mem::take(&mut item.value.v), span);
204 match T::from_value(spanned).at(span) {
205 Ok(val) => list.push(val),
206 Err(diags) => errors.extend(diags),
207 }
208 false
209 });
210 if !errors.is_empty() {
211 return Err(errors);
212 }
213 Ok(list)
214 }
215
216 pub fn named<T>(&mut self, name: &str) -> SourceResult<Option<T>>
219 where
220 T: FromValue<Spanned<Value>>,
221 {
222 let mut i = 0;
225 let mut found = None;
226 while i < self.items.len() {
227 if self.items[i].name.as_deref() == Some(name) {
228 let value = self.items.remove(i).value;
229 let span = value.span;
230 found = Some(T::from_value(value).at(span)?);
231 } else {
232 i += 1;
233 }
234 }
235 Ok(found)
236 }
237
238 pub fn named_or_find<T>(&mut self, name: &str) -> SourceResult<Option<T>>
240 where
241 T: FromValue<Spanned<Value>>,
242 {
243 match self.named(name)? {
244 Some(value) => Ok(Some(value)),
245 None => self.find(),
246 }
247 }
248
249 pub fn take(&mut self) -> Self {
251 Self {
252 span: self.span,
253 items: std::mem::take(&mut self.items),
254 }
255 }
256
257 pub fn finish(self) -> SourceResult<()> {
260 if let Some(arg) = self.items.first() {
261 match &arg.name {
262 Some(name) => bail!(arg.span, "unexpected argument: {name}"),
263 _ => bail!(arg.span, "unexpected argument"),
264 }
265 }
266 Ok(())
267 }
268}
269
270#[derive(Debug, Clone, Eq, PartialEq)]
273pub enum ArgumentKey {
274 Index(i64),
275 Name(Str),
276}
277
278cast! {
279 ArgumentKey,
280 v: i64 => Self::Index(v),
281 v: Str => Self::Name(v),
282}
283
284impl Args {
285 pub fn is_empty(&self) -> bool {
287 self.items.is_empty()
288 }
289
290 fn get(&self, key: &ArgumentKey) -> Option<&Value> {
291 let item = match key {
292 &ArgumentKey::Index(index) => {
293 let mut iter = self.items.iter().filter(|item| item.name.is_none());
294 if index < 0 {
295 let index = (-(index + 1)).try_into().ok()?;
296 iter.nth_back(index)
297 } else {
298 let index = index.try_into().ok()?;
299 iter.nth(index)
300 }
301 }
302 ArgumentKey::Name(name) => {
304 self.items.iter().rfind(|item| item.name.as_ref() == Some(name))
305 }
306 };
307 item.map(|item| &item.value.v)
308 }
309
310 pub fn field(&self, field: &str) -> StrResult<&Value> {
312 self.items
313 .iter()
314 .rfind(|item| item.name.as_ref().map(|name| name.as_str()) == Some(field))
315 .ok_or_else(|| eco_format!("no named argument {}", field.repr()))
316 .map(|item| &item.value.v)
317 }
318}
319
320#[scope]
321impl Args {
322 #[func(constructor)]
331 pub fn construct(
332 args: &mut Args,
333 #[external]
335 #[variadic]
336 arguments: Vec<Value>,
337 ) -> Args {
338 args.take()
339 }
340
341 #[func(title = "Length")]
343 pub fn len(&self) -> usize {
344 self.items.len()
345 }
346
347 #[func]
359 pub fn at(
360 &self,
361 key: ArgumentKey,
363 #[named]
365 default: Option<Value>,
366 ) -> StrResult<Value> {
367 self.get(&key)
368 .cloned()
369 .or(default)
370 .ok_or_else(|| missing_key_no_default(key))
371 }
372
373 #[func(name = "pos", title = "Positional")]
375 pub fn to_pos(&self) -> Array {
376 self.items
377 .iter()
378 .filter(|item| item.name.is_none())
379 .map(|item| item.value.v.clone())
380 .collect()
381 }
382
383 #[func(name = "named")]
385 pub fn to_named(&self) -> Dict {
386 self.items
387 .iter()
388 .filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone())))
389 .collect()
390 }
391
392 #[func]
402 pub fn filter(
403 self,
404 engine: &mut Engine,
405 context: Tracked<Context>,
406 test: Func,
408 ) -> SourceResult<Args> {
409 let mut run_test = |v: &Value| {
410 test.call(engine, context, [v.clone()])?
411 .cast::<bool>()
412 .at(test.span())
413 };
414 self.into_iter()
415 .filter_map(|arg| {
416 run_test(&arg.value.v).map(|b| b.then_some(arg)).transpose()
417 })
418 .collect()
419 }
420
421 #[func]
431 pub fn map(
432 self,
433 engine: &mut Engine,
434 context: Tracked<Context>,
435 mapper: Func,
437 ) -> SourceResult<Args> {
438 self.into_iter()
439 .map(|arg| {
440 let mapped_value = mapper.call(engine, context, [arg.value.v])?;
441 Ok(Arg {
442 span: arg.span,
443 name: arg.name,
444 value: Spanned::detached(mapped_value),
445 })
446 })
447 .collect()
448 }
449}
450
451impl Debug for Args {
452 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
453 f.debug_list().entries(&self.items).finish()
454 }
455}
456
457impl Repr for Args {
458 fn repr(&self) -> EcoString {
459 let pieces = self.items.iter().map(Arg::repr).collect::<Vec<_>>();
460 eco_format!("arguments{}", repr::pretty_array_like(&pieces, false))
461 }
462}
463
464impl PartialEq for Args {
465 fn eq(&self, other: &Self) -> bool {
466 self.to_pos() == other.to_pos() && self.to_named() == other.to_named()
467 }
468}
469
470impl Add for Args {
471 type Output = Self;
472
473 fn add(mut self, rhs: Self) -> Self::Output {
474 self.items.retain(|item| {
475 !item.name.as_ref().is_some_and(|name| {
476 rhs.items.iter().any(|a| a.name.as_ref() == Some(name))
477 })
478 });
479 self.items.extend(rhs.items);
480 self.span = Span::detached();
481 self
482 }
483}
484
485impl FromIterator<Arg> for Args {
486 fn from_iter<T: IntoIterator<Item = Arg>>(iter: T) -> Self {
487 Self {
488 span: Span::detached(),
489 items: iter.into_iter().collect(),
490 }
491 }
492}
493
494impl IntoIterator for Args {
495 type Item = Arg;
496 type IntoIter = <EcoVec<Arg> as IntoIterator>::IntoIter;
497
498 fn into_iter(self) -> Self::IntoIter {
499 self.items.into_iter()
500 }
501}
502
503impl<'a> IntoIterator for &'a Args {
504 type Item = &'a Arg;
505 type IntoIter = slice::Iter<'a, Arg>;
506
507 fn into_iter(self) -> Self::IntoIter {
508 self.items.iter()
509 }
510}
511
512#[derive(Clone, Hash)]
514pub struct Arg {
515 pub span: Span,
517 pub name: Option<Str>,
519 pub value: Spanned<Value>,
521}
522
523impl Debug for Arg {
524 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
525 if let Some(name) = &self.name {
526 name.fmt(f)?;
527 f.write_str(": ")?;
528 self.value.v.fmt(f)
529 } else {
530 self.value.v.fmt(f)
531 }
532 }
533}
534
535impl Repr for Arg {
536 fn repr(&self) -> EcoString {
537 if let Some(name) = &self.name {
538 eco_format!("{}: {}", name, self.value.v.repr())
539 } else {
540 self.value.v.repr()
541 }
542 }
543}
544
545impl PartialEq for Arg {
546 fn eq(&self, other: &Self) -> bool {
547 self.name == other.name && self.value.v == other.value.v
548 }
549}
550
551pub trait IntoArgs {
553 fn into_args(self, fallback: Span) -> Args;
556}
557
558impl IntoArgs for Args {
559 fn into_args(self, fallback: Span) -> Args {
560 self.spanned(fallback)
561 }
562}
563
564impl<I, T> IntoArgs for I
565where
566 I: IntoIterator<Item = T>,
567 T: IntoValue,
568{
569 fn into_args(self, fallback: Span) -> Args {
570 Args::new(fallback, self)
571 }
572}
573
574#[cold]
576fn missing_key_no_default(key: ArgumentKey) -> EcoString {
577 match key {
578 ArgumentKey::Index(i) => eco_format!(
579 "no positional argument at index {i} and no default value was specified",
580 ),
581 ArgumentKey::Name(name) => eco_format!(
582 "no named argument {} and no default value was specified",
583 name.repr()
584 ),
585 }
586}