typst_library/foundations/
args.rs1use std::fmt::{self, Debug, Formatter};
2use std::ops::Add;
3
4use ecow::{eco_format, eco_vec, EcoString, EcoVec};
5use typst_syntax::{Span, Spanned};
6
7use crate::diag::{bail, error, At, SourceDiagnostic, SourceResult, StrResult};
8use crate::foundations::{
9 cast, func, repr, scope, ty, Array, Dict, FromValue, IntoValue, Repr, Str, Value,
10};
11
12#[ty(scope, cast, name = "arguments")]
43#[derive(Clone, Hash)]
44#[allow(clippy::derived_hash_with_manual_eq)]
45pub struct Args {
46 pub span: Span,
49 pub items: EcoVec<Arg>,
51}
52
53impl Args {
54 pub fn new<T: IntoValue>(span: Span, values: impl IntoIterator<Item = T>) -> Self {
56 let items = values
57 .into_iter()
58 .map(|value| Arg {
59 span,
60 name: None,
61 value: Spanned::new(value.into_value(), span),
62 })
63 .collect();
64 Self { span, items }
65 }
66
67 pub fn spanned(mut self, span: Span) -> Self {
69 if self.span.is_detached() {
70 self.span = span;
71 }
72 self
73 }
74
75 pub fn remaining(&self) -> usize {
77 self.items.iter().filter(|slot| slot.name.is_none()).count()
78 }
79
80 pub fn insert(&mut self, index: usize, span: Span, value: Value) {
82 self.items.insert(
83 index,
84 Arg {
85 span: self.span,
86 name: None,
87 value: Spanned::new(value, span),
88 },
89 )
90 }
91
92 pub fn push(&mut self, span: Span, value: Value) {
94 self.items.push(Arg {
95 span: self.span,
96 name: None,
97 value: Spanned::new(value, span),
98 })
99 }
100
101 pub fn eat<T>(&mut self) -> SourceResult<Option<T>>
103 where
104 T: FromValue<Spanned<Value>>,
105 {
106 for (i, slot) in self.items.iter().enumerate() {
107 if slot.name.is_none() {
108 let value = self.items.remove(i).value;
109 let span = value.span;
110 return T::from_value(value).at(span).map(Some);
111 }
112 }
113 Ok(None)
114 }
115
116 pub fn consume(&mut self, n: usize) -> SourceResult<Vec<Arg>> {
118 let mut list = vec![];
119
120 let mut i = 0;
121 while i < self.items.len() && list.len() < n {
122 if self.items[i].name.is_none() {
123 list.push(self.items.remove(i));
124 } else {
125 i += 1;
126 }
127 }
128
129 if list.len() < n {
130 bail!(self.span, "not enough arguments");
131 }
132
133 Ok(list)
134 }
135
136 pub fn expect<T>(&mut self, what: &str) -> SourceResult<T>
141 where
142 T: FromValue<Spanned<Value>>,
143 {
144 match self.eat()? {
145 Some(v) => Ok(v),
146 None => bail!(self.missing_argument(what)),
147 }
148 }
149
150 fn missing_argument(&self, what: &str) -> SourceDiagnostic {
152 for item in &self.items {
153 let Some(name) = item.name.as_deref() else { continue };
154 if name == what {
155 return error!(
156 item.span,
157 "the argument `{what}` is positional";
158 hint: "try removing `{}:`", name,
159 );
160 }
161 }
162
163 error!(self.span, "missing argument: {what}")
164 }
165
166 pub fn find<T>(&mut self) -> SourceResult<Option<T>>
168 where
169 T: FromValue<Spanned<Value>>,
170 {
171 for (i, slot) in self.items.iter().enumerate() {
172 if slot.name.is_none() && T::castable(&slot.value.v) {
173 let value = self.items.remove(i).value;
174 let span = value.span;
175 return T::from_value(value).at(span).map(Some);
176 }
177 }
178 Ok(None)
179 }
180
181 pub fn all<T>(&mut self) -> SourceResult<Vec<T>>
183 where
184 T: FromValue<Spanned<Value>>,
185 {
186 let mut list = vec![];
187 let mut errors = eco_vec![];
188 self.items.retain(|item| {
189 if item.name.is_some() {
190 return true;
191 }
192 let span = item.value.span;
193 let spanned = Spanned::new(std::mem::take(&mut item.value.v), span);
194 match T::from_value(spanned).at(span) {
195 Ok(val) => list.push(val),
196 Err(diags) => errors.extend(diags),
197 }
198 false
199 });
200 if !errors.is_empty() {
201 return Err(errors);
202 }
203 Ok(list)
204 }
205
206 pub fn named<T>(&mut self, name: &str) -> SourceResult<Option<T>>
209 where
210 T: FromValue<Spanned<Value>>,
211 {
212 let mut i = 0;
215 let mut found = None;
216 while i < self.items.len() {
217 if self.items[i].name.as_deref() == Some(name) {
218 let value = self.items.remove(i).value;
219 let span = value.span;
220 found = Some(T::from_value(value).at(span)?);
221 } else {
222 i += 1;
223 }
224 }
225 Ok(found)
226 }
227
228 pub fn named_or_find<T>(&mut self, name: &str) -> SourceResult<Option<T>>
230 where
231 T: FromValue<Spanned<Value>>,
232 {
233 match self.named(name)? {
234 Some(value) => Ok(Some(value)),
235 None => self.find(),
236 }
237 }
238
239 pub fn take(&mut self) -> Self {
241 Self {
242 span: self.span,
243 items: std::mem::take(&mut self.items),
244 }
245 }
246
247 pub fn finish(self) -> SourceResult<()> {
250 if let Some(arg) = self.items.first() {
251 match &arg.name {
252 Some(name) => bail!(arg.span, "unexpected argument: {name}"),
253 _ => bail!(arg.span, "unexpected argument"),
254 }
255 }
256 Ok(())
257 }
258}
259
260#[derive(Debug, Clone, Eq, PartialEq)]
263pub enum ArgumentKey {
264 Index(i64),
265 Name(Str),
266}
267
268cast! {
269 ArgumentKey,
270 v: i64 => Self::Index(v),
271 v: Str => Self::Name(v),
272}
273
274impl Args {
275 fn get(&self, key: &ArgumentKey) -> Option<&Value> {
276 let item = match key {
277 &ArgumentKey::Index(index) => {
278 let mut iter = self.items.iter().filter(|item| item.name.is_none());
279 if index < 0 {
280 let index = (-(index + 1)).try_into().ok()?;
281 iter.nth_back(index)
282 } else {
283 let index = index.try_into().ok()?;
284 iter.nth(index)
285 }
286 }
287 ArgumentKey::Name(name) => {
289 self.items.iter().rfind(|item| item.name.as_ref() == Some(name))
290 }
291 };
292 item.map(|item| &item.value.v)
293 }
294}
295
296#[scope]
297impl Args {
298 #[func(constructor)]
307 pub fn construct(
308 args: &mut Args,
309 #[external]
311 #[variadic]
312 arguments: Vec<Value>,
313 ) -> Args {
314 args.take()
315 }
316
317 #[func]
325 pub fn at(
326 &self,
327 key: ArgumentKey,
329 #[named]
331 default: Option<Value>,
332 ) -> StrResult<Value> {
333 self.get(&key)
334 .cloned()
335 .or(default)
336 .ok_or_else(|| missing_key_no_default(key))
337 }
338
339 #[func(name = "pos", title = "Positional")]
341 pub fn to_pos(&self) -> Array {
342 self.items
343 .iter()
344 .filter(|item| item.name.is_none())
345 .map(|item| item.value.v.clone())
346 .collect()
347 }
348
349 #[func(name = "named")]
351 pub fn to_named(&self) -> Dict {
352 self.items
353 .iter()
354 .filter_map(|item| item.name.clone().map(|name| (name, item.value.v.clone())))
355 .collect()
356 }
357}
358
359impl Debug for Args {
360 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
361 f.debug_list().entries(&self.items).finish()
362 }
363}
364
365impl Repr for Args {
366 fn repr(&self) -> EcoString {
367 let pieces = self.items.iter().map(Arg::repr).collect::<Vec<_>>();
368 eco_format!("arguments{}", repr::pretty_array_like(&pieces, false))
369 }
370}
371
372impl PartialEq for Args {
373 fn eq(&self, other: &Self) -> bool {
374 self.to_pos() == other.to_pos() && self.to_named() == other.to_named()
375 }
376}
377
378impl Add for Args {
379 type Output = Self;
380
381 fn add(mut self, rhs: Self) -> Self::Output {
382 self.items.retain(|item| {
383 !item.name.as_ref().is_some_and(|name| {
384 rhs.items.iter().any(|a| a.name.as_ref() == Some(name))
385 })
386 });
387 self.items.extend(rhs.items);
388 self.span = Span::detached();
389 self
390 }
391}
392
393#[derive(Clone, Hash)]
395#[allow(clippy::derived_hash_with_manual_eq)]
396pub struct Arg {
397 pub span: Span,
399 pub name: Option<Str>,
401 pub value: Spanned<Value>,
403}
404
405impl Debug for Arg {
406 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
407 if let Some(name) = &self.name {
408 name.fmt(f)?;
409 f.write_str(": ")?;
410 self.value.v.fmt(f)
411 } else {
412 self.value.v.fmt(f)
413 }
414 }
415}
416
417impl Repr for Arg {
418 fn repr(&self) -> EcoString {
419 if let Some(name) = &self.name {
420 eco_format!("{}: {}", name, self.value.v.repr())
421 } else {
422 self.value.v.repr()
423 }
424 }
425}
426
427impl PartialEq for Arg {
428 fn eq(&self, other: &Self) -> bool {
429 self.name == other.name && self.value.v == other.value.v
430 }
431}
432
433pub trait IntoArgs {
435 fn into_args(self, fallback: Span) -> Args;
438}
439
440impl IntoArgs for Args {
441 fn into_args(self, fallback: Span) -> Args {
442 self.spanned(fallback)
443 }
444}
445
446impl<I, T> IntoArgs for I
447where
448 I: IntoIterator<Item = T>,
449 T: IntoValue,
450{
451 fn into_args(self, fallback: Span) -> Args {
452 Args::new(fallback, self)
453 }
454}
455
456#[cold]
458fn missing_key_no_default(key: ArgumentKey) -> EcoString {
459 eco_format!(
460 "arguments do not contain key {} \
461 and no default value was specified",
462 match key {
463 ArgumentKey::Index(i) => i.repr(),
464 ArgumentKey::Name(name) => name.repr(),
465 }
466 )
467}