diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 774770a3b..7478c8cb8 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -8,15 +8,16 @@ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{Error, Ident, Result}; -/// Turn a node into a class. #[proc_macro_attribute] -pub fn class(_: TokenStream, item: TokenStream) -> TokenStream { +pub fn node(stream: TokenStream, item: TokenStream) -> TokenStream { let impl_block = syn::parse_macro_input!(item as syn::ItemImpl); - expand(impl_block).unwrap_or_else(|err| err.to_compile_error()).into() + expand(TokenStream2::from(stream), impl_block) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } /// Expand an impl block for a node. -fn expand(mut impl_block: syn::ItemImpl) -> Result { +fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result { // Split the node type into name and generic type arguments. let params = &impl_block.generics.params; let self_ty = &*impl_block.self_ty; @@ -77,13 +78,20 @@ fn expand(mut impl_block: syn::ItemImpl) -> Result { }); parse_quote! { - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { + fn set(args: &mut Args) -> TypResult { + let mut styles = StyleMap::new(); #(#sets)* - Ok(()) + Ok(styles) } } }); + let showable = match stream.to_string().as_str() { + "" => false, + "showable" => true, + _ => return Err(Error::new(stream.span(), "unrecognized argument")), + }; + // Put everything into a module with a hopefully unique type to isolate // it from the outside. Ok(quote! { @@ -92,16 +100,14 @@ fn expand(mut impl_block: syn::ItemImpl) -> Result { use std::any::TypeId; use std::marker::PhantomData; use once_cell::sync::Lazy; - use crate::eval::{Construct, Nonfolding, Property, Set}; + use crate::eval; use super::*; #impl_block - impl<#params> Construct for #self_ty { + impl<#params> eval::Node for #self_ty { + const SHOWABLE: bool = #showable; #construct - } - - impl<#params> Set for #self_ty { #set } @@ -206,8 +212,8 @@ fn process_const( )); } - let nonfolding = fold.is_none().then(|| { - quote! { impl<#params> Nonfolding for #key {} } + let referencable = fold.is_none().then(|| { + quote! { impl<#params> eval::Referencable for #key {} } }); // Generate the module code. @@ -226,7 +232,7 @@ fn process_const( } } - impl<#params> Property for #key { + impl<#params> eval::Key for #key { type Value = #value_ty; const NAME: &'static str = #name; @@ -247,7 +253,7 @@ fn process_const( #fold } - #nonfolding + #referencable } }; diff --git a/src/eval/args.rs b/src/eval/args.rs new file mode 100644 index 000000000..67da9865f --- /dev/null +++ b/src/eval/args.rs @@ -0,0 +1,202 @@ +use std::fmt::{self, Debug, Formatter, Write}; + +use super::{Cast, Value}; +use crate::diag::{At, TypResult}; +use crate::syntax::{Span, Spanned}; +use crate::util::EcoString; + +/// Evaluated arguments to a function. +#[derive(Clone, PartialEq, Hash)] +pub struct Args { + /// The span of the whole argument list. + pub span: Span, + /// The positional and named arguments. + pub items: Vec, +} + +/// An argument to a function call: `12` or `draw: false`. +#[derive(Clone, PartialEq, Hash)] +pub struct Arg { + /// The span of the whole argument. + pub span: Span, + /// The name of the argument (`None` for positional arguments). + pub name: Option, + /// The value of the argument. + pub value: Spanned, +} + +impl Args { + /// Create positional arguments from a span and values. + pub fn from_values(span: Span, values: impl IntoIterator) -> Self { + Self { + span, + items: values + .into_iter() + .map(|value| Arg { + span, + name: None, + value: Spanned::new(value, span), + }) + .collect(), + } + } + + /// Consume and cast the first positional argument. + /// + /// Returns a `missing argument: {what}` error if no positional argument is + /// left. + pub fn expect(&mut self, what: &str) -> TypResult + where + T: Cast>, + { + match self.eat()? { + Some(v) => Ok(v), + None => bail!(self.span, "missing argument: {}", what), + } + } + + /// Consume and cast the first positional argument if there is one. + pub fn eat(&mut self) -> TypResult> + where + T: Cast>, + { + for (i, slot) in self.items.iter().enumerate() { + if slot.name.is_none() { + let value = self.items.remove(i).value; + let span = value.span; + return T::cast(value).at(span).map(Some); + } + } + Ok(None) + } + + /// Find and consume the first castable positional argument. + pub fn find(&mut self) -> TypResult> + where + T: Cast>, + { + for (i, slot) in self.items.iter().enumerate() { + if slot.name.is_none() && T::is(&slot.value) { + let value = self.items.remove(i).value; + let span = value.span; + return T::cast(value).at(span).map(Some); + } + } + Ok(None) + } + + /// Find and consume all castable positional arguments. + pub fn all(&mut self) -> TypResult> + where + T: Cast>, + { + let mut list = vec![]; + while let Some(value) = self.find()? { + list.push(value); + } + Ok(list) + } + + /// Cast and remove the value for the given named argument, returning an + /// error if the conversion fails. + pub fn named(&mut self, name: &str) -> TypResult> + where + T: Cast>, + { + // We don't quit once we have a match because when multiple matches + // exist, we want to remove all of them and use the last one. + let mut i = 0; + let mut found = None; + while i < self.items.len() { + if self.items[i].name.as_deref() == Some(name) { + let value = self.items.remove(i).value; + let span = value.span; + found = Some(T::cast(value).at(span)?); + } else { + i += 1; + } + } + Ok(found) + } + + /// Same as named, but with fallback to find. + pub fn named_or_find(&mut self, name: &str) -> TypResult> + where + T: Cast>, + { + match self.named(name)? { + Some(value) => Ok(Some(value)), + None => self.find(), + } + } + + /// Take out all arguments into a new instance. + pub fn take(&mut self) -> Self { + Self { + span: self.span, + items: std::mem::take(&mut self.items), + } + } + + /// Return an "unexpected argument" error if there is any remaining + /// argument. + pub fn finish(self) -> TypResult<()> { + if let Some(arg) = self.items.first() { + bail!(arg.span, "unexpected argument"); + } + Ok(()) + } + + /// Reinterpret these arguments as actually being an array index. + pub fn into_index(self) -> TypResult { + self.into_castable("index") + } + + /// Reinterpret these arguments as actually being a dictionary key. + pub fn into_key(self) -> TypResult { + self.into_castable("key") + } + + /// Reinterpret these arguments as actually being a single castable thing. + fn into_castable(self, what: &str) -> TypResult { + let mut iter = self.items.into_iter(); + let value = match iter.next() { + Some(Arg { name: None, value, .. }) => value.v.cast().at(value.span)?, + None => { + bail!(self.span, "missing {}", what); + } + Some(Arg { name: Some(_), span, .. }) => { + bail!(span, "named pair is not allowed here"); + } + }; + + if let Some(arg) = iter.next() { + bail!(arg.span, "only one {} is allowed", what); + } + + Ok(value) + } +} + +impl Debug for Args { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_char('(')?; + for (i, arg) in self.items.iter().enumerate() { + arg.fmt(f)?; + if i + 1 < self.items.len() { + f.write_str(", ")?; + } + } + f.write_char(')') + } +} + +impl Debug for Arg { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if let Some(name) = &self.name { + f.write_str(name)?; + f.write_str(": ")?; + } + Debug::fmt(&self.value.v, f) + } +} diff --git a/src/eval/class.rs b/src/eval/class.rs deleted file mode 100644 index 051916673..000000000 --- a/src/eval/class.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::any::TypeId; -use std::fmt::{self, Debug, Formatter, Write}; -use std::hash::{Hash, Hasher}; - -use super::{Args, Content, Func, StyleMap, Value}; -use crate::diag::TypResult; -use crate::Context; - -/// A class of nodes. -/// -/// You can [construct] an instance of a class in Typst code by invoking the -/// class as a callable. This always produces a content value, but not -/// necessarily a simple inline or block node. For example, the `text` -/// constructor does not actually create a [`TextNode`]. Instead it applies -/// styling to whatever content you pass in and returns it structurally -/// unchanged. -/// -/// The arguments you can pass to a class constructor fall into two categories: -/// Data that is inherent to the instance (e.g. the text/content of a heading) -/// and style properties (e.g. the fill color of a heading). As the latter are -/// often shared by many instances throughout a document, they can also be -/// conveniently configured through class's [`set`] rule. Then, they apply to -/// all nodes that are instantiated into the content block where the `set` was -/// executed. -/// -/// ```typst -/// This is normal. -/// [ -/// #set text(weight: "bold") -/// #set heading(fill: blue) -/// = A blue & bold heading -/// ] -/// Normal again. -/// ``` -/// -/// [construct]: Self::construct -/// [`TextNode`]: crate::library::text::TextNode -/// [`set`]: Self::set -#[derive(Clone)] -pub struct Class { - name: &'static str, - id: TypeId, - construct: fn(&mut Context, &mut Args) -> TypResult, - set: fn(&mut Args, &mut StyleMap) -> TypResult<()>, -} - -impl Class { - /// Create a new class. - pub fn new(name: &'static str) -> Self - where - T: Construct + Set + 'static, - { - Self { - name, - id: TypeId::of::(), - construct: |ctx, args| { - let mut styles = StyleMap::new(); - T::set(args, &mut styles)?; - let content = T::construct(ctx, args)?; - Ok(Value::Content(content.styled_with_map(styles.scoped()))) - }, - set: T::set, - } - } - - /// The name of the class. - pub fn name(&self) -> &'static str { - self.name - } - - /// The type id of the class. - pub fn id(&self) -> TypeId { - self.id - } - - /// Return the class constructor as a function. - pub fn constructor(&self) -> Func { - Func::native(self.name, self.construct) - } - - /// Construct an instance of the class. - /// - /// This parses both property and data arguments (in this order), styles the - /// content constructed from the data with the style properties and wraps it - /// in a value. - pub fn construct(&self, ctx: &mut Context, mut args: Args) -> TypResult { - let value = (self.construct)(ctx, &mut args)?; - args.finish()?; - Ok(value) - } - - /// Execute the class's set rule. - /// - /// This parses property arguments and return the resulting styles. - pub fn set(&self, mut args: Args) -> TypResult { - let mut styles = StyleMap::new(); - (self.set)(&mut args, &mut styles)?; - args.finish()?; - Ok(styles) - } -} - -impl Debug for Class { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("') - } -} - -impl PartialEq for Class { - fn eq(&self, other: &Self) -> bool { - self.name == other.name - } -} - -impl Hash for Class { - fn hash(&self, state: &mut H) { - (self.construct as usize).hash(state); - (self.set as usize).hash(state); - } -} - -/// Construct an instance of a class. -pub trait Construct { - /// Construct an instance of this class from the arguments. - /// - /// This is passed only the arguments that remain after execution of the - /// class's set rule. - fn construct(ctx: &mut Context, args: &mut Args) -> TypResult; -} - -/// Set style properties of a class. -pub trait Set { - /// Parse the arguments and insert style properties of this class into the - /// given style map. - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()>; -} diff --git a/src/eval/content.rs b/src/eval/content.rs index b6d60957f..5f8c5861c 100644 --- a/src/eval/content.rs +++ b/src/eval/content.rs @@ -6,11 +6,11 @@ use std::ops::{Add, AddAssign}; use typed_arena::Arena; use super::{ - CollapsingBuilder, Interruption, Layout, LayoutNode, Property, Show, ShowNode, - StyleMap, StyleVecBuilder, + CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Show, ShowNode, StyleMap, + StyleVecBuilder, }; use crate::diag::StrResult; -use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, SpacingKind}; +use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing}; use crate::library::prelude::*; use crate::library::structure::{ListItem, ListKind, ListNode, ORDERED, UNORDERED}; use crate::library::text::{DecoNode, ParChild, ParNode, TextNode, UNDERLINE}; @@ -42,7 +42,7 @@ pub enum Content { /// A line break. Linebreak, /// Horizontal spacing. - Horizontal(SpacingKind), + Horizontal(Spacing), /// Plain text. Text(EcoString), /// An inline-level node. @@ -52,7 +52,7 @@ pub enum Content { /// A column break. Colbreak, /// Vertical spacing. - Vertical(SpacingKind), + Vertical(Spacing), /// A block-level node. Block(LayoutNode), /// An item in an unordered list. @@ -102,7 +102,7 @@ impl Content { } /// Style this content with a single style property. - pub fn styled(mut self, key: P, value: P::Value) -> Self { + pub fn styled(mut self, key: P, value: P::Value) -> Self { if let Self::Styled(styled) = &mut self { if let Some((_, map)) = Arc::get_mut(styled) { if !map.has_scoped() { @@ -465,7 +465,7 @@ impl<'a> Builder<'a> { }) .unwrap_or_default() { - par.push_front(ParChild::Spacing(SpacingKind::Linear(indent))) + par.push_front(ParChild::Spacing(Spacing::Linear(indent))) } let node = ParNode(par).pack(); diff --git a/src/eval/func.rs b/src/eval/func.rs index 451dcbbb7..3eae453e0 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -1,11 +1,12 @@ +use std::any::TypeId; use std::fmt::{self, Debug, Formatter, Write}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{Cast, Control, Eval, Scope, Scopes, Value}; -use crate::diag::{At, TypResult}; +use super::{Args, Content, Control, Eval, Scope, Scopes, StyleMap, Value}; +use crate::diag::{StrResult, TypResult}; use crate::syntax::ast::Expr; -use crate::syntax::{Span, Spanned}; +use crate::syntax::Span; use crate::util::EcoString; use crate::Context; @@ -26,18 +27,50 @@ enum Repr { impl Func { /// Create a new function from a native rust function. - pub fn native( + pub fn from_fn( name: &'static str, func: fn(&mut Context, &mut Args) -> TypResult, ) -> Self { - Self(Arc::new(Repr::Native(Native { name, func }))) + Self(Arc::new(Repr::Native(Native { + name, + func, + set: None, + show: None, + }))) + } + + /// Create a new function from a native rust node. + pub fn from_node(name: &'static str) -> Self { + Self(Arc::new(Repr::Native(Native { + name, + func: |ctx, args| { + let styles = T::set(args)?; + let content = T::construct(ctx, args)?; + Ok(Value::Content(content.styled_with_map(styles.scoped()))) + }, + set: Some(T::set), + show: if T::SHOWABLE { + Some(|recipe, span| { + let mut styles = StyleMap::new(); + styles.set_recipe(TypeId::of::(), recipe, span); + styles + }) + } else { + None + }, + }))) } /// Create a new function from a closure. - pub fn closure(closure: Closure) -> Self { + pub fn from_closure(closure: Closure) -> Self { Self(Arc::new(Repr::Closure(closure))) } + /// Apply the given arguments to the function. + pub fn with(self, args: Args) -> Self { + Self(Arc::new(Repr::With(self, args))) + } + /// The name of the function. pub fn name(&self) -> Option<&str> { match self.0.as_ref() { @@ -61,9 +94,22 @@ impl Func { Ok(value) } - /// Apply the given arguments to the function. - pub fn with(self, args: Args) -> Self { - Self(Arc::new(Repr::With(self, args))) + /// Execute the function's set rule. + pub fn set(&self, mut args: Args) -> TypResult { + let styles = match self.0.as_ref() { + Repr::Native(Native { set: Some(set), .. }) => set(&mut args)?, + _ => StyleMap::new(), + }; + args.finish()?; + Ok(styles) + } + + /// Execute the function's show rule. + pub fn show(&self, recipe: Func, span: Span) -> StrResult { + match self.0.as_ref() { + Repr::Native(Native { show: Some(show), .. }) => Ok(show(recipe, span)), + _ => Err("this function cannot be customized with show")?, + } } } @@ -90,14 +136,36 @@ struct Native { pub name: &'static str, /// The function pointer. pub func: fn(&mut Context, &mut Args) -> TypResult, + /// The set rule. + pub set: Option TypResult>, + /// The show rule. + pub show: Option StyleMap>, } impl Hash for Native { fn hash(&self, state: &mut H) { + self.name.hash(state); (self.func as usize).hash(state); + self.set.map(|set| set as usize).hash(state); + self.show.map(|show| show as usize).hash(state); } } +/// A constructable, stylable content node. +pub trait Node: 'static { + /// Whether this node can be customized through a show rule. + const SHOWABLE: bool; + + /// Construct a node from the arguments. + /// + /// This is passed only the arguments that remain after execution of the + /// node's set rule. + fn construct(ctx: &mut Context, args: &mut Args) -> TypResult; + + /// Parse the arguments into style properties for this node. + fn set(args: &mut Args) -> TypResult; +} + /// A user-defined closure. #[derive(Hash)] pub struct Closure { @@ -146,199 +214,3 @@ impl Closure { Ok(value) } } - -/// Evaluated arguments to a function. -#[derive(Clone, PartialEq, Hash)] -pub struct Args { - /// The span of the whole argument list. - pub span: Span, - /// The positional and named arguments. - pub items: Vec, -} - -/// An argument to a function call: `12` or `draw: false`. -#[derive(Clone, PartialEq, Hash)] -pub struct Arg { - /// The span of the whole argument. - pub span: Span, - /// The name of the argument (`None` for positional arguments). - pub name: Option, - /// The value of the argument. - pub value: Spanned, -} - -impl Args { - /// Create positional arguments from a span and values. - pub fn from_values(span: Span, values: impl IntoIterator) -> Self { - Self { - span, - items: values - .into_iter() - .map(|value| Arg { - span, - name: None, - value: Spanned::new(value, span), - }) - .collect(), - } - } - - /// Consume and cast the first positional argument. - /// - /// Returns a `missing argument: {what}` error if no positional argument is - /// left. - pub fn expect(&mut self, what: &str) -> TypResult - where - T: Cast>, - { - match self.eat()? { - Some(v) => Ok(v), - None => bail!(self.span, "missing argument: {}", what), - } - } - - /// Consume and cast the first positional argument if there is one. - pub fn eat(&mut self) -> TypResult> - where - T: Cast>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() { - let value = self.items.remove(i).value; - let span = value.span; - return T::cast(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Find and consume the first castable positional argument. - pub fn find(&mut self) -> TypResult> - where - T: Cast>, - { - for (i, slot) in self.items.iter().enumerate() { - if slot.name.is_none() && T::is(&slot.value) { - let value = self.items.remove(i).value; - let span = value.span; - return T::cast(value).at(span).map(Some); - } - } - Ok(None) - } - - /// Find and consume all castable positional arguments. - pub fn all(&mut self) -> TypResult> - where - T: Cast>, - { - let mut list = vec![]; - while let Some(value) = self.find()? { - list.push(value); - } - Ok(list) - } - - /// Cast and remove the value for the given named argument, returning an - /// error if the conversion fails. - pub fn named(&mut self, name: &str) -> TypResult> - where - T: Cast>, - { - // We don't quit once we have a match because when multiple matches - // exist, we want to remove all of them and use the last one. - let mut i = 0; - let mut found = None; - while i < self.items.len() { - if self.items[i].name.as_deref() == Some(name) { - let value = self.items.remove(i).value; - let span = value.span; - found = Some(T::cast(value).at(span)?); - } else { - i += 1; - } - } - Ok(found) - } - - /// Same as named, but with fallback to find. - pub fn named_or_find(&mut self, name: &str) -> TypResult> - where - T: Cast>, - { - match self.named(name)? { - Some(value) => Ok(Some(value)), - None => self.find(), - } - } - - /// Take out all arguments into a new instance. - pub fn take(&mut self) -> Self { - Self { - span: self.span, - items: std::mem::take(&mut self.items), - } - } - - /// Return an "unexpected argument" error if there is any remaining - /// argument. - pub fn finish(self) -> TypResult<()> { - if let Some(arg) = self.items.first() { - bail!(arg.span, "unexpected argument"); - } - Ok(()) - } - - /// Reinterpret these arguments as actually being an array index. - pub fn into_index(self) -> TypResult { - self.into_castable("index") - } - - /// Reinterpret these arguments as actually being a dictionary key. - pub fn into_key(self) -> TypResult { - self.into_castable("key") - } - - /// Reinterpret these arguments as actually being a single castable thing. - fn into_castable(self, what: &str) -> TypResult { - let mut iter = self.items.into_iter(); - let value = match iter.next() { - Some(Arg { name: None, value, .. }) => value.v.cast().at(value.span)?, - None => { - bail!(self.span, "missing {}", what); - } - Some(Arg { name: Some(_), span, .. }) => { - bail!(span, "named pair is not allowed here"); - } - }; - - if let Some(arg) = iter.next() { - bail!(arg.span, "only one {} is allowed", what); - } - - Ok(value) - } -} - -impl Debug for Args { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('(')?; - for (i, arg) in self.items.iter().enumerate() { - arg.fmt(f)?; - if i + 1 < self.items.len() { - f.write_str(", ")?; - } - } - f.write_char(')') - } -} - -impl Debug for Arg { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Some(name) = &self.name { - f.write_str(name)?; - f.write_str(": ")?; - } - Debug::fmt(&self.value.v, f) - } -} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 79c0ad83f..e00a40f28 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -8,8 +8,8 @@ mod dict; mod value; #[macro_use] mod styles; +mod args; mod capture; -mod class; mod collapse; mod content; mod control; @@ -20,9 +20,9 @@ mod ops; mod scope; mod show; +pub use args::*; pub use array::*; pub use capture::*; -pub use class::*; pub use collapse::*; pub use content::*; pub use control::*; @@ -417,11 +417,6 @@ impl Eval for CallExpr { func.call(ctx, args).trace(point, self.span()) } - Value::Class(class) => { - let point = || Tracepoint::Call(Some(class.name().to_string())); - class.construct(ctx, args).trace(point, self.span()) - } - v => bail!( span, "expected callable or collection, found {}", @@ -520,7 +515,7 @@ impl Eval for ClosureExpr { } // Define the actual function. - Ok(Value::Func(Func::closure(Closure { + Ok(Value::Func(Func::from_closure(Closure { name, captured, params, @@ -558,10 +553,10 @@ impl Eval for SetExpr { type Output = StyleMap; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { - let class = self.class(); - let class = class.eval(ctx, scp)?.cast::().at(class.span())?; + let target = self.target(); + let target = target.eval(ctx, scp)?.cast::().at(target.span())?; let args = self.args().eval(ctx, scp)?; - Ok(class.set(args)?) + Ok(target.set(args)?) } } @@ -569,13 +564,13 @@ impl Eval for ShowExpr { type Output = StyleMap; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { - let class = self.class(); - let class = class.eval(ctx, scp)?.cast::().at(class.span())?; - let closure = self.closure(); - let func = closure.eval(ctx, scp)?.cast::().at(closure.span())?; - let mut styles = StyleMap::new(); - styles.set_recipe(class.id(), func, self.span()); - Ok(styles) + let target = self.target(); + let target_span = target.span(); + let target = target.eval(ctx, scp)?.cast::().at(target_span)?; + let recipe = self.recipe(); + let recipe_span = recipe.span(); + let recipe = recipe.eval(ctx, scp)?.cast::().at(recipe_span)?; + Ok(target.show(recipe, recipe_span).at(target_span)?) } } diff --git a/src/eval/scope.rs b/src/eval/scope.rs index e09d05c82..19899cae9 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher}; use std::iter; use std::sync::{Arc, RwLock}; -use super::{Args, Class, Construct, Func, Set, Value}; +use super::{Args, Func, Node, Value}; use crate::diag::TypResult; use crate::util::EcoString; use crate::Context; @@ -83,21 +83,18 @@ impl Scope { self.values.insert(var.into(), slot); } - /// Define a constant native function. - pub fn def_func( + /// Define a function through a native rust function. + pub fn def_fn( &mut self, name: &'static str, func: fn(&mut Context, &mut Args) -> TypResult, ) { - self.def_const(name, Func::native(name, func)); + self.def_const(name, Func::from_fn(name, func)); } - /// Define a constant class. - pub fn def_class(&mut self, name: &'static str) - where - T: Construct + Set + 'static, - { - self.def_const(name, Class::new::(name)); + /// Define a function through a native rust node. + pub fn def_node(&mut self, name: &'static str) { + self.def_const(name, Func::from_node::(name)); } /// Look up the value of a variable. diff --git a/src/eval/styles.rs b/src/eval/styles.rs index 20c36ae20..e52aa9f3d 100644 --- a/src/eval/styles.rs +++ b/src/eval/styles.rs @@ -30,19 +30,19 @@ impl StyleMap { } /// Create a style map from a single property-value pair. - pub fn with(key: P, value: P::Value) -> Self { + pub fn with(key: K, value: K::Value) -> Self { let mut styles = Self::new(); styles.set(key, value); styles } /// Set the value for a style property. - pub fn set(&mut self, key: P, value: P::Value) { + pub fn set(&mut self, key: K, value: K::Value) { self.props.push(Entry::new(key, value)); } /// Set a value for a style property if it is `Some(_)`. - pub fn set_opt(&mut self, key: P, value: Option) { + pub fn set_opt(&mut self, key: K, value: Option) { if let Some(value) = value { self.set(key, value); } @@ -126,8 +126,8 @@ pub enum Interruption { /// Style property keys. /// /// This trait is not intended to be implemented manually, but rather through -/// the `#[class]` proc-macro. -pub trait Property: Sync + Send + 'static { +/// the `#[node]` proc-macro. +pub trait Key: Sync + Send + 'static { /// The type of value that is returned when getting this property from a /// style map. For example, this could be [`Length`](crate::geom::Length) /// for a `WIDTH` property. @@ -148,7 +148,7 @@ pub trait Property: Sync + Send + 'static { /// A static reference to the default value of the property. /// /// This is automatically implemented through lazy-initialization in the - /// `#[class]` macro. This way, expensive defaults don't need to be + /// `#[node]` macro. This way, expensive defaults don't need to be /// recreated all the time. fn default_ref() -> &'static Self::Value; @@ -162,8 +162,12 @@ pub trait Property: Sync + Send + 'static { } } -/// Marker trait that indicates that a property doesn't need folding. -pub trait Nonfolding {} +/// Marker trait indicating that a property can be accessed by reference. +/// +/// This is implemented by a key if and only if `K::FOLDING` if false. +/// Unfortunately, Rust's type system doesn't allow use to use an associated +/// constant to bound a function, so we need this trait. +pub trait Referencable {} /// An entry for a single style property. #[derive(Clone)] @@ -173,14 +177,14 @@ struct Entry { } impl Entry { - fn new(key: P, value: P::Value) -> Self { + fn new(key: K, value: K::Value) -> Self { Self { pair: Arc::new((key, value)), scoped: false, } } - fn is(&self) -> bool { + fn is(&self) -> bool { self.pair.style_id() == TypeId::of::

() } @@ -192,7 +196,7 @@ impl Entry { self.pair.node_id() == node } - fn downcast(&self) -> Option<&P::Value> { + fn downcast(&self) -> Option<&K::Value> { self.pair.as_any().downcast_ref() } @@ -244,18 +248,18 @@ trait Bounds: Sync + Send + 'static { fn style_id(&self) -> TypeId; } -impl Bounds for (P, P::Value) { +impl Bounds for (K, K::Value) { fn as_any(&self) -> &dyn Any { &self.1 } fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{} = {:?}", P::NAME, self.1) + write!(f, "{} = {:?}", K::NAME, self.1) } fn dyn_eq(&self, other: &Entry) -> bool { self.style_id() == other.pair.style_id() - && if let Some(other) = other.downcast::

() { + && if let Some(other) = other.downcast::() { &self.1 == other } else { false @@ -270,11 +274,11 @@ impl Bounds for (P, P::Value) { } fn node_id(&self) -> TypeId { - P::node_id() + K::node_id() } fn style_id(&self) -> TypeId { - TypeId::of::

() + TypeId::of::() } } @@ -366,9 +370,9 @@ impl<'a> StyleChain<'a> { /// /// Returns the property's default value if no map in the chain contains an /// entry for it. - pub fn get(self, key: P) -> P::Value + pub fn get(self, key: K) -> K::Value where - P::Value: Copy, + K::Value: Copy, { self.get_cloned(key) } @@ -381,11 +385,11 @@ impl<'a> StyleChain<'a> { /// /// Returns a lazily-initialized reference to the property's default value /// if no map in the chain contains an entry for it. - pub fn get_ref(self, key: P) -> &'a P::Value + pub fn get_ref(self, key: K) -> &'a K::Value where - P: Nonfolding, + K: Referencable, { - self.values(key).next().unwrap_or_else(|| P::default_ref()) + self.values(key).next().unwrap_or_else(|| K::default_ref()) } /// Get the (folded) value of any style property. @@ -396,15 +400,15 @@ impl<'a> StyleChain<'a> { /// /// Returns the property's default value if no map in the chain contains an /// entry for it. - pub fn get_cloned(self, key: P) -> P::Value { - if P::FOLDING { + pub fn get_cloned(self, key: K) -> K::Value { + if K::FOLDING { self.values(key) .cloned() - .chain(std::iter::once(P::default())) - .reduce(P::fold) + .chain(std::iter::once(K::default())) + .reduce(K::fold) .unwrap() } else { - self.values(key).next().cloned().unwrap_or_else(P::default) + self.values(key).next().cloned().unwrap_or_else(K::default) } } @@ -445,19 +449,19 @@ impl<'a> StyleChain<'a> { impl<'a> StyleChain<'a> { /// Iterate over all values for the given property in the chain. - fn values(self, _: P) -> impl Iterator { + fn values(self, _: K) -> impl Iterator { let mut depth = 0; self.links().flat_map(move |link| { let mut entries: &[Entry] = &[]; match link { Link::Map(map) => entries = &map.props, - Link::Barrier(id) => depth += (id == P::node_id()) as usize, + Link::Barrier(id) => depth += (id == K::node_id()) as usize, } entries .iter() .rev() - .filter(move |entry| entry.is::

() && (!entry.scoped || depth <= 1)) - .filter_map(|entry| entry.downcast::

()) + .filter(move |entry| entry.is::() && (!entry.scoped || depth <= 1)) + .filter_map(|entry| entry.downcast::()) }) } diff --git a/src/eval/value.rs b/src/eval/value.rs index 48b2139f1..8867b38ae 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; -use super::{ops, Args, Array, Class, Content, Dict, Func, Layout}; +use super::{ops, Args, Array, Content, Dict, Func, Layout}; use crate::diag::{with_alternative, StrResult}; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; use crate::syntax::Spanned; @@ -47,8 +47,6 @@ pub enum Value { Func(Func), /// Captured arguments to a function. Args(Args), - /// A class of nodes. - Class(Class), /// A dynamic value. Dyn(Dynamic), } @@ -90,7 +88,6 @@ impl Value { Self::Dict(_) => Dict::TYPE_NAME, Self::Func(_) => Func::TYPE_NAME, Self::Args(_) => Args::TYPE_NAME, - Self::Class(_) => Class::TYPE_NAME, Self::Dyn(v) => v.type_name(), } } @@ -151,7 +148,6 @@ impl Debug for Value { Self::Dict(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f), Self::Args(v) => Debug::fmt(v, f), - Self::Class(v) => Debug::fmt(v, f), Self::Dyn(v) => Debug::fmt(v, f), } } @@ -190,7 +186,6 @@ impl Hash for Value { Self::Dict(v) => v.hash(state), Self::Func(v) => v.hash(state), Self::Args(v) => v.hash(state), - Self::Class(v) => v.hash(state), Self::Dyn(v) => v.hash(state), } } @@ -444,9 +439,8 @@ primitive! { EcoString: "string", Str } primitive! { Content: "content", Content, None => Content::new() } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } -primitive! { Func: "function", Func, Class(v) => v.constructor() } +primitive! { Func: "function", Func } primitive! { Args: "arguments", Args } -primitive! { Class: "class", Class } impl Cast for Value { fn is(_: &Value) -> bool { @@ -570,7 +564,7 @@ mod tests { // Functions. test( - Func::native("nil", |_, _| Ok(Value::None)), + Func::from_fn("nil", |_, _| Ok(Value::None)), "", ); diff --git a/src/library/graphics/hide.rs b/src/library/graphics/hide.rs index 21fc58c25..85971c36e 100644 --- a/src/library/graphics/hide.rs +++ b/src/library/graphics/hide.rs @@ -4,7 +4,7 @@ use crate::library::prelude::*; #[derive(Debug, Hash)] pub struct HideNode(pub LayoutNode); -#[class] +#[node] impl HideNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::inline(Self(args.expect("body")?))) diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs index faf89850f..54793754d 100644 --- a/src/library/graphics/image.rs +++ b/src/library/graphics/image.rs @@ -7,7 +7,7 @@ use crate::library::text::TextNode; #[derive(Debug, Hash)] pub struct ImageNode(pub ImageId); -#[class] +#[node] impl ImageNode { /// How the image should adjust itself to a given area. pub const FIT: ImageFit = ImageFit::Cover; diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index fbe213479..9f9ff889e 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -19,7 +19,7 @@ pub type CircleNode = ShapeNode; /// Place a node into an ellipse. pub type EllipseNode = ShapeNode; -#[class] +#[node] impl ShapeNode { /// How to fill the shape. pub const FILL: Option = None; diff --git a/src/library/graphics/transform.rs b/src/library/graphics/transform.rs index fcd7528d4..0dc501668 100644 --- a/src/library/graphics/transform.rs +++ b/src/library/graphics/transform.rs @@ -19,7 +19,7 @@ pub type RotateNode = TransformNode; /// Transform a node by scaling it without affecting layout. pub type ScaleNode = TransformNode; -#[class] +#[node] impl TransformNode { /// The origin of the transformation. pub const ORIGIN: Spec> = Spec::default(); diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs index 2a9695248..b08e5fcee 100644 --- a/src/library/layout/align.rs +++ b/src/library/layout/align.rs @@ -10,7 +10,7 @@ pub struct AlignNode { pub child: LayoutNode, } -#[class] +#[node] impl AlignNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { let aligns: Spec<_> = args.find()?.unwrap_or_default(); diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs index 9e461108d..b5ec7de9c 100644 --- a/src/library/layout/columns.rs +++ b/src/library/layout/columns.rs @@ -11,7 +11,7 @@ pub struct ColumnsNode { pub child: LayoutNode, } -#[class] +#[node] impl ColumnsNode { /// The size of the gutter space between each column. pub const GUTTER: Linear = Relative::new(0.04).into(); @@ -103,7 +103,7 @@ impl Layout for ColumnsNode { /// A column break. pub struct ColbreakNode; -#[class] +#[node] impl ColbreakNode { fn construct(_: &mut Context, _: &mut Args) -> TypResult { Ok(Content::Colbreak) diff --git a/src/library/layout/container.rs b/src/library/layout/container.rs index f7f4017c3..6689dd487 100644 --- a/src/library/layout/container.rs +++ b/src/library/layout/container.rs @@ -3,7 +3,7 @@ use crate::library::prelude::*; /// An inline-level container that sizes content and places it into a paragraph. pub struct BoxNode; -#[class] +#[node] impl BoxNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { let width = args.named("width")?; @@ -16,7 +16,7 @@ impl BoxNode { /// A block-level container that places content into a separate flow. pub struct BlockNode; -#[class] +#[node] impl BlockNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::Block(args.find()?.unwrap_or_default())) diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs index f4b885b1a..3602bea68 100644 --- a/src/library/layout/flow.rs +++ b/src/library/layout/flow.rs @@ -1,4 +1,4 @@ -use super::{AlignNode, PlaceNode, SpacingKind}; +use super::{AlignNode, PlaceNode, Spacing}; use crate::library::prelude::*; use crate::library::text::{ParNode, TextNode}; @@ -19,7 +19,7 @@ pub enum FlowChild { /// A column / region break. Colbreak, /// Vertical spacing between other children. - Spacing(SpacingKind), + Spacing(Spacing), /// An arbitrary block-level node. Node(LayoutNode), } @@ -142,9 +142,9 @@ impl FlowLayouter { } /// Layout spacing. - pub fn layout_spacing(&mut self, spacing: SpacingKind) { + pub fn layout_spacing(&mut self, spacing: Spacing) { match spacing { - SpacingKind::Linear(v) => { + Spacing::Linear(v) => { // Resolve the linear and limit it to the remaining space. let resolved = v.resolve(self.full.y); let limited = resolved.min(self.regions.first.y); @@ -152,7 +152,7 @@ impl FlowLayouter { self.used.y += limited; self.items.push(FlowItem::Absolute(resolved)); } - SpacingKind::Fractional(v) => { + Spacing::Fractional(v) => { self.items.push(FlowItem::Fractional(v)); self.fr += v; } diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index 90cf6da3a..716ac8539 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -11,7 +11,7 @@ pub struct GridNode { pub children: Vec, } -#[class] +#[node] impl GridNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { let columns = args.named("columns")?.unwrap_or_default(); diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs index 835beef98..664c63acb 100644 --- a/src/library/layout/pad.rs +++ b/src/library/layout/pad.rs @@ -9,7 +9,7 @@ pub struct PadNode { pub child: LayoutNode, } -#[class] +#[node] impl PadNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { let all = args.find()?; diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs index def2940e9..b1008febc 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -7,7 +7,7 @@ use crate::library::prelude::*; #[derive(Clone, PartialEq, Hash)] pub struct PageNode(pub LayoutNode); -#[class] +#[node] impl PageNode { /// The unflipped width of the page. pub const WIDTH: Smart = Smart::Custom(Paper::A4.width()); @@ -36,7 +36,9 @@ impl PageNode { Ok(Content::Page(Self(args.expect("body")?))) } - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { + fn set(args: &mut Args) -> TypResult { + let mut styles = StyleMap::new(); + if let Some(paper) = args.named_or_find::("paper")? { styles.set(Self::WIDTH, Smart::Custom(paper.width())); styles.set(Self::HEIGHT, Smart::Custom(paper.height())); @@ -59,7 +61,7 @@ impl PageNode { styles.set_opt(Self::HEADER, args.named("header")?); styles.set_opt(Self::FOOTER, args.named("footer")?); - Ok(()) + Ok(styles) } } @@ -153,7 +155,7 @@ impl Debug for PageNode { /// A page break. pub struct PagebreakNode; -#[class] +#[node] impl PagebreakNode { fn construct(_: &mut Context, _: &mut Args) -> TypResult { Ok(Content::Pagebreak) diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs index 99ff52927..2d4ebc4d3 100644 --- a/src/library/layout/place.rs +++ b/src/library/layout/place.rs @@ -5,7 +5,7 @@ use crate::library::prelude::*; #[derive(Debug, Hash)] pub struct PlaceNode(pub LayoutNode); -#[class] +#[node] impl PlaceNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { let aligns = args.find()?.unwrap_or(Spec::with_x(Some(Align::Left))); diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs index 9a27a8b2e..8aea780ca 100644 --- a/src/library/layout/spacing.rs +++ b/src/library/layout/spacing.rs @@ -3,7 +3,7 @@ use crate::library::prelude::*; /// Horizontal spacing. pub struct HNode; -#[class] +#[node] impl HNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::Horizontal(args.expect("spacing")?)) @@ -13,7 +13,7 @@ impl HNode { /// Vertical spacing. pub struct VNode; -#[class] +#[node] impl VNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::Vertical(args.expect("spacing")?)) @@ -22,28 +22,28 @@ impl VNode { /// Kinds of spacing. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum SpacingKind { +pub enum Spacing { /// A length stated in absolute values and/or relative to the parent's size. Linear(Linear), /// A length that is the fraction of the remaining free space in the parent. Fractional(Fractional), } -impl SpacingKind { +impl Spacing { /// Whether this is fractional spacing. pub fn is_fractional(self) -> bool { matches!(self, Self::Fractional(_)) } } -impl From for SpacingKind { +impl From for Spacing { fn from(length: Length) -> Self { Self::Linear(length.into()) } } castable! { - SpacingKind, + Spacing, Expected: "linear or fractional", Value::Length(v) => Self::Linear(v.into()), Value::Relative(v) => Self::Linear(v.into()), diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs index 88e271168..11d45bb4d 100644 --- a/src/library/layout/stack.rs +++ b/src/library/layout/stack.rs @@ -1,4 +1,4 @@ -use super::{AlignNode, SpacingKind}; +use super::{AlignNode, Spacing}; use crate::library::prelude::*; /// Arrange nodes and spacing along an axis. @@ -7,12 +7,12 @@ pub struct StackNode { /// The stacking direction. pub dir: Dir, /// The spacing between non-spacing children. - pub spacing: Option, + pub spacing: Option, /// The children to be stacked. pub children: Vec, } -#[class] +#[node] impl StackNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::block(Self { @@ -60,7 +60,7 @@ impl Layout for StackNode { #[derive(Hash)] pub enum StackChild { /// Spacing between other nodes. - Spacing(SpacingKind), + Spacing(Spacing), /// An arbitrary node. Node(LayoutNode), } @@ -77,10 +77,10 @@ impl Debug for StackChild { castable! { StackChild, Expected: "linear, fractional or content", - Value::Length(v) => Self::Spacing(SpacingKind::Linear(v.into())), - Value::Relative(v) => Self::Spacing(SpacingKind::Linear(v.into())), - Value::Linear(v) => Self::Spacing(SpacingKind::Linear(v)), - Value::Fractional(v) => Self::Spacing(SpacingKind::Fractional(v)), + Value::Length(v) => Self::Spacing(Spacing::Linear(v.into())), + Value::Relative(v) => Self::Spacing(Spacing::Linear(v.into())), + Value::Linear(v) => Self::Spacing(Spacing::Linear(v)), + Value::Fractional(v) => Self::Spacing(Spacing::Fractional(v)), Value::Content(v) => Self::Node(v.pack()), } @@ -142,9 +142,9 @@ impl StackLayouter { } /// Add spacing along the spacing direction. - pub fn layout_spacing(&mut self, spacing: SpacingKind) { + pub fn layout_spacing(&mut self, spacing: Spacing) { match spacing { - SpacingKind::Linear(v) => { + Spacing::Linear(v) => { // Resolve the linear and limit it to the remaining space. let resolved = v.resolve(self.regions.base.get(self.axis)); let remaining = self.regions.first.get_mut(self.axis); @@ -153,7 +153,7 @@ impl StackLayouter { self.used.main += limited; self.items.push(StackItem::Absolute(resolved)); } - SpacingKind::Fractional(v) => { + Spacing::Fractional(v) => { self.fr += v; self.items.push(StackItem::Fractional(v)); } diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs index b43db22e1..f20d65438 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -11,7 +11,7 @@ pub struct MathNode { pub display: bool, } -#[class] +#[node(showable)] impl MathNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::show(Self { diff --git a/src/library/mod.rs b/src/library/mod.rs index b2e4e4087..8f00e5fe0 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -18,77 +18,77 @@ pub fn new() -> Scope { let mut std = Scope::new(); // Text. - std.def_class::("text"); - std.def_class::("par"); - std.def_class::("linebreak"); - std.def_class::("parbreak"); - std.def_class::("strong"); - std.def_class::("emph"); - std.def_class::("raw"); - std.def_class::("underline"); - std.def_class::("strike"); - std.def_class::("overline"); - std.def_class::("link"); + std.def_node::("text"); + std.def_node::("par"); + std.def_node::("linebreak"); + std.def_node::("parbreak"); + std.def_node::("strong"); + std.def_node::("emph"); + std.def_node::("raw"); + std.def_node::("underline"); + std.def_node::("strike"); + std.def_node::("overline"); + std.def_node::("link"); // Structure. - std.def_class::("heading"); - std.def_class::("list"); - std.def_class::("enum"); - std.def_class::("table"); + std.def_node::("heading"); + std.def_node::("list"); + std.def_node::("enum"); + std.def_node::("table"); // Layout. - std.def_class::("page"); - std.def_class::("pagebreak"); - std.def_class::("h"); - std.def_class::("v"); - std.def_class::("box"); - std.def_class::("block"); - std.def_class::("align"); - std.def_class::("pad"); - std.def_class::("stack"); - std.def_class::("grid"); - std.def_class::("columns"); - std.def_class::("colbreak"); - std.def_class::("place"); + std.def_node::("page"); + std.def_node::("pagebreak"); + std.def_node::("h"); + std.def_node::("v"); + std.def_node::("box"); + std.def_node::("block"); + std.def_node::("align"); + std.def_node::("pad"); + std.def_node::("stack"); + std.def_node::("grid"); + std.def_node::("columns"); + std.def_node::("colbreak"); + std.def_node::("place"); // Graphics. - std.def_class::("image"); - std.def_class::("rect"); - std.def_class::("square"); - std.def_class::("ellipse"); - std.def_class::("circle"); - std.def_class::("move"); - std.def_class::("scale"); - std.def_class::("rotate"); - std.def_class::("hide"); + std.def_node::("image"); + std.def_node::("rect"); + std.def_node::("square"); + std.def_node::("ellipse"); + std.def_node::("circle"); + std.def_node::("move"); + std.def_node::("scale"); + std.def_node::("rotate"); + std.def_node::("hide"); // Math. - std.def_class::("math"); + std.def_node::("math"); // Utility functions. - std.def_func("assert", utility::assert); - std.def_func("type", utility::type_); - std.def_func("repr", utility::repr); - std.def_func("join", utility::join); - std.def_func("int", utility::int); - std.def_func("float", utility::float); - std.def_func("str", utility::str); - std.def_func("abs", utility::abs); - std.def_func("min", utility::min); - std.def_func("max", utility::max); - std.def_func("even", utility::even); - std.def_func("odd", utility::odd); - std.def_func("mod", utility::modulo); - std.def_func("range", utility::range); - std.def_func("rgb", utility::rgb); - std.def_func("cmyk", utility::cmyk); - std.def_func("lower", utility::lower); - std.def_func("upper", utility::upper); - std.def_func("letter", utility::letter); - std.def_func("roman", utility::roman); - std.def_func("symbol", utility::symbol); - std.def_func("len", utility::len); - std.def_func("sorted", utility::sorted); + std.def_fn("assert", utility::assert); + std.def_fn("type", utility::type_); + std.def_fn("repr", utility::repr); + std.def_fn("join", utility::join); + std.def_fn("int", utility::int); + std.def_fn("float", utility::float); + std.def_fn("str", utility::str); + std.def_fn("abs", utility::abs); + std.def_fn("min", utility::min); + std.def_fn("max", utility::max); + std.def_fn("even", utility::even); + std.def_fn("odd", utility::odd); + std.def_fn("mod", utility::modulo); + std.def_fn("range", utility::range); + std.def_fn("rgb", utility::rgb); + std.def_fn("cmyk", utility::cmyk); + std.def_fn("lower", utility::lower); + std.def_fn("upper", utility::upper); + std.def_fn("letter", utility::letter); + std.def_fn("roman", utility::roman); + std.def_fn("symbol", utility::symbol); + std.def_fn("len", utility::len); + std.def_fn("sorted", utility::sorted); // Predefined colors. std.def_const("black", Color::BLACK); diff --git a/src/library/prelude.rs b/src/library/prelude.rs index 0cca718f1..001798f37 100644 --- a/src/library/prelude.rs +++ b/src/library/prelude.rs @@ -5,13 +5,12 @@ pub use std::hash::Hash; pub use std::num::NonZeroUsize; pub use std::sync::Arc; -pub use typst_macros::class; +pub use typst_macros::node; pub use crate::diag::{with_alternative, At, StrResult, TypResult}; pub use crate::eval::{ - Arg, Args, Array, Cast, Construct, Content, Dict, Func, Layout, LayoutNode, Merge, - Property, Regions, Scope, Set, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, - Value, + Arg, Args, Array, Cast, Content, Dict, Func, Key, Layout, LayoutNode, Merge, Node, + Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value, }; pub use crate::frame::*; pub use crate::geom::*; diff --git a/src/library/structure/heading.rs b/src/library/structure/heading.rs index f5565f3c9..f1bc795f6 100644 --- a/src/library/structure/heading.rs +++ b/src/library/structure/heading.rs @@ -11,7 +11,7 @@ pub struct HeadingNode { pub body: Content, } -#[class] +#[node(showable)] impl HeadingNode { /// The heading's font family. Just the normal text family if `auto`. pub const FAMILY: Leveled> = Leveled::Value(Smart::Auto); diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index 2630d231b..414f601e9 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -28,7 +28,7 @@ pub struct ListItem { /// An ordered list. pub type EnumNode = ListNode; -#[class] +#[node(showable)] impl ListNode { /// How the list is labelled. pub const LABEL: Label = Label::Default; diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index 42b62eac7..0e455eadc 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -12,7 +12,7 @@ pub struct TableNode { pub children: Vec, } -#[class] +#[node(showable)] impl TableNode { /// The primary cell fill color. pub const PRIMARY: Option = None; @@ -41,14 +41,15 @@ impl TableNode { })) } - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { + fn set(args: &mut Args) -> TypResult { + let mut styles = StyleMap::new(); let fill = args.named("fill")?; styles.set_opt(Self::PRIMARY, args.named("primary")?.or(fill)); styles.set_opt(Self::SECONDARY, args.named("secondary")?.or(fill)); styles.set_opt(Self::STROKE, args.named("stroke")?); styles.set_opt(Self::THICKNESS, args.named("thickness")?); styles.set_opt(Self::PADDING, args.named("padding")?); - Ok(()) + Ok(styles) } } diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index 608ebb185..29c04b2db 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -18,7 +18,7 @@ pub type StrikethroughNode = DecoNode; /// Typeset overlined text. pub type OverlineNode = DecoNode; -#[class] +#[node(showable)] impl DecoNode { /// Stroke color of the line, defaults to the text color if `None`. #[shorthand] diff --git a/src/library/text/link.rs b/src/library/text/link.rs index e0041df59..4c2b5e7b2 100644 --- a/src/library/text/link.rs +++ b/src/library/text/link.rs @@ -11,7 +11,7 @@ pub struct LinkNode { pub body: Option, } -#[class] +#[node(showable)] impl LinkNode { /// The fill color of text in the link. Just the surrounding text color /// if `auto`. diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs index dbc486fba..8939a8c13 100644 --- a/src/library/text/mod.rs +++ b/src/library/text/mod.rs @@ -25,7 +25,7 @@ use crate::util::EcoString; #[derive(Hash)] pub struct TextNode; -#[class] +#[node] impl TextNode { /// A prioritized sequence of font families. #[variadic] @@ -122,7 +122,7 @@ impl TextNode { #[derive(Debug, Hash)] pub struct StrongNode(pub Content); -#[class] +#[node(showable)] impl StrongNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::show(Self(args.expect("body")?))) @@ -141,7 +141,7 @@ impl Show for StrongNode { #[derive(Debug, Hash)] pub struct EmphNode(pub Content); -#[class] +#[node(showable)] impl EmphNode { fn construct(_: &mut Context, args: &mut Args) -> TypResult { Ok(Content::show(Self(args.expect("body")?))) diff --git a/src/library/text/par.rs b/src/library/text/par.rs index dc888637b..be4e8096b 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -6,7 +6,7 @@ use xi_unicode::LineBreakIterator; use super::{shape, ShapedText, TextNode}; use crate::font::FontStore; -use crate::library::layout::SpacingKind; +use crate::library::layout::Spacing; use crate::library::prelude::*; use crate::util::{ArcExt, EcoString, RangeExt, SliceExt}; @@ -20,12 +20,12 @@ pub enum ParChild { /// A chunk of text. Text(EcoString), /// Horizontal spacing between other children. - Spacing(SpacingKind), + Spacing(Spacing), /// An arbitrary inline-level node. Node(LayoutNode), } -#[class] +#[node] impl ParNode { /// An ISO 639-1 language code. pub const LANG: Option = None; @@ -53,9 +53,10 @@ impl ParNode { Ok(Content::Block(args.expect("body")?)) } - fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { - let lang = args.named::>("lang")?; + fn set(args: &mut Args) -> TypResult { + let mut styles = StyleMap::new(); + let lang = args.named::>("lang")?; let mut dir = lang.clone().flatten().map(|iso| match iso.to_lowercase().as_str() { "ar" | "dv" | "fa" | "he" | "ks" | "pa" | "ps" | "sd" | "ug" | "ur" @@ -89,7 +90,7 @@ impl ParNode { styles.set_opt(Self::SPACING, args.named("spacing")?); styles.set_opt(Self::INDENT, args.named("indent")?); - Ok(()) + Ok(styles) } } @@ -183,7 +184,7 @@ impl Merge for ParChild { /// A paragraph break. pub struct ParbreakNode; -#[class] +#[node] impl ParbreakNode { fn construct(_: &mut Context, _: &mut Args) -> TypResult { Ok(Content::Parbreak) @@ -193,7 +194,7 @@ impl ParbreakNode { /// A line break. pub struct LinebreakNode; -#[class] +#[node] impl LinebreakNode { fn construct(_: &mut Context, _: &mut Args) -> TypResult { Ok(Content::Linebreak) @@ -256,13 +257,13 @@ impl<'a> ParLayout<'a> { ranges.push(subrange); } } - ParChild::Spacing(kind) => match *kind { - SpacingKind::Linear(v) => { + ParChild::Spacing(spacing) => match *spacing { + Spacing::Linear(v) => { let resolved = v.resolve(regions.first.x); items.push(ParItem::Absolute(resolved)); ranges.push(range); } - SpacingKind::Fractional(v) => { + Spacing::Fractional(v) => { items.push(ParItem::Fractional(v)); ranges.push(range); } diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs index 988bd04e8..e225803f6 100644 --- a/src/library/text/raw.rs +++ b/src/library/text/raw.rs @@ -24,7 +24,7 @@ pub struct RawNode { pub block: bool, } -#[class] +#[node(showable)] impl RawNode { /// The language to syntax-highlight in. pub const LANG: Option = None; diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index f48b445c7..928680e57 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -900,16 +900,14 @@ node! { } impl SetExpr { - /// The class to set style properties for. - pub fn class(&self) -> Ident { - self.0.cast_first_child().expect("set expression is missing class") + /// The function to set style properties for. + pub fn target(&self) -> Ident { + self.0.cast_first_child().expect("set rule is missing target") } /// The style properties to set. pub fn args(&self) -> CallArgs { - self.0 - .cast_first_child() - .expect("set expression is missing argument list") + self.0.cast_last_child().expect("set rule is missing argument list") } } @@ -919,14 +917,14 @@ node! { } impl ShowExpr { - /// The class to set the show rule for. - pub fn class(&self) -> Ident { - self.0.cast_first_child().expect("show expression is missing class") + /// The function to customize with this show rule. + pub fn target(&self) -> Ident { + self.0.cast_first_child().expect("show rule is missing target") } /// The closure that defines the rule. - pub fn closure(&self) -> ClosureExpr { - self.0.cast_first_child().expect("show expression is missing closure") + pub fn recipe(&self) -> ClosureExpr { + self.0.cast_last_child().expect("show rule is missing closure") } } diff --git a/tests/ref/code/repr.png b/tests/ref/code/repr.png index be502800b..822b096d3 100644 Binary files a/tests/ref/code/repr.png and b/tests/ref/code/repr.png differ diff --git a/tests/typ/style/show.typ b/tests/typ/style/show.typ index e442e4d57..2e003b0a8 100644 --- a/tests/typ/style/show.typ +++ b/tests/typ/style/show.typ @@ -34,15 +34,19 @@ Another text. A [= Heading] C --- -// Error: 1-22 unexpected argument +// Error: 14-22 unexpected argument #show heading() as [] = Heading --- -// Error: 1-28 expected content, found string +// Error: 14-28 expected content, found string #show heading(_, _) as "hi" = Heading +--- +// Error: 7-12 this function cannot be customized with show +#show upper() as {} + --- // Ref: false // // Error: 1-29 show rule is recursive diff --git a/tests/typeset.rs b/tests/typeset.rs index 388d45168..dfc8bff9d 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -73,7 +73,7 @@ fn main() { let mut std = typst::library::new(); std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); - std.def_func("test", move |_, args| { + std.def_fn("test", move |_, args| { let lhs = args.expect::("left-hand side")?; let rhs = args.expect::("right-hand side")?; if lhs != rhs {