diff --git a/src/eval/call.rs b/src/eval/call.rs deleted file mode 100644 index 1a80e15a3..000000000 --- a/src/eval/call.rs +++ /dev/null @@ -1,197 +0,0 @@ -use super::*; - -impl Eval for ExprCall { - type Output = Value; - - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let callee = self.callee.eval(ctx); - - if let Value::Func(func) = callee { - let func = func.clone(); - let mut args = self.args.eval(ctx); - let returned = func(ctx, &mut args); - args.finish(ctx); - - return returned; - } else if callee != Value::Error { - ctx.diag(error!( - self.callee.span(), - "expected function, found {}", - callee.type_name(), - )); - } - - Value::Error - } -} - -impl Eval for ExprArgs { - type Output = Args; - - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let mut pos = vec![]; - let mut named = vec![]; - - for arg in &self.items { - match arg { - Argument::Pos(expr) => { - pos.push(expr.eval(ctx).with_span(expr.span())); - } - Argument::Named(Named { name, expr }) => { - named.push(( - name.string.clone().with_span(name.span), - expr.eval(ctx).with_span(expr.span()), - )); - } - } - } - - Args { span: self.span, pos, named } - } -} - -/// Evaluated arguments to a function. -#[derive(Debug)] -pub struct Args { - /// The span of the whole argument list. - pub span: Span, - /// The positional arguments. - pub pos: SpanVec, - /// The named arguments. - pub named: Vec<(Spanned, Spanned)>, -} - -impl Args { - /// Find and remove the first convertible positional argument. - pub fn find(&mut self, ctx: &mut EvalContext) -> Option - where - T: Cast>, - { - (0 .. self.pos.len()).find_map(move |i| try_cast(ctx, &mut self.pos, i)) - } - - /// 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 - where - T: Cast>, - { - let found = self.find(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, 'b: 'a, T>( - &'a mut self, - ctx: &'a mut EvalContext<'b>, - ) -> impl Iterator + Captures<'a> + Captures<'b> - where - T: Cast>, - { - let mut i = 0; - std::iter::from_fn(move || { - while i < self.pos.len() { - if let Some(val) = try_cast(ctx, &mut self.pos, i) { - return Some(val); - } - i += 1; - } - None - }) - } - - /// Convert 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 - where - T: Cast>, - { - let index = self.named.iter().position(|(k, _)| k.v.as_str() == name)?; - let value = self.named.remove(index).1; - cast(ctx, value) - } - - /// Drain all remainings arguments into an array and a dictionary. - pub fn drain(&mut self) -> (ValueArray, ValueDict) { - let array = self.pos.drain(..).map(|s| s.v).collect(); - let dict = self.named.drain(..).map(|(k, v)| (k.v, v.v)).collect(); - (array, dict) - } - - /// Produce "unexpected argument" errors for all remaining arguments. - pub fn finish(self, ctx: &mut EvalContext) { - let a = self.pos.iter().map(|v| v.as_ref()); - let b = self.named.iter().map(|(k, v)| (&v.v).with_span(k.span.join(v.span))); - for value in a.chain(b) { - if value.v != &Value::Error { - ctx.diag(error!(value.span, "unexpected argument")); - } - } - } -} - -// This is a workaround because `-> impl Trait + 'a + 'b` does not work. -// -// See also: https://github.com/rust-lang/rust/issues/49431 -#[doc(hidden)] -pub trait Captures<'a> {} -impl<'a, T: ?Sized> Captures<'a> for T {} - -/// Cast the value into `T`, generating an error if the conversion fails. -fn cast(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) => { - ctx.diag(warning!(span, "{}", m)); - Some(t) - } - CastResult::Err(value) => { - ctx.diag(error!( - span, - "expected {}, found {}", - T::TYPE_NAME, - value.v.type_name() - )); - None - } - } -} - -/// Try to cast the value in the slot into `T`, putting it back if the -/// conversion fails. -fn try_cast( - ctx: &mut EvalContext, - vec: &mut Vec>, - i: usize, -) -> Option -where - T: Cast>, -{ - // Replace with error placeholder when conversion works since error values - // are ignored when generating "unexpected argument" errors. - let slot = &mut vec[i]; - let value = std::mem::replace(slot, Spanned::zero(Value::None)); - let span = value.span; - match T::cast(value) { - CastResult::Ok(t) => { - vec.remove(i); - Some(t) - } - CastResult::Warn(t, m) => { - vec.remove(i); - ctx.diag(warning!(span, "{}", m)); - Some(t) - } - CastResult::Err(value) => { - *slot = value; - None - } - } -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 2390a84f8..4f0beb34b 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -2,12 +2,10 @@ #[macro_use] mod value; -mod call; mod capture; mod ops; mod scope; -pub use call::*; pub use capture::*; pub use scope::*; pub use value::*; @@ -344,6 +342,54 @@ impl ExprBinary { } } +impl Eval for ExprCall { + type Output = Value; + + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let callee = self.callee.eval(ctx); + + if let Value::Func(func) = callee { + let func = func.clone(); + let mut args = self.args.eval(ctx); + let returned = func(ctx, &mut args); + args.finish(ctx); + + return returned; + } else if callee != Value::Error { + ctx.diag(error!( + self.callee.span(), + "expected function, found {}", + callee.type_name(), + )); + } + + Value::Error + } +} + +impl Eval for ExprArgs { + type Output = ValueArgs; + + fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + let items = self + .items + .iter() + .map(|arg| match arg { + ExprArg::Pos(expr) => ValueArg { + name: None, + value: expr.eval(ctx).with_span(expr.span()), + }, + ExprArg::Named(Named { name, expr }) => ValueArg { + name: Some(name.string.clone().with_span(name.span)), + value: expr.eval(ctx).with_span(expr.span()), + }, + }) + .collect(); + + ValueArgs { span: self.span, items } + } +} + impl Eval for ExprLet { type Output = Value; diff --git a/src/eval/value.rs b/src/eval/value.rs index dd1221e5b..94477aa5b 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -42,6 +42,8 @@ pub enum Value { Template(ValueTemplate), /// An executable function. Func(ValueFunc), + /// Arguments to a function. + Args(ValueArgs), /// Any object. Any(ValueAny), /// The result of invalid operations. @@ -50,11 +52,11 @@ pub enum Value { impl Value { /// Create a new template value consisting of a single dynamic node. - pub fn template(f: F) -> Self + pub fn template(name: impl Into, f: F) -> Self where F: Fn(&mut ExecContext) + 'static, { - Self::Template(vec![TemplateNode::Any(TemplateAny::new(f))]) + Self::Template(vec![TemplateNode::Any(TemplateAny::new(name, f))]) } /// The name of the stored value's type. @@ -74,6 +76,7 @@ impl Value { Self::Dict(_) => ValueDict::TYPE_NAME, Self::Template(_) => ValueTemplate::TYPE_NAME, Self::Func(_) => ValueFunc::TYPE_NAME, + Self::Args(_) => ValueArgs::TYPE_NAME, Self::Any(v) => v.type_name(), Self::Error => "error", } @@ -111,8 +114,9 @@ impl Pretty for Value { Value::Dict(v) => v.pretty(p), Value::Template(v) => v.pretty(p), Value::Func(v) => v.pretty(p), + Value::Args(v) => v.pretty(p), Value::Any(v) => v.pretty(p), - Value::Error => p.push_str("(error)"), + Value::Error => p.push_str(""), } } } @@ -194,16 +198,17 @@ impl Pretty for TemplateNode { /// A reference-counted dynamic template node (can implement custom behaviour). #[derive(Clone)] pub struct TemplateAny { + name: String, f: Rc, } impl TemplateAny { /// Create a new dynamic template value from a rust function or closure. - pub fn new(f: F) -> Self + pub fn new(name: impl Into, f: F) -> Self where F: Fn(&mut ExecContext) + 'static, { - Self { f: Rc::new(f) } + Self { name: name.into(), f: Rc::new(f) } } } @@ -224,7 +229,9 @@ impl Deref for TemplateAny { impl Pretty for TemplateAny { fn pretty(&self, p: &mut Printer) { - p.push_str(""); + p.push('<'); + p.push_str(&self.name); + p.push('>'); } } @@ -238,14 +245,14 @@ impl Debug for TemplateAny { #[derive(Clone)] pub struct ValueFunc { name: String, - f: Rc Value>, + f: Rc Value>, } impl ValueFunc { /// Create a new function value from a rust function or closure. pub fn new(name: impl Into, f: F) -> Self where - F: Fn(&mut EvalContext, &mut Args) -> Value + 'static, + F: Fn(&mut EvalContext, &mut ValueArgs) -> Value + 'static, { Self { name: name.into(), f: Rc::new(f) } } @@ -259,7 +266,7 @@ impl PartialEq for ValueFunc { } impl Deref for ValueFunc { - type Target = dyn Fn(&mut EvalContext, &mut Args) -> Value; + type Target = dyn Fn(&mut EvalContext, &mut ValueArgs) -> Value; fn deref(&self) -> &Self::Target { self.f.as_ref() @@ -268,7 +275,9 @@ impl Deref for ValueFunc { impl Pretty for ValueFunc { fn pretty(&self, p: &mut Printer) { + p.push('<'); p.push_str(&self.name); + p.push('>'); } } @@ -278,6 +287,180 @@ impl Debug for ValueFunc { } } +/// Evaluated arguments to a function. +#[derive(Debug, Clone, PartialEq)] +pub struct ValueArgs { + /// The span of the whole argument list. + pub span: Span, + /// The arguments. + pub items: Vec, +} + +impl ValueArgs { + /// Find and remove the first convertible positional argument. + pub fn find(&mut self, ctx: &mut EvalContext) -> Option + where + T: Cast>, + { + (0 .. self.items.len()).find_map(move |i| self.try_take(ctx, i)) + } + + /// 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 + where + T: Cast>, + { + let found = self.find(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, 'b: 'a, T>( + &'a mut self, + ctx: &'a mut EvalContext<'b>, + ) -> impl Iterator + Captures<'a> + Captures<'b> + where + T: Cast>, + { + let mut i = 0; + std::iter::from_fn(move || { + while i < self.items.len() { + if let Some(val) = self.try_take(ctx, i) { + return Some(val); + } + i += 1; + } + None + }) + } + + /// Convert 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 + where + T: Cast>, + { + let index = self + .items + .iter() + .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) => { + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + ctx.diag(error!( + span, + "expected {}, found {}", + T::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, ctx: &mut EvalContext, 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); + ctx.diag(warning!(span, "{}", m)); + Some(t) + } + CastResult::Err(value) => { + slot.value = value; + None + } + } + } +} + +impl Pretty for ValueArgs { + fn pretty(&self, p: &mut Printer) { + p.push('<'); + p.join(&self.items, ", ", |item, p| item.pretty(p)); + p.push('>'); + } +} + +// This is a workaround because `-> impl Trait + 'a + 'b` does not work. +// +// See also: https://github.com/rust-lang/rust/issues/49431 +#[doc(hidden)] +pub trait Captures<'a> {} +impl<'a, T: ?Sized> Captures<'a> for T {} + +/// An argument to a function call: `12` or `draw: false`. +#[derive(Debug, Clone, PartialEq)] +pub struct ValueArg { + /// The name of the argument (`None` for positional arguments). + pub name: Option>, + /// The value of the argument. + pub value: Spanned, +} + +impl ValueArg { + /// The source code location. + pub fn span(&self) -> Span { + match &self.name { + Some(name) => name.span.join(self.value.span), + None => self.value.span, + } + } +} + +impl Pretty for ValueArg { + fn pretty(&self, p: &mut Printer) { + if let Some(name) = &self.name { + p.push_str(&name.v); + p.push_str(": "); + } + self.value.v.pretty(p); + } +} + /// A wrapper around a dynamic value. pub struct ValueAny(Box); @@ -454,7 +637,7 @@ where } } -macro_rules! impl_primitive { +macro_rules! primitive { ($type:ty: $type_name:literal, $variant:path @@ -482,28 +665,29 @@ macro_rules! impl_primitive { }; } -impl_primitive! { bool: "boolean", Value::Bool } -impl_primitive! { i64: "integer", Value::Int } -impl_primitive! { +primitive! { bool: "boolean", Value::Bool } +primitive! { i64: "integer", Value::Int } +primitive! { f64: "float", Value::Float, Value::Int(v) => v as f64, } -impl_primitive! { Length: "length", Value::Length } -impl_primitive! { Angle: "angle", Value::Angle } -impl_primitive! { Relative: "relative", Value::Relative } -impl_primitive! { +primitive! { Length: "length", Value::Length } +primitive! { Angle: "angle", Value::Angle } +primitive! { Relative: "relative", Value::Relative } +primitive! { Linear: "linear", Value::Linear, Value::Length(v) => v.into(), Value::Relative(v) => v.into(), } -impl_primitive! { Color: "color", Value::Color } -impl_primitive! { String: "string", Value::Str } -impl_primitive! { ValueArray: "array", Value::Array } -impl_primitive! { ValueDict: "dictionary", Value::Dict } -impl_primitive! { ValueTemplate: "template", Value::Template } -impl_primitive! { ValueFunc: "function", Value::Func } +primitive! { Color: "color", Value::Color } +primitive! { String: "string", Value::Str } +primitive! { ValueArray: "array", Value::Array } +primitive! { ValueDict: "dictionary", Value::Dict } +primitive! { ValueTemplate: "template", Value::Template } +primitive! { ValueFunc: "function", Value::Func } +primitive! { ValueArgs: "arguments", Value::Args } impl From<&str> for Value { fn from(v: &str) -> Self { @@ -519,11 +703,23 @@ impl From for Value { /// Make a type usable as a [`Value`]. /// -/// Given a type `T`, this always implements the following traits: +/// Given a type `T`, this implements the following traits: /// - [`Type`] for `T`, /// - [`Cast`](Cast) for `T`. +/// +/// # Example +/// Make a type `FontFamily` that can be cast from a [`Value::Any`] variant +/// containing a `FontFamily` or from a string. +/// ``` +/// # use typst::typify; +/// # enum FontFamily { Named(String) } +/// typify! { +/// FontFamily: "font family", +/// Value::Str(string) => Self::Named(string.to_lowercase()) +/// } +/// ``` #[macro_export] -macro_rules! impl_type { +macro_rules! typify { ($type:ty: $type_name:literal $(, $pattern:pat => $out:expr)* @@ -575,23 +771,19 @@ mod tests { } #[test] - fn test_pretty_print_simple_values() { + fn test_pretty_print_value() { + // Simple values. test_pretty(Value::None, "none"); test_pretty(false, "false"); - test_pretty(12.4, "12.4"); + test_pretty(12, "12"); + test_pretty(3.14, "3.14"); test_pretty(Length::pt(5.5), "5.5pt"); test_pretty(Angle::deg(90.0), "90.0deg"); test_pretty(Relative::ONE / 2.0, "50.0%"); test_pretty(Relative::new(0.3) + Length::cm(2.0), "30.0% + 2.0cm"); test_pretty(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101"); test_pretty("hello", r#""hello""#); - test_pretty(ValueFunc::new("nil", |_, _| Value::None), "nil"); - test_pretty(ValueAny::new(1), "1"); - test_pretty(Value::Error, "(error)"); - } - #[test] - fn test_pretty_print_collections() { // Array. test_pretty(Value::Array(vec![]), "()"); test_pretty(vec![Value::None], "(none,)"); @@ -599,9 +791,47 @@ mod tests { // Dictionary. let mut dict = BTreeMap::new(); + test_pretty(dict.clone(), "(:)"); dict.insert("one".into(), Value::Int(1)); + test_pretty(dict.clone(), "(one: 1)"); dict.insert("two".into(), Value::Bool(false)); - test_pretty(BTreeMap::new(), "(:)"); test_pretty(dict, "(one: 1, two: false)"); + + // Template. + test_pretty( + vec![ + TemplateNode::Tree { + tree: Rc::new(vec![Node::Strong]), + map: HashMap::new(), + }, + TemplateNode::Any(TemplateAny::new("example", |_| {})), + ], + "[*]", + ); + + // Function and arguments. + test_pretty(ValueFunc::new("nil", |_, _| Value::None), ""); + test_pretty( + ValueArgs { + span: Span::ZERO, + items: vec![ + ValueArg { + name: Some(Spanned::zero("a".into())), + value: Spanned::zero(Value::Int(1)), + }, + ValueArg { + name: None, + value: Spanned::zero(Value::Int(2)), + }, + ], + }, + "", + ); + + // Any. + test_pretty(ValueAny::new(1), "1"); + + // Error. + test_pretty(Value::Error, ""); } } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 25edcce3c..c323cf491 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -59,17 +59,23 @@ pub trait ExecWith { impl ExecWith for Tree { fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap) { for node in self { - match node { - Node::Text(text) => ctx.push_text(text), - Node::Space => ctx.push_space(), - Node::Linebreak => ctx.apply_linebreak(), - Node::Parbreak => ctx.apply_parbreak(), - Node::Strong => ctx.state.font.strong ^= true, - Node::Emph => ctx.state.font.emph ^= true, - Node::Heading(heading) => heading.exec_with(ctx, map), - Node::Raw(raw) => raw.exec(ctx), - Node::Expr(expr) => map[&(expr as *const _)].exec(ctx), - } + node.exec_with(ctx, map); + } + } +} + +impl ExecWith for Node { + fn exec_with(&self, ctx: &mut ExecContext, map: &ExprMap) { + match self { + Node::Text(text) => ctx.push_text(text), + Node::Space => ctx.push_space(), + Node::Linebreak => ctx.apply_linebreak(), + Node::Parbreak => ctx.apply_parbreak(), + Node::Strong => ctx.state.font.strong ^= true, + Node::Emph => ctx.state.font.emph ^= true, + Node::Heading(heading) => heading.exec_with(ctx, map), + Node::Raw(raw) => raw.exec(ctx), + Node::Expr(expr) => map[&(expr as *const _)].exec(ctx), } } } diff --git a/src/library/extend.rs b/src/library/extend.rs index e388c9c84..6396274e0 100644 --- a/src/library/extend.rs +++ b/src/library/extend.rs @@ -7,7 +7,7 @@ use crate::prelude::*; /// /// # Return value /// The name of the value's type as a string. -pub fn type_(ctx: &mut EvalContext, args: &mut Args) -> Value { +pub fn type_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { if let Some(value) = args.require::(ctx, "value") { value.type_name().into() } else { diff --git a/src/library/insert.rs b/src/library/insert.rs index eff54e914..4f0f64898 100644 --- a/src/library/insert.rs +++ b/src/library/insert.rs @@ -10,12 +10,12 @@ use crate::prelude::*; /// /// # Positional arguments /// - Path to image file: of type `string`. -pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value { +pub fn image(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { let path = args.require::>(ctx, "path to image file"); let width = args.get(ctx, "width"); let height = args.get(ctx, "height"); - Value::template(move |ctx| { + Value::template("image", move |ctx| { if let Some(path) = &path { let loaded = ctx.env.resources.load(&path.v, ImageResource::parse); if let Some((res, img)) = loaded { diff --git a/src/library/layout.rs b/src/library/layout.rs index 44c985362..3577ee366 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -26,14 +26,14 @@ use crate::prelude::*; /// - `top` /// - `bottom` /// - `center` -pub fn align(ctx: &mut EvalContext, args: &mut Args) -> Value { +pub fn align(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { let first = args.find(ctx); let second = args.find(ctx); let hor = args.get(ctx, "horizontal"); let ver = args.get(ctx, "vertical"); let body = args.find::(ctx); - Value::template(move |ctx| { + Value::template("align", move |ctx| { let snapshot = ctx.state.clone(); let mut had = Gen::uniform(false); @@ -157,7 +157,7 @@ impl Switch for Alignment { } } -impl_type! { +typify! { Alignment: "alignment", } @@ -188,7 +188,7 @@ impl Display for Alignment { /// - `rtl` (right to left) /// - `ttb` (top to bottom) /// - `btt` (bottom to top) -pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value { +pub fn box_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { let width = args.get(ctx, "width"); let height = args.get(ctx, "height"); let main = args.get(ctx, "main-dir"); @@ -196,7 +196,7 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value { let color = args.get(ctx, "color"); let body = args.find::(ctx); - Value::template(move |ctx| { + Value::template("box", move |ctx| { let snapshot = ctx.state.clone(); ctx.set_dirs(Gen::new(main, cross)); @@ -230,7 +230,7 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value { }) } -impl_type! { +typify! { Dir: "direction" } @@ -238,7 +238,7 @@ impl_type! { /// /// # Positional arguments /// - Amount of spacing: of type `linear` relative to current font size. -pub fn h(ctx: &mut EvalContext, args: &mut Args) -> Value { +pub fn h(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { spacing(ctx, args, SpecAxis::Horizontal) } @@ -246,15 +246,15 @@ pub fn h(ctx: &mut EvalContext, args: &mut Args) -> Value { /// /// # Positional arguments /// - Amount of spacing: of type `linear` relative to current font size. -pub fn v(ctx: &mut EvalContext, args: &mut Args) -> Value { +pub fn v(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { spacing(ctx, args, SpecAxis::Vertical) } /// Apply spacing along a specific axis. -fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value { +fn spacing(ctx: &mut EvalContext, args: &mut ValueArgs, axis: SpecAxis) -> Value { let spacing: Option = args.require(ctx, "spacing"); - Value::template(move |ctx| { + Value::template("spacing", move |ctx| { if let Some(linear) = spacing { let amount = linear.resolve(ctx.state.font.font_size()); let spacing = NodeSpacing { amount, softness: Softness::Hard }; @@ -286,7 +286,7 @@ fn spacing(ctx: &mut EvalContext, args: &mut Args, axis: SpecAxis) -> Value { /// - Flip width and height: `flip`, of type `bool`. /// - Main layouting direction: `main-dir`, of type `direction`. /// - Cross layouting direction: `cross-dir`, of type `direction`. -pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value { +pub fn page(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { let paper = args.find::>(ctx).and_then(|name| { Paper::from_name(&name.v).or_else(|| { ctx.diag(error!(name.span, "invalid paper name")); @@ -305,7 +305,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value { let cross = args.get(ctx, "cross-dir"); let body = args.find::(ctx); - Value::template(move |ctx| { + Value::template("page", move |ctx| { let snapshot = ctx.state.clone(); if let Some(paper) = paper { @@ -370,8 +370,8 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value { } /// `pagebreak`: Start a new page. -pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> Value { - Value::template(move |ctx| { +pub fn pagebreak(_: &mut EvalContext, _: &mut ValueArgs) -> Value { + Value::template("pagebreak", move |ctx| { ctx.end_page_group(|_| true); ctx.start_page_group(Softness::Hard); }) diff --git a/src/library/style.rs b/src/library/style.rs index 23bd5298e..357275157 100644 --- a/src/library/style.rs +++ b/src/library/style.rs @@ -54,7 +54,7 @@ use crate::prelude::*; /// - `expanded` /// - `extra-expanded` /// - `ultra-expanded` -pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value { +pub fn font(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { let size = args.find::(ctx); let list: Vec<_> = args.filter::(ctx).map(|f| f.to_string()).collect(); let style = args.get(ctx, "style"); @@ -65,7 +65,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value { let monospace = args.get(ctx, "monospace"); let body = args.find::(ctx); - Value::template(move |ctx| { + Value::template("font", move |ctx| { let snapshot = ctx.state.clone(); if let Some(linear) = size { @@ -145,7 +145,7 @@ impl Display for FontFamily { } } -impl_type! { +typify! { FontFamilies: "font family or array of font families", Value::Str(string) => Self(vec![FontFamily::Named(string.to_lowercase())]), Value::Array(values) => Self(values @@ -156,16 +156,16 @@ impl_type! { #(family: FontFamily) => Self(vec![family]), } -impl_type! { +typify! { FontFamily: "font family", Value::Str(string) => Self::Named(string.to_lowercase()) } -impl_type! { +typify! { FontStyle: "font style" } -impl_type! { +typify! { FontWeight: "font weight", Value::Int(number) => { let [min, max] = [Self::THIN, Self::BLACK]; @@ -180,7 +180,7 @@ impl_type! { }, } -impl_type! { +typify! { FontStretch: "font stretch" } @@ -191,7 +191,7 @@ impl_type! { /// - Green component: of type `float`, between 0.0 and 1.0. /// - Blue component: of type `float`, between 0.0 and 1.0. /// - Alpha component: optional, of type `float`, between 0.0 and 1.0. -pub fn rgb(ctx: &mut EvalContext, args: &mut Args) -> Value { +pub fn rgb(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { let r = args.require(ctx, "red component"); let g = args.require(ctx, "green component"); let b = args.require(ctx, "blue component"); diff --git a/src/parse/collection.rs b/src/parse/collection.rs index 162a8bd5b..7ffc4539f 100644 --- a/src/parse/collection.rs +++ b/src/parse/collection.rs @@ -51,29 +51,29 @@ fn collection(p: &mut Parser, mut collection: T) -> T { } /// Parse an expression or a named pair. -fn argument(p: &mut Parser) -> Option { +fn argument(p: &mut Parser) -> Option { let first = expr(p)?; if p.eat_if(Token::Colon) { if let Expr::Ident(name) = first { - Some(Argument::Named(Named { name, expr: expr(p)? })) + Some(ExprArg::Named(Named { name, expr: expr(p)? })) } else { p.diag(error!(first.span(), "expected identifier")); expr(p); None } } else { - Some(Argument::Pos(first)) + Some(ExprArg::Pos(first)) } } /// Abstraction for comma-separated list of expression / named pairs. trait Collection { - fn push_arg(&mut self, p: &mut Parser, arg: Argument); + fn push_arg(&mut self, p: &mut Parser, arg: ExprArg); fn push_comma(&mut self) {} } -impl Collection for Vec { - fn push_arg(&mut self, _: &mut Parser, arg: Argument) { +impl Collection for Vec { + fn push_arg(&mut self, _: &mut Parser, arg: ExprArg) { self.push(arg); } } @@ -99,23 +99,23 @@ impl State { } impl Collection for State { - fn push_arg(&mut self, p: &mut Parser, arg: Argument) { + fn push_arg(&mut self, p: &mut Parser, arg: ExprArg) { match self { Self::Unknown => match arg { - Argument::Pos(expr) => *self = Self::Expr(expr), - Argument::Named(named) => *self = Self::Dict(vec![named]), + ExprArg::Pos(expr) => *self = Self::Expr(expr), + ExprArg::Named(named) => *self = Self::Dict(vec![named]), }, Self::Expr(prev) => match arg { - Argument::Pos(expr) => *self = Self::Array(vec![take(prev), expr]), - Argument::Named(_) => diag(p, arg), + ExprArg::Pos(expr) => *self = Self::Array(vec![take(prev), expr]), + ExprArg::Named(_) => diag(p, arg), }, Self::Array(array) => match arg { - Argument::Pos(expr) => array.push(expr), - Argument::Named(_) => diag(p, arg), + ExprArg::Pos(expr) => array.push(expr), + ExprArg::Named(_) => diag(p, arg), }, Self::Dict(dict) => match arg { - Argument::Pos(_) => diag(p, arg), - Argument::Named(named) => dict.push(named), + ExprArg::Pos(_) => diag(p, arg), + ExprArg::Named(named) => dict.push(named), }, } } @@ -135,9 +135,9 @@ fn take(expr: &mut Expr) -> Expr { ) } -fn diag(p: &mut Parser, arg: Argument) { +fn diag(p: &mut Parser, arg: ExprArg) { p.diag(error!(arg.span(), "{}", match arg { - Argument::Pos(_) => "expected named pair, found expression", - Argument::Named(_) => "expected expression, found named pair", + ExprArg::Pos(_) => "expected named pair, found expression", + ExprArg::Named(_) => "expected expression, found named pair", })); } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 2c34d7b82..e7e25a1d8 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -198,11 +198,11 @@ fn bracket_call(p: &mut Parser) -> Option { let mut inner = inner?; if let Some(body) = body { inner.span.expand(body.span()); - inner.args.items.push(Argument::Pos(body)); + inner.args.items.push(ExprArg::Pos(body)); } while let Some(mut top) = outer.pop() { - top.args.items.push(Argument::Pos(Expr::Call(inner))); + top.args.items.push(ExprArg::Pos(Expr::Call(inner))); inner = top; } diff --git a/src/prelude.rs b/src/prelude.rs index 6bab80e67..3f5bcab74 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,7 +3,7 @@ pub use crate::diag::{Feedback, Pass}; #[doc(no_inline)] pub use crate::eval::{ - Args, CastResult, Eval, EvalContext, TemplateAny, TemplateNode, Value, ValueAny, + CastResult, Eval, EvalContext, TemplateAny, TemplateNode, Value, ValueAny, ValueArgs, ValueArray, ValueDict, ValueTemplate, }; #[doc(no_inline)] @@ -13,4 +13,4 @@ pub use crate::geom::*; pub use crate::layout::Node; #[doc(no_inline)] pub use crate::syntax::{Span, Spanned, WithSpan}; -pub use crate::{error, impl_type, warning}; +pub use crate::{error, typify, warning}; diff --git a/src/pretty.rs b/src/pretty.rs index 0123e9a75..e01955cd8 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -105,7 +105,7 @@ impl Pretty for str { } } -macro_rules! impl_pretty_display { +macro_rules! pretty_display { ($($type:ty),* $(,)?) => { $(impl Pretty for $type { fn pretty(&self, p: &mut Printer) { @@ -115,7 +115,7 @@ macro_rules! impl_pretty_display { }; } -impl_pretty_display! { +pretty_display! { bool, Length, Angle, diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index f431ba8dc..8a11ebc41 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -531,7 +531,7 @@ impl ExprCall { // Function name. self.callee.pretty(p); - let mut write_args = |items: &[Argument]| { + let mut write_args = |items: &[ExprArg]| { if !items.is_empty() { p.push(' '); p.join(items, ", ", |item, p| item.pretty(p)); @@ -542,7 +542,7 @@ impl ExprCall { // This can written as a chain. // // Example: Transforms "#[v][[f]]" => "#[v | f]". - [head @ .., Argument::Pos(Expr::Call(call))] => { + [head @ .., ExprArg::Pos(Expr::Call(call))] => { write_args(head); call.pretty_bracketed(p, true); } @@ -550,7 +550,7 @@ impl ExprCall { // This can be written with a body. // // Example: Transforms "#[v [Hi]]" => "#[v][Hi]". - [head @ .., Argument::Pos(Expr::Template(template))] => { + [head @ .., ExprArg::Pos(Expr::Template(template))] => { write_args(head); p.push(']'); template.pretty(p); @@ -573,7 +573,7 @@ pub struct ExprArgs { /// The source code location. pub span: Span, /// The positional and named arguments. - pub items: Vec, + pub items: Vec, } impl Pretty for ExprArgs { @@ -584,14 +584,14 @@ impl Pretty for ExprArgs { /// An argument to a function call: `12` or `draw: false`. #[derive(Debug, Clone, PartialEq)] -pub enum Argument { - /// A positional arguments. +pub enum ExprArg { + /// A positional argument. Pos(Expr), /// A named argument. Named(Named), } -impl Argument { +impl ExprArg { /// The source code location. pub fn span(&self) -> Span { match self { @@ -601,7 +601,7 @@ impl Argument { } } -impl Pretty for Argument { +impl Pretty for ExprArg { fn pretty(&self, p: &mut Printer) { match self { Self::Pos(expr) => expr.pretty(p), diff --git a/src/syntax/node.rs b/src/syntax/node.rs index fe9767a18..246790f6a 100644 --- a/src/syntax/node.rs +++ b/src/syntax/node.rs @@ -31,6 +31,7 @@ impl Pretty for Node { Self::Space => p.push(' '), Self::Linebreak => p.push_str(r"\"), Self::Parbreak => p.push_str("\n\n"), + // TODO: Handle escaping. Self::Text(text) => p.push_str(&text), Self::Heading(heading) => heading.pretty(p), Self::Raw(raw) => raw.pretty(p), diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index 70159d2d8..2d3c683f8 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -111,10 +111,10 @@ visit! { } } - fn visit_arg(v, node: &Argument) { + fn visit_arg(v, node: &ExprArg) { match node { - Argument::Pos(expr) => v.visit_expr(&expr), - Argument::Named(named) => v.visit_expr(&named.expr), + ExprArg::Pos(expr) => v.visit_expr(&expr), + ExprArg::Named(named) => v.visit_expr(&named.expr), } } diff --git a/tests/lang/ref/call-args.png b/tests/lang/ref/call-args.png index 0ce9000a6..99acd84b3 100644 Binary files a/tests/lang/ref/call-args.png and b/tests/lang/ref/call-args.png differ diff --git a/tests/lang/ref/call-bracket.png b/tests/lang/ref/call-bracket.png index 2f020629d..a1214b3d9 100644 Binary files a/tests/lang/ref/call-bracket.png and b/tests/lang/ref/call-bracket.png differ diff --git a/tests/lang/ref/call-chain.png b/tests/lang/ref/call-chain.png index 5c90dbd80..de5520b84 100644 Binary files a/tests/lang/ref/call-chain.png and b/tests/lang/ref/call-chain.png differ diff --git a/tests/lang/ref/call-invalid.png b/tests/lang/ref/call-invalid.png index e03d84c11..a60e037f9 100644 Binary files a/tests/lang/ref/call-invalid.png and b/tests/lang/ref/call-invalid.png differ diff --git a/tests/lang/ref/comment.png b/tests/lang/ref/comment.png index 79f438b2f..0ab9a988c 100644 Binary files a/tests/lang/ref/comment.png and b/tests/lang/ref/comment.png differ diff --git a/tests/lang/ref/if-branch.png b/tests/lang/ref/if-branch.png index 8bb852968..4e7f471a4 100644 Binary files a/tests/lang/ref/if-branch.png and b/tests/lang/ref/if-branch.png differ diff --git a/tests/lang/ref/repr.png b/tests/lang/ref/repr.png index f3bf781eb..110e26c08 100644 Binary files a/tests/lang/ref/repr.png and b/tests/lang/ref/repr.png differ diff --git a/tests/typeset.rs b/tests/typeset.rs index c894b2d48..d64294d1c 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -16,7 +16,7 @@ use walkdir::WalkDir; use typst::diag::{Diag, Feedback, Level, Pass}; use typst::env::{Env, ImageResource, ResourceLoader}; -use typst::eval::{Args, EvalContext, Scope, Value, ValueFunc}; +use typst::eval::{EvalContext, Scope, Value, ValueArgs, ValueFunc}; use typst::exec::State; use typst::export::pdf; use typst::font::FsIndexExt; @@ -24,7 +24,6 @@ use typst::geom::{Length, Point, Sides, Size, Spec}; use typst::layout::{Element, Expansion, Fill, Frame, Geometry, Image, Shape}; use typst::library; use typst::parse::{LineMap, Scanner}; -use typst::pretty::{Pretty, Printer}; use typst::shaping::Shaped; use typst::syntax::{Location, Pos, SpanVec, Spanned, WithSpan}; use typst::typeset; @@ -320,28 +319,13 @@ struct Panic { } fn register_helpers(scope: &mut Scope, panics: Rc>>) { - pub fn f(_: &mut EvalContext, args: &mut Args) -> Value { - let (array, dict) = args.drain(); - let iter = array - .into_iter() - .map(|v| (None, v)) - .chain(dict.into_iter().map(|(k, v)| (Some(k), v))); - - let mut p = Printer::new(); - p.push_str("f("); - p.join(iter, ", ", |(key, value), p| { - if let Some(key) = key { - p.push_str(&key); - p.push_str(": "); - } - value.pretty(p); - }); - p.push(')'); - - Value::Str(p.finish()) + pub fn f(_: &mut EvalContext, args: &mut ValueArgs) -> Value { + let value = args.clone().into(); + args.items.clear(); + value } - let test = move |ctx: &mut EvalContext, args: &mut Args| -> Value { + let test = move |ctx: &mut EvalContext, args: &mut ValueArgs| -> Value { let lhs = args.require::(ctx, "left-hand side"); let rhs = args.require::(ctx, "right-hand side"); if lhs != rhs {