diff --git a/src/env/fs.rs b/src/env/fs.rs index 45850c693..969ee9e04 100644 --- a/src/env/fs.rs +++ b/src/env/fs.rs @@ -130,11 +130,7 @@ impl FsLoader { fn parse_face(&mut self, path: &Path, face: &Face<'_>, index: u32) -> io::Result<()> { fn find_name(face: &Face, name_id: u16) -> Option { face.names().find_map(|entry| { - if entry.name_id() == name_id { - entry.to_string() - } else { - None - } + (entry.name_id() == name_id).then(|| entry.to_string()).flatten() }) } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 88110d88f..da7fca536 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -401,7 +401,7 @@ impl Eval for ClosureExpr { for param in params.iter() { // Set the parameter to `none` if the argument is missing. let value = - args.require::(ctx, param.as_str()).unwrap_or_default(); + args.eat_expect::(ctx, param.as_str()).unwrap_or_default(); ctx.scopes.def_mut(param.as_str(), value); } diff --git a/src/eval/value.rs b/src/eval/value.rs index 33592e5d6..a69398f09 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -7,7 +7,6 @@ use std::rc::Rc; use super::{EvalContext, NodeMap}; use crate::color::Color; -use crate::diag::DiagSet; use crate::exec::ExecContext; use crate::geom::{Angle, Length, Linear, Relative}; use crate::syntax::{Span, Spanned, Tree}; @@ -261,51 +260,67 @@ pub struct FuncArgs { } impl FuncArgs { - /// Find and remove the first convertible positional argument. - pub fn find(&mut self, ctx: &mut EvalContext) -> Option + /// Find and consume the first castable positional argument. + pub fn eat(&mut self, ctx: &mut EvalContext) -> Option where T: Cast>, { - (0 .. self.items.len()).find_map(move |i| self.try_take(&mut ctx.diags, i)) + (0 .. self.items.len()).find_map(|index| { + let slot = &mut self.items[index]; + if slot.name.is_some() { + return None; + } + + let value = std::mem::replace(&mut slot.value, Spanned::zero(Value::None)); + let span = value.span; + + match T::cast(value) { + CastResult::Ok(t) => { + self.items.remove(index); + Some(t) + } + CastResult::Warn(t, m) => { + self.items.remove(index); + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + slot.value = value; + None + } + } + }) } - /// Find and remove the first convertible positional argument, producing an - /// error if no match was found. - pub fn require(&mut self, ctx: &mut EvalContext, what: &str) -> Option + /// Find and consume the first castable positional argument, producing a + /// `missing argument: {what}` error if no match was found. + pub fn eat_expect(&mut self, ctx: &mut EvalContext, what: &str) -> Option where T: Cast>, { - let found = self.find(ctx); + let found = self.eat(ctx); if found.is_none() { ctx.diag(error!(self.span, "missing argument: {}", what)); } found } - /// Filter out and remove all convertible positional arguments. - pub fn filter<'a, T>( - &'a mut self, - ctx: &'a mut EvalContext, - ) -> impl Iterator + 'a + /// Find, consume and collect all castable positional arguments. + /// + /// This function returns a vector instead of an iterator because the + /// iterator would require unique access to the context, rendering it rather + /// unusable. If you need to process arguments one-by-one, you probably want + /// to use a while-let loop together with [`eat()`](Self::eat). + pub fn eat_all(&mut self, ctx: &mut EvalContext) -> Vec where T: Cast>, { - let diags = &mut ctx.diags; - let mut i = 0; - std::iter::from_fn(move || { - while i < self.items.len() { - if let Some(val) = self.try_take(diags, i) { - return Some(val); - } - i += 1; - } - None - }) + std::iter::from_fn(|| self.eat(ctx)).collect() } - /// Convert and remove the value for the given named argument, producing an + /// Cast and remove the value for the given named argument, producing an /// error if the conversion fails. - pub fn get(&mut self, ctx: &mut EvalContext, name: &str) -> Option + pub fn eat_named(&mut self, ctx: &mut EvalContext, name: &str) -> Option where T: Cast>, { @@ -315,24 +330,8 @@ impl FuncArgs { .position(|arg| arg.name.as_ref().map(|s| s.v.as_str()) == Some(name))?; let value = self.items.remove(index).value; - self.cast(ctx, value) - } - - /// Produce "unexpected argument" errors for all remaining arguments. - pub fn finish(self, ctx: &mut EvalContext) { - for arg in &self.items { - if arg.value.v != Value::Error { - ctx.diag(error!(arg.span(), "unexpected argument")); - } - } - } - - /// Cast the value into `T`, generating an error if the conversion fails. - fn cast(&self, ctx: &mut EvalContext, value: Spanned) -> Option - where - T: Cast>, - { let span = value.span; + match T::cast(value) { CastResult::Ok(t) => Some(t), CastResult::Warn(t, m) => { @@ -344,39 +343,18 @@ impl FuncArgs { span, "expected {}, found {}", T::TYPE_NAME, - value.v.type_name() + value.v.type_name(), )); None } } } - /// Try to take and cast a positional argument in the i'th slot into `T`, - /// putting it back if the conversion fails. - fn try_take(&mut self, diags: &mut DiagSet, i: usize) -> Option - where - T: Cast>, - { - let slot = &mut self.items[i]; - if slot.name.is_some() { - return None; - } - - let value = std::mem::replace(&mut slot.value, Spanned::zero(Value::None)); - let span = value.span; - match T::cast(value) { - CastResult::Ok(t) => { - self.items.remove(i); - Some(t) - } - CastResult::Warn(t, m) => { - self.items.remove(i); - diags.insert(warning!(span, "{}", m)); - Some(t) - } - CastResult::Err(value) => { - slot.value = value; - None + /// Produce "unexpected argument" errors for all remaining arguments. + pub fn finish(self, ctx: &mut EvalContext) { + for arg in &self.items { + if arg.value.v != Value::Error { + ctx.diag(error!(arg.span(), "unexpected argument")); } } } diff --git a/src/library/align.rs b/src/library/align.rs index 3b1ea24ea..35d925eb8 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -24,11 +24,11 @@ use super::*; /// - `top` /// - `bottom` pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let first = args.find::(ctx); - let second = args.find::(ctx); - let mut horizontal = args.get::(ctx, "horizontal"); - let mut vertical = args.get::(ctx, "vertical"); - let body = args.find::(ctx); + let first = args.eat::(ctx); + let second = args.eat::(ctx); + let mut horizontal = args.eat_named::(ctx, "horizontal"); + let mut vertical = args.eat_named::(ctx, "vertical"); + let body = args.eat::(ctx); for value in first.into_iter().chain(second) { match value.axis() { diff --git a/src/library/basic.rs b/src/library/basic.rs index 48925122f..a094f039d 100644 --- a/src/library/basic.rs +++ b/src/library/basic.rs @@ -11,7 +11,7 @@ use super::*; /// # Return value /// The name of the value's type as a string. pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - match args.require::(ctx, "value") { + match args.eat_expect::(ctx, "value") { Some(value) => value.type_name().into(), None => Value::Error, } @@ -25,7 +25,7 @@ pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { /// # Return value /// The string representation of the value. pub fn repr(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - match args.require::(ctx, "value") { + match args.eat_expect::(ctx, "value") { Some(value) => pretty(&value).into(), None => Value::Error, } @@ -42,10 +42,10 @@ pub fn repr(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { /// # Return value /// The color with the given components. pub fn rgb(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let r = args.require(ctx, "red component"); - let g = args.require(ctx, "green component"); - let b = args.require(ctx, "blue component"); - let a = args.find(ctx); + let r = args.eat_expect(ctx, "red component"); + let g = args.eat_expect(ctx, "green component"); + let b = args.eat_expect(ctx, "blue component"); + let a = args.eat(ctx); let mut clamp = |component: Option>, default| { component.map_or(default, |c| { diff --git a/src/library/font.rs b/src/library/font.rs index df7b90058..64aec75b8 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -56,18 +56,18 @@ use super::*; /// - `baseline` /// - `descender` pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let size = args.find::(ctx); - let list: Vec<_> = args.filter::(ctx).collect(); - let style = args.get(ctx, "style"); - let weight = args.get(ctx, "weight"); - let stretch = args.get(ctx, "stretch"); - let top_edge = args.get(ctx, "top-edge"); - let bottom_edge = args.get(ctx, "bottom-edge"); - let color = args.get(ctx, "color"); - let serif = args.get(ctx, "serif"); - let sans_serif = args.get(ctx, "sans-serif"); - let monospace = args.get(ctx, "monospace"); - let body = args.find::(ctx); + let size = args.eat::(ctx); + let list = args.eat_all::(ctx); + let style = args.eat_named(ctx, "style"); + let weight = args.eat_named(ctx, "weight"); + let stretch = args.eat_named(ctx, "stretch"); + let top_edge = args.eat_named(ctx, "top-edge"); + let bottom_edge = args.eat_named(ctx, "bottom-edge"); + let color = args.eat_named(ctx, "color"); + let serif = args.eat_named(ctx, "serif"); + let sans_serif = args.eat_named(ctx, "sans-serif"); + let monospace = args.eat_named(ctx, "monospace"); + let body = args.eat::(ctx); Value::template("font", move |ctx| { let snapshot = ctx.state.clone(); diff --git a/src/library/image.rs b/src/library/image.rs index 0287eaf5c..9e730a0e3 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -14,9 +14,9 @@ use crate::layout::{AnyNode, Areas, Element, Frame, Layout, LayoutContext}; /// # Return value /// A template that inserts an image. pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let path = args.require::>(ctx, "path to image file"); - let width = args.get(ctx, "width"); - let height = args.get(ctx, "height"); + let path = args.eat_expect::>(ctx, "path to image file"); + let width = args.eat_named(ctx, "width"); + let height = args.eat_named(ctx, "height"); Value::template("image", move |ctx| { if let Some(path) = &path { diff --git a/src/library/lang.rs b/src/library/lang.rs index 1248b707d..9e163755b 100644 --- a/src/library/lang.rs +++ b/src/library/lang.rs @@ -16,8 +16,8 @@ use super::*; /// - `ltr` /// - `rtl` pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let iso = args.find::(ctx).map(|s| s.to_ascii_lowercase()); - let dir = args.get::>(ctx, "dir"); + let iso = args.eat::(ctx).map(|s| s.to_ascii_lowercase()); + let dir = args.eat_named::>(ctx, "dir"); Value::template("lang", move |ctx| { if let Some(iso) = &iso { diff --git a/src/library/markup.rs b/src/library/markup.rs index 0ace43b6f..c218746ba 100644 --- a/src/library/markup.rs +++ b/src/library/markup.rs @@ -43,7 +43,7 @@ pub fn parbreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value { /// A template that flips the boldness of text. The effect is scoped to the /// body if present. pub fn strong(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let body = args.find::(ctx); + let body = args.eat::(ctx); Value::template(Node::STRONG, move |ctx| { let snapshot = ctx.state.clone(); ctx.state.font.strong ^= true; @@ -70,7 +70,7 @@ pub fn strong(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { /// A template that flips whether text is set in italics. The effect is scoped /// to the body if present. pub fn emph(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let body = args.find::(ctx); + let body = args.eat::(ctx); Value::template(Node::EMPH, move |ctx| { let snapshot = ctx.state.clone(); ctx.state.font.emph ^= true; @@ -104,9 +104,9 @@ pub fn emph(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { /// A template that sets the body as a section heading, that is, large and in /// bold. pub fn heading(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let level = args.get(ctx, HeadingNode::LEVEL).unwrap_or(1); + let level = args.eat_named(ctx, HeadingNode::LEVEL).unwrap_or(1); let body = args - .require::(ctx, HeadingNode::BODY) + .eat_expect::(ctx, HeadingNode::BODY) .unwrap_or_default(); Value::template(Node::HEADING, move |ctx| { @@ -149,9 +149,9 @@ pub fn heading(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { /// A template that sets the text raw, that is, in monospace and optionally with /// syntax highlighting. pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let text = args.require::(ctx, RawNode::TEXT).unwrap_or_default(); - let _lang = args.get::(ctx, RawNode::LANG); - let block = args.get(ctx, RawNode::BLOCK).unwrap_or(false); + let text = args.eat_expect::(ctx, RawNode::TEXT).unwrap_or_default(); + let _lang = args.eat_named::(ctx, RawNode::LANG); + let block = args.eat_named(ctx, RawNode::BLOCK).unwrap_or(false); Value::template(Node::RAW, move |ctx| { if block { diff --git a/src/library/math.rs b/src/library/math.rs index 3392f6644..89c45ecc3 100644 --- a/src/library/math.rs +++ b/src/library/math.rs @@ -27,17 +27,15 @@ pub fn max(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { } /// Find the minimum or maximum of a sequence of values. -fn minmax(ctx: &mut EvalContext, args: &mut FuncArgs, which: Ordering) -> Value { - let mut values = args.filter::(ctx); +fn minmax(ctx: &mut EvalContext, args: &mut FuncArgs, goal: Ordering) -> Value { let mut extremum = None; - for value in &mut values { + while let Some(value) = args.eat::(ctx) { if let Some(prev) = &extremum { match value.cmp(&prev) { - Some(ord) if ord == which => extremum = Some(value), + Some(ordering) if ordering == goal => extremum = Some(value), Some(_) => {} None => { - drop(values); ctx.diag(error!( args.span, "cannot compare {} with {}", @@ -52,9 +50,8 @@ fn minmax(ctx: &mut EvalContext, args: &mut FuncArgs, which: Ordering) -> Value } } - drop(values); extremum.unwrap_or_else(|| { - args.require::(ctx, "value"); + args.eat_expect::(ctx, "value"); Value::Error }) } diff --git a/src/library/pad.rs b/src/library/pad.rs index 7c422239d..24643948c 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -16,12 +16,12 @@ use crate::layout::PadNode; /// # Return value /// A template that sets the body into a padded area. pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let all = args.find(ctx); - let left = args.get(ctx, "left"); - let top = args.get(ctx, "top"); - let right = args.get(ctx, "right"); - let bottom = args.get(ctx, "bottom"); - let body = args.require::(ctx, "body").unwrap_or_default(); + let all = args.eat(ctx); + let left = args.eat_named(ctx, "left"); + let top = args.eat_named(ctx, "top"); + let right = args.eat_named(ctx, "right"); + let bottom = args.eat_named(ctx, "bottom"); + let body = args.eat_expect::(ctx, "body").unwrap_or_default(); let padding = Sides::new( left.or(all).unwrap_or_default(), diff --git a/src/library/page.rs b/src/library/page.rs index 1694b7d47..59cca1f0b 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -22,22 +22,22 @@ use crate::paper::{Paper, PaperClass}; /// A template that configures page properties. The effect is scoped to the body /// if present. pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let paper = args.find::>(ctx).and_then(|name| { + let paper = args.eat::>(ctx).and_then(|name| { Paper::from_name(&name.v).or_else(|| { ctx.diag(error!(name.span, "invalid paper name")); None }) }); - let width = args.get(ctx, "width"); - let height = args.get(ctx, "height"); - let margins = args.get(ctx, "margins"); - let left = args.get(ctx, "left"); - let top = args.get(ctx, "top"); - let right = args.get(ctx, "right"); - let bottom = args.get(ctx, "bottom"); - let flip = args.get(ctx, "flip"); - let body = args.find::(ctx); + let width = args.eat_named(ctx, "width"); + let height = args.eat_named(ctx, "height"); + let margins = args.eat_named(ctx, "margins"); + let left = args.eat_named(ctx, "left"); + let top = args.eat_named(ctx, "top"); + let right = args.eat_named(ctx, "right"); + let bottom = args.eat_named(ctx, "bottom"); + let flip = args.eat_named(ctx, "flip"); + let body = args.eat::(ctx); let span = args.span; Value::template("page", move |ctx| { diff --git a/src/library/par.rs b/src/library/par.rs index 92fc20e8a..46ba85c45 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -10,9 +10,9 @@ use super::*; /// # Return value /// A template that configures paragraph properties. pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let spacing = args.get(ctx, "spacing"); - let leading = args.get(ctx, "leading"); - let word_spacing = args.get(ctx, "word-spacing"); + let spacing = args.eat_named(ctx, "spacing"); + let leading = args.eat_named(ctx, "leading"); + let word_spacing = args.eat_named(ctx, "word-spacing"); Value::template("par", move |ctx| { if let Some(spacing) = spacing { diff --git a/src/library/shapes.rs b/src/library/shapes.rs index d6cdba8e2..4d588140f 100644 --- a/src/library/shapes.rs +++ b/src/library/shapes.rs @@ -17,10 +17,10 @@ use crate::layout::{BackgroundNode, BackgroundShape, Fill, FixedNode, PadNode}; /// # Return value /// A template that inserts a rectangle and sets the body into it. pub fn rect(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let width = args.get(ctx, "width"); - let height = args.get(ctx, "height"); - let fill = args.get(ctx, "fill"); - let body = args.find::(ctx).unwrap_or_default(); + let width = args.eat_named(ctx, "width"); + let height = args.eat_named(ctx, "height"); + let fill = args.eat_named(ctx, "fill"); + let body = args.eat::(ctx).unwrap_or_default(); rect_impl("rect", width, height, None, fill, body) } @@ -42,11 +42,11 @@ pub fn rect(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { /// # Return value /// A template that inserts a square and sets the body into it. pub fn square(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let length = args.get::(ctx, "length").map(Linear::from); - let width = length.or_else(|| args.get(ctx, "width")); - let height = if width.is_none() { args.get(ctx, "height") } else { None }; - let fill = args.get(ctx, "fill"); - let body = args.find::(ctx).unwrap_or_default(); + let length = args.eat_named::(ctx, "length").map(Linear::from); + let width = length.or_else(|| args.eat_named(ctx, "width")); + let height = width.is_none().then(|| args.eat_named(ctx, "height")).flatten(); + let fill = args.eat_named(ctx, "fill"); + let body = args.eat::(ctx).unwrap_or_default(); rect_impl("square", width, height, Some(1.0), fill, body) } @@ -89,10 +89,10 @@ fn rect_impl( /// # Return value /// A template that inserts an ellipse and sets the body into it. pub fn ellipse(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let width = args.get(ctx, "width"); - let height = args.get(ctx, "height"); - let fill = args.get(ctx, "fill"); - let body = args.find::(ctx).unwrap_or_default(); + let width = args.eat_named(ctx, "width"); + let height = args.eat_named(ctx, "height"); + let fill = args.eat_named(ctx, "fill"); + let body = args.eat::(ctx).unwrap_or_default(); ellipse_impl("ellipse", width, height, None, fill, body) } @@ -114,11 +114,11 @@ pub fn ellipse(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { /// # Return value /// A template that inserts a circle and sets the body into it. pub fn circle(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let radius = args.get::(ctx, "radius").map(|r| 2.0 * Linear::from(r)); - let width = radius.or_else(|| args.get(ctx, "width")); - let height = if width.is_none() { args.get(ctx, "height") } else { None }; - let fill = args.get(ctx, "fill"); - let body = args.find::(ctx).unwrap_or_default(); + let radius = args.eat_named::(ctx, "radius").map(|r| 2.0 * Linear::from(r)); + let width = radius.or_else(|| args.eat_named(ctx, "width")); + let height = width.is_none().then(|| args.eat_named(ctx, "height")).flatten(); + let fill = args.eat_named(ctx, "fill"); + let body = args.eat::(ctx).unwrap_or_default(); ellipse_impl("circle", width, height, Some(1.0), fill, body) } diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 36cd88fbd..ff6aaa69f 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -28,7 +28,7 @@ fn spacing_impl( args: &mut FuncArgs, axis: GenAxis, ) -> Value { - let spacing: Option = args.require(ctx, "spacing"); + let spacing: Option = args.eat_expect(ctx, "spacing"); Value::template(name, move |ctx| { if let Some(linear) = spacing { let amount = linear.resolve(ctx.state.font.resolve_size()); diff --git a/tests/typeset.rs b/tests/typeset.rs index 1e1964468..0d862c811 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -321,8 +321,8 @@ fn register_helpers(scope: &mut Scope, panics: Rc>>) { } let test = move |ctx: &mut EvalContext, args: &mut FuncArgs| -> Value { - let lhs = args.require::(ctx, "left-hand side"); - let rhs = args.require::(ctx, "right-hand side"); + let lhs = args.eat_expect::(ctx, "left-hand side"); + let rhs = args.eat_expect::(ctx, "right-hand side"); if lhs != rhs { panics.borrow_mut().push(Panic { pos: args.span.start, lhs, rhs }); Value::Str(format!("(panic)"))