Remove classes and improve naming

This commit is contained in:
Laurenz 2022-03-12 14:24:24 +01:00
parent 5ac7eb3860
commit 2890a156d2
38 changed files with 509 additions and 572 deletions

View File

@ -8,15 +8,16 @@ use syn::punctuated::Punctuated;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{Error, Ident, Result}; use syn::{Error, Ident, Result};
/// Turn a node into a class.
#[proc_macro_attribute] #[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); 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. /// Expand an impl block for a node.
fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> { fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
// Split the node type into name and generic type arguments. // Split the node type into name and generic type arguments.
let params = &impl_block.generics.params; let params = &impl_block.generics.params;
let self_ty = &*impl_block.self_ty; let self_ty = &*impl_block.self_ty;
@ -77,13 +78,20 @@ fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
}); });
parse_quote! { parse_quote! {
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { fn set(args: &mut Args) -> TypResult<StyleMap> {
let mut styles = StyleMap::new();
#(#sets)* #(#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 // Put everything into a module with a hopefully unique type to isolate
// it from the outside. // it from the outside.
Ok(quote! { Ok(quote! {
@ -92,16 +100,14 @@ fn expand(mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
use std::any::TypeId; use std::any::TypeId;
use std::marker::PhantomData; use std::marker::PhantomData;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::eval::{Construct, Nonfolding, Property, Set}; use crate::eval;
use super::*; use super::*;
#impl_block #impl_block
impl<#params> Construct for #self_ty { impl<#params> eval::Node for #self_ty {
const SHOWABLE: bool = #showable;
#construct #construct
}
impl<#params> Set for #self_ty {
#set #set
} }
@ -206,8 +212,8 @@ fn process_const(
)); ));
} }
let nonfolding = fold.is_none().then(|| { let referencable = fold.is_none().then(|| {
quote! { impl<#params> Nonfolding for #key {} } quote! { impl<#params> eval::Referencable for #key {} }
}); });
// Generate the module code. // 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; type Value = #value_ty;
const NAME: &'static str = #name; const NAME: &'static str = #name;
@ -247,7 +253,7 @@ fn process_const(
#fold #fold
} }
#nonfolding #referencable
} }
}; };

202
src/eval/args.rs Normal file
View File

@ -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<Arg>,
}
/// 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<EcoString>,
/// The value of the argument.
pub value: Spanned<Value>,
}
impl Args {
/// Create positional arguments from a span and values.
pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> 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<T>(&mut self, what: &str) -> TypResult<T>
where
T: Cast<Spanned<Value>>,
{
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<T>(&mut self) -> TypResult<Option<T>>
where
T: Cast<Spanned<Value>>,
{
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<T>(&mut self) -> TypResult<Option<T>>
where
T: Cast<Spanned<Value>>,
{
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<T>(&mut self) -> TypResult<Vec<T>>
where
T: Cast<Spanned<Value>>,
{
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<T>(&mut self, name: &str) -> TypResult<Option<T>>
where
T: Cast<Spanned<Value>>,
{
// 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<T>(&mut self, name: &str) -> TypResult<Option<T>>
where
T: Cast<Spanned<Value>>,
{
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<i64> {
self.into_castable("index")
}
/// Reinterpret these arguments as actually being a dictionary key.
pub fn into_key(self) -> TypResult<EcoString> {
self.into_castable("key")
}
/// Reinterpret these arguments as actually being a single castable thing.
fn into_castable<T: Cast>(self, what: &str) -> TypResult<T> {
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)
}
}

View File

@ -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<Value>,
set: fn(&mut Args, &mut StyleMap) -> TypResult<()>,
}
impl Class {
/// Create a new class.
pub fn new<T>(name: &'static str) -> Self
where
T: Construct + Set + 'static,
{
Self {
name,
id: TypeId::of::<T>(),
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<Value> {
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<StyleMap> {
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("<class ")?;
f.write_str(self.name())?;
f.write_char('>')
}
}
impl PartialEq for Class {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Hash for Class {
fn hash<H: Hasher>(&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<Content>;
}
/// 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<()>;
}

View File

@ -6,11 +6,11 @@ use std::ops::{Add, AddAssign};
use typed_arena::Arena; use typed_arena::Arena;
use super::{ use super::{
CollapsingBuilder, Interruption, Layout, LayoutNode, Property, Show, ShowNode, CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Show, ShowNode, StyleMap,
StyleMap, StyleVecBuilder, StyleVecBuilder,
}; };
use crate::diag::StrResult; 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::prelude::*;
use crate::library::structure::{ListItem, ListKind, ListNode, ORDERED, UNORDERED}; use crate::library::structure::{ListItem, ListKind, ListNode, ORDERED, UNORDERED};
use crate::library::text::{DecoNode, ParChild, ParNode, TextNode, UNDERLINE}; use crate::library::text::{DecoNode, ParChild, ParNode, TextNode, UNDERLINE};
@ -42,7 +42,7 @@ pub enum Content {
/// A line break. /// A line break.
Linebreak, Linebreak,
/// Horizontal spacing. /// Horizontal spacing.
Horizontal(SpacingKind), Horizontal(Spacing),
/// Plain text. /// Plain text.
Text(EcoString), Text(EcoString),
/// An inline-level node. /// An inline-level node.
@ -52,7 +52,7 @@ pub enum Content {
/// A column break. /// A column break.
Colbreak, Colbreak,
/// Vertical spacing. /// Vertical spacing.
Vertical(SpacingKind), Vertical(Spacing),
/// A block-level node. /// A block-level node.
Block(LayoutNode), Block(LayoutNode),
/// An item in an unordered list. /// An item in an unordered list.
@ -102,7 +102,7 @@ impl Content {
} }
/// Style this content with a single style property. /// Style this content with a single style property.
pub fn styled<P: Property>(mut self, key: P, value: P::Value) -> Self { pub fn styled<P: Key>(mut self, key: P, value: P::Value) -> Self {
if let Self::Styled(styled) = &mut self { if let Self::Styled(styled) = &mut self {
if let Some((_, map)) = Arc::get_mut(styled) { if let Some((_, map)) = Arc::get_mut(styled) {
if !map.has_scoped() { if !map.has_scoped() {
@ -465,7 +465,7 @@ impl<'a> Builder<'a> {
}) })
.unwrap_or_default() .unwrap_or_default()
{ {
par.push_front(ParChild::Spacing(SpacingKind::Linear(indent))) par.push_front(ParChild::Spacing(Spacing::Linear(indent)))
} }
let node = ParNode(par).pack(); let node = ParNode(par).pack();

View File

@ -1,11 +1,12 @@
use std::any::TypeId;
use std::fmt::{self, Debug, Formatter, Write}; use std::fmt::{self, Debug, Formatter, Write};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
use super::{Cast, Control, Eval, Scope, Scopes, Value}; use super::{Args, Content, Control, Eval, Scope, Scopes, StyleMap, Value};
use crate::diag::{At, TypResult}; use crate::diag::{StrResult, TypResult};
use crate::syntax::ast::Expr; use crate::syntax::ast::Expr;
use crate::syntax::{Span, Spanned}; use crate::syntax::Span;
use crate::util::EcoString; use crate::util::EcoString;
use crate::Context; use crate::Context;
@ -26,18 +27,50 @@ enum Repr {
impl Func { impl Func {
/// Create a new function from a native rust function. /// Create a new function from a native rust function.
pub fn native( pub fn from_fn(
name: &'static str, name: &'static str,
func: fn(&mut Context, &mut Args) -> TypResult<Value>, func: fn(&mut Context, &mut Args) -> TypResult<Value>,
) -> Self { ) -> 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<T: 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::<T>(), recipe, span);
styles
})
} else {
None
},
})))
} }
/// Create a new function from a closure. /// 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))) 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. /// The name of the function.
pub fn name(&self) -> Option<&str> { pub fn name(&self) -> Option<&str> {
match self.0.as_ref() { match self.0.as_ref() {
@ -61,9 +94,22 @@ impl Func {
Ok(value) Ok(value)
} }
/// Apply the given arguments to the function. /// Execute the function's set rule.
pub fn with(self, args: Args) -> Self { pub fn set(&self, mut args: Args) -> TypResult<StyleMap> {
Self(Arc::new(Repr::With(self, args))) 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<StyleMap> {
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, pub name: &'static str,
/// The function pointer. /// The function pointer.
pub func: fn(&mut Context, &mut Args) -> TypResult<Value>, pub func: fn(&mut Context, &mut Args) -> TypResult<Value>,
/// The set rule.
pub set: Option<fn(&mut Args) -> TypResult<StyleMap>>,
/// The show rule.
pub show: Option<fn(Func, Span) -> StyleMap>,
} }
impl Hash for Native { impl Hash for Native {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
(self.func as usize).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<Content>;
/// Parse the arguments into style properties for this node.
fn set(args: &mut Args) -> TypResult<StyleMap>;
}
/// A user-defined closure. /// A user-defined closure.
#[derive(Hash)] #[derive(Hash)]
pub struct Closure { pub struct Closure {
@ -146,199 +214,3 @@ impl Closure {
Ok(value) 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<Arg>,
}
/// 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<EcoString>,
/// The value of the argument.
pub value: Spanned<Value>,
}
impl Args {
/// Create positional arguments from a span and values.
pub fn from_values(span: Span, values: impl IntoIterator<Item = Value>) -> 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<T>(&mut self, what: &str) -> TypResult<T>
where
T: Cast<Spanned<Value>>,
{
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<T>(&mut self) -> TypResult<Option<T>>
where
T: Cast<Spanned<Value>>,
{
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<T>(&mut self) -> TypResult<Option<T>>
where
T: Cast<Spanned<Value>>,
{
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<T>(&mut self) -> TypResult<Vec<T>>
where
T: Cast<Spanned<Value>>,
{
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<T>(&mut self, name: &str) -> TypResult<Option<T>>
where
T: Cast<Spanned<Value>>,
{
// 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<T>(&mut self, name: &str) -> TypResult<Option<T>>
where
T: Cast<Spanned<Value>>,
{
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<i64> {
self.into_castable("index")
}
/// Reinterpret these arguments as actually being a dictionary key.
pub fn into_key(self) -> TypResult<EcoString> {
self.into_castable("key")
}
/// Reinterpret these arguments as actually being a single castable thing.
fn into_castable<T: Cast>(self, what: &str) -> TypResult<T> {
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)
}
}

View File

@ -8,8 +8,8 @@ mod dict;
mod value; mod value;
#[macro_use] #[macro_use]
mod styles; mod styles;
mod args;
mod capture; mod capture;
mod class;
mod collapse; mod collapse;
mod content; mod content;
mod control; mod control;
@ -20,9 +20,9 @@ mod ops;
mod scope; mod scope;
mod show; mod show;
pub use args::*;
pub use array::*; pub use array::*;
pub use capture::*; pub use capture::*;
pub use class::*;
pub use collapse::*; pub use collapse::*;
pub use content::*; pub use content::*;
pub use control::*; pub use control::*;
@ -417,11 +417,6 @@ impl Eval for CallExpr {
func.call(ctx, args).trace(point, self.span()) 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!( v => bail!(
span, span,
"expected callable or collection, found {}", "expected callable or collection, found {}",
@ -520,7 +515,7 @@ impl Eval for ClosureExpr {
} }
// Define the actual function. // Define the actual function.
Ok(Value::Func(Func::closure(Closure { Ok(Value::Func(Func::from_closure(Closure {
name, name,
captured, captured,
params, params,
@ -558,10 +553,10 @@ impl Eval for SetExpr {
type Output = StyleMap; type Output = StyleMap;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
let class = self.class(); let target = self.target();
let class = class.eval(ctx, scp)?.cast::<Class>().at(class.span())?; let target = target.eval(ctx, scp)?.cast::<Func>().at(target.span())?;
let args = self.args().eval(ctx, scp)?; 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; type Output = StyleMap;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
let class = self.class(); let target = self.target();
let class = class.eval(ctx, scp)?.cast::<Class>().at(class.span())?; let target_span = target.span();
let closure = self.closure(); let target = target.eval(ctx, scp)?.cast::<Func>().at(target_span)?;
let func = closure.eval(ctx, scp)?.cast::<Func>().at(closure.span())?; let recipe = self.recipe();
let mut styles = StyleMap::new(); let recipe_span = recipe.span();
styles.set_recipe(class.id(), func, self.span()); let recipe = recipe.eval(ctx, scp)?.cast::<Func>().at(recipe_span)?;
Ok(styles) Ok(target.show(recipe, recipe_span).at(target_span)?)
} }
} }

View File

@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher};
use std::iter; use std::iter;
use std::sync::{Arc, RwLock}; 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::diag::TypResult;
use crate::util::EcoString; use crate::util::EcoString;
use crate::Context; use crate::Context;
@ -83,21 +83,18 @@ impl Scope {
self.values.insert(var.into(), slot); self.values.insert(var.into(), slot);
} }
/// Define a constant native function. /// Define a function through a native rust function.
pub fn def_func( pub fn def_fn(
&mut self, &mut self,
name: &'static str, name: &'static str,
func: fn(&mut Context, &mut Args) -> TypResult<Value>, func: fn(&mut Context, &mut Args) -> TypResult<Value>,
) { ) {
self.def_const(name, Func::native(name, func)); self.def_const(name, Func::from_fn(name, func));
} }
/// Define a constant class. /// Define a function through a native rust node.
pub fn def_class<T>(&mut self, name: &'static str) pub fn def_node<T: Node>(&mut self, name: &'static str) {
where self.def_const(name, Func::from_node::<T>(name));
T: Construct + Set + 'static,
{
self.def_const(name, Class::new::<T>(name));
} }
/// Look up the value of a variable. /// Look up the value of a variable.

View File

@ -30,19 +30,19 @@ impl StyleMap {
} }
/// Create a style map from a single property-value pair. /// Create a style map from a single property-value pair.
pub fn with<P: Property>(key: P, value: P::Value) -> Self { pub fn with<K: Key>(key: K, value: K::Value) -> Self {
let mut styles = Self::new(); let mut styles = Self::new();
styles.set(key, value); styles.set(key, value);
styles styles
} }
/// Set the value for a style property. /// Set the value for a style property.
pub fn set<P: Property>(&mut self, key: P, value: P::Value) { pub fn set<K: Key>(&mut self, key: K, value: K::Value) {
self.props.push(Entry::new(key, value)); self.props.push(Entry::new(key, value));
} }
/// Set a value for a style property if it is `Some(_)`. /// Set a value for a style property if it is `Some(_)`.
pub fn set_opt<P: Property>(&mut self, key: P, value: Option<P::Value>) { pub fn set_opt<K: Key>(&mut self, key: K, value: Option<K::Value>) {
if let Some(value) = value { if let Some(value) = value {
self.set(key, value); self.set(key, value);
} }
@ -126,8 +126,8 @@ pub enum Interruption {
/// Style property keys. /// Style property keys.
/// ///
/// This trait is not intended to be implemented manually, but rather through /// This trait is not intended to be implemented manually, but rather through
/// the `#[class]` proc-macro. /// the `#[node]` proc-macro.
pub trait Property: Sync + Send + 'static { pub trait Key: Sync + Send + 'static {
/// The type of value that is returned when getting this property from a /// The type of value that is returned when getting this property from a
/// style map. For example, this could be [`Length`](crate::geom::Length) /// style map. For example, this could be [`Length`](crate::geom::Length)
/// for a `WIDTH` property. /// for a `WIDTH` property.
@ -148,7 +148,7 @@ pub trait Property: Sync + Send + 'static {
/// A static reference to the default value of the property. /// A static reference to the default value of the property.
/// ///
/// This is automatically implemented through lazy-initialization in the /// 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. /// recreated all the time.
fn default_ref() -> &'static Self::Value; 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. /// Marker trait indicating that a property can be accessed by reference.
pub trait Nonfolding {} ///
/// 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. /// An entry for a single style property.
#[derive(Clone)] #[derive(Clone)]
@ -173,14 +177,14 @@ struct Entry {
} }
impl Entry { impl Entry {
fn new<P: Property>(key: P, value: P::Value) -> Self { fn new<K: Key>(key: K, value: K::Value) -> Self {
Self { Self {
pair: Arc::new((key, value)), pair: Arc::new((key, value)),
scoped: false, scoped: false,
} }
} }
fn is<P: Property>(&self) -> bool { fn is<P: Key>(&self) -> bool {
self.pair.style_id() == TypeId::of::<P>() self.pair.style_id() == TypeId::of::<P>()
} }
@ -192,7 +196,7 @@ impl Entry {
self.pair.node_id() == node self.pair.node_id() == node
} }
fn downcast<P: Property>(&self) -> Option<&P::Value> { fn downcast<K: Key>(&self) -> Option<&K::Value> {
self.pair.as_any().downcast_ref() self.pair.as_any().downcast_ref()
} }
@ -244,18 +248,18 @@ trait Bounds: Sync + Send + 'static {
fn style_id(&self) -> TypeId; fn style_id(&self) -> TypeId;
} }
impl<P: Property> Bounds for (P, P::Value) { impl<K: Key> Bounds for (K, K::Value) {
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
&self.1 &self.1
} }
fn dyn_fmt(&self, f: &mut Formatter) -> fmt::Result { 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 { fn dyn_eq(&self, other: &Entry) -> bool {
self.style_id() == other.pair.style_id() self.style_id() == other.pair.style_id()
&& if let Some(other) = other.downcast::<P>() { && if let Some(other) = other.downcast::<K>() {
&self.1 == other &self.1 == other
} else { } else {
false false
@ -270,11 +274,11 @@ impl<P: Property> Bounds for (P, P::Value) {
} }
fn node_id(&self) -> TypeId { fn node_id(&self) -> TypeId {
P::node_id() K::node_id()
} }
fn style_id(&self) -> TypeId { fn style_id(&self) -> TypeId {
TypeId::of::<P>() TypeId::of::<K>()
} }
} }
@ -366,9 +370,9 @@ impl<'a> StyleChain<'a> {
/// ///
/// Returns the property's default value if no map in the chain contains an /// Returns the property's default value if no map in the chain contains an
/// entry for it. /// entry for it.
pub fn get<P: Property>(self, key: P) -> P::Value pub fn get<K: Key>(self, key: K) -> K::Value
where where
P::Value: Copy, K::Value: Copy,
{ {
self.get_cloned(key) self.get_cloned(key)
} }
@ -381,11 +385,11 @@ impl<'a> StyleChain<'a> {
/// ///
/// Returns a lazily-initialized reference to the property's default value /// Returns a lazily-initialized reference to the property's default value
/// if no map in the chain contains an entry for it. /// if no map in the chain contains an entry for it.
pub fn get_ref<P: Property>(self, key: P) -> &'a P::Value pub fn get_ref<K: Key>(self, key: K) -> &'a K::Value
where 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. /// 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 /// Returns the property's default value if no map in the chain contains an
/// entry for it. /// entry for it.
pub fn get_cloned<P: Property>(self, key: P) -> P::Value { pub fn get_cloned<K: Key>(self, key: K) -> K::Value {
if P::FOLDING { if K::FOLDING {
self.values(key) self.values(key)
.cloned() .cloned()
.chain(std::iter::once(P::default())) .chain(std::iter::once(K::default()))
.reduce(P::fold) .reduce(K::fold)
.unwrap() .unwrap()
} else { } 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> { impl<'a> StyleChain<'a> {
/// Iterate over all values for the given property in the chain. /// Iterate over all values for the given property in the chain.
fn values<P: Property>(self, _: P) -> impl Iterator<Item = &'a P::Value> { fn values<K: Key>(self, _: K) -> impl Iterator<Item = &'a K::Value> {
let mut depth = 0; let mut depth = 0;
self.links().flat_map(move |link| { self.links().flat_map(move |link| {
let mut entries: &[Entry] = &[]; let mut entries: &[Entry] = &[];
match link { match link {
Link::Map(map) => entries = &map.props, 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 entries
.iter() .iter()
.rev() .rev()
.filter(move |entry| entry.is::<P>() && (!entry.scoped || depth <= 1)) .filter(move |entry| entry.is::<K>() && (!entry.scoped || depth <= 1))
.filter_map(|entry| entry.downcast::<P>()) .filter_map(|entry| entry.downcast::<K>())
}) })
} }

View File

@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::Arc; 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::diag::{with_alternative, StrResult};
use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor};
use crate::syntax::Spanned; use crate::syntax::Spanned;
@ -47,8 +47,6 @@ pub enum Value {
Func(Func), Func(Func),
/// Captured arguments to a function. /// Captured arguments to a function.
Args(Args), Args(Args),
/// A class of nodes.
Class(Class),
/// A dynamic value. /// A dynamic value.
Dyn(Dynamic), Dyn(Dynamic),
} }
@ -90,7 +88,6 @@ impl Value {
Self::Dict(_) => Dict::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME,
Self::Func(_) => Func::TYPE_NAME, Self::Func(_) => Func::TYPE_NAME,
Self::Args(_) => Args::TYPE_NAME, Self::Args(_) => Args::TYPE_NAME,
Self::Class(_) => Class::TYPE_NAME,
Self::Dyn(v) => v.type_name(), Self::Dyn(v) => v.type_name(),
} }
} }
@ -151,7 +148,6 @@ impl Debug for Value {
Self::Dict(v) => Debug::fmt(v, f), Self::Dict(v) => Debug::fmt(v, f),
Self::Func(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f),
Self::Args(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), Self::Dyn(v) => Debug::fmt(v, f),
} }
} }
@ -190,7 +186,6 @@ impl Hash for Value {
Self::Dict(v) => v.hash(state), Self::Dict(v) => v.hash(state),
Self::Func(v) => v.hash(state), Self::Func(v) => v.hash(state),
Self::Args(v) => v.hash(state), Self::Args(v) => v.hash(state),
Self::Class(v) => v.hash(state),
Self::Dyn(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! { Content: "content", Content, None => Content::new() }
primitive! { Array: "array", Array } primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict } primitive! { Dict: "dictionary", Dict }
primitive! { Func: "function", Func, Class(v) => v.constructor() } primitive! { Func: "function", Func }
primitive! { Args: "arguments", Args } primitive! { Args: "arguments", Args }
primitive! { Class: "class", Class }
impl Cast for Value { impl Cast for Value {
fn is(_: &Value) -> bool { fn is(_: &Value) -> bool {
@ -570,7 +564,7 @@ mod tests {
// Functions. // Functions.
test( test(
Func::native("nil", |_, _| Ok(Value::None)), Func::from_fn("nil", |_, _| Ok(Value::None)),
"<function nil>", "<function nil>",
); );

View File

@ -4,7 +4,7 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct HideNode(pub LayoutNode); pub struct HideNode(pub LayoutNode);
#[class] #[node]
impl HideNode { impl HideNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::inline(Self(args.expect("body")?))) Ok(Content::inline(Self(args.expect("body")?)))

View File

@ -7,7 +7,7 @@ use crate::library::text::TextNode;
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct ImageNode(pub ImageId); pub struct ImageNode(pub ImageId);
#[class] #[node]
impl ImageNode { impl ImageNode {
/// How the image should adjust itself to a given area. /// How the image should adjust itself to a given area.
pub const FIT: ImageFit = ImageFit::Cover; pub const FIT: ImageFit = ImageFit::Cover;

View File

@ -19,7 +19,7 @@ pub type CircleNode = ShapeNode<CIRCLE>;
/// Place a node into an ellipse. /// Place a node into an ellipse.
pub type EllipseNode = ShapeNode<ELLIPSE>; pub type EllipseNode = ShapeNode<ELLIPSE>;
#[class] #[node]
impl<const S: ShapeKind> ShapeNode<S> { impl<const S: ShapeKind> ShapeNode<S> {
/// How to fill the shape. /// How to fill the shape.
pub const FILL: Option<Paint> = None; pub const FILL: Option<Paint> = None;

View File

@ -19,7 +19,7 @@ pub type RotateNode = TransformNode<ROTATE>;
/// Transform a node by scaling it without affecting layout. /// Transform a node by scaling it without affecting layout.
pub type ScaleNode = TransformNode<SCALE>; pub type ScaleNode = TransformNode<SCALE>;
#[class] #[node]
impl<const T: TransformKind> TransformNode<T> { impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation. /// The origin of the transformation.
pub const ORIGIN: Spec<Option<Align>> = Spec::default(); pub const ORIGIN: Spec<Option<Align>> = Spec::default();

View File

@ -10,7 +10,7 @@ pub struct AlignNode {
pub child: LayoutNode, pub child: LayoutNode,
} }
#[class] #[node]
impl AlignNode { impl AlignNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let aligns: Spec<_> = args.find()?.unwrap_or_default(); let aligns: Spec<_> = args.find()?.unwrap_or_default();

View File

@ -11,7 +11,7 @@ pub struct ColumnsNode {
pub child: LayoutNode, pub child: LayoutNode,
} }
#[class] #[node]
impl ColumnsNode { impl ColumnsNode {
/// The size of the gutter space between each column. /// The size of the gutter space between each column.
pub const GUTTER: Linear = Relative::new(0.04).into(); pub const GUTTER: Linear = Relative::new(0.04).into();
@ -103,7 +103,7 @@ impl Layout for ColumnsNode {
/// A column break. /// A column break.
pub struct ColbreakNode; pub struct ColbreakNode;
#[class] #[node]
impl ColbreakNode { impl ColbreakNode {
fn construct(_: &mut Context, _: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, _: &mut Args) -> TypResult<Content> {
Ok(Content::Colbreak) Ok(Content::Colbreak)

View File

@ -3,7 +3,7 @@ use crate::library::prelude::*;
/// An inline-level container that sizes content and places it into a paragraph. /// An inline-level container that sizes content and places it into a paragraph.
pub struct BoxNode; pub struct BoxNode;
#[class] #[node]
impl BoxNode { impl BoxNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let width = args.named("width")?; let width = args.named("width")?;
@ -16,7 +16,7 @@ impl BoxNode {
/// A block-level container that places content into a separate flow. /// A block-level container that places content into a separate flow.
pub struct BlockNode; pub struct BlockNode;
#[class] #[node]
impl BlockNode { impl BlockNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::Block(args.find()?.unwrap_or_default())) Ok(Content::Block(args.find()?.unwrap_or_default()))

View File

@ -1,4 +1,4 @@
use super::{AlignNode, PlaceNode, SpacingKind}; use super::{AlignNode, PlaceNode, Spacing};
use crate::library::prelude::*; use crate::library::prelude::*;
use crate::library::text::{ParNode, TextNode}; use crate::library::text::{ParNode, TextNode};
@ -19,7 +19,7 @@ pub enum FlowChild {
/// A column / region break. /// A column / region break.
Colbreak, Colbreak,
/// Vertical spacing between other children. /// Vertical spacing between other children.
Spacing(SpacingKind), Spacing(Spacing),
/// An arbitrary block-level node. /// An arbitrary block-level node.
Node(LayoutNode), Node(LayoutNode),
} }
@ -142,9 +142,9 @@ impl FlowLayouter {
} }
/// Layout spacing. /// Layout spacing.
pub fn layout_spacing(&mut self, spacing: SpacingKind) { pub fn layout_spacing(&mut self, spacing: Spacing) {
match spacing { match spacing {
SpacingKind::Linear(v) => { Spacing::Linear(v) => {
// Resolve the linear and limit it to the remaining space. // Resolve the linear and limit it to the remaining space.
let resolved = v.resolve(self.full.y); let resolved = v.resolve(self.full.y);
let limited = resolved.min(self.regions.first.y); let limited = resolved.min(self.regions.first.y);
@ -152,7 +152,7 @@ impl FlowLayouter {
self.used.y += limited; self.used.y += limited;
self.items.push(FlowItem::Absolute(resolved)); self.items.push(FlowItem::Absolute(resolved));
} }
SpacingKind::Fractional(v) => { Spacing::Fractional(v) => {
self.items.push(FlowItem::Fractional(v)); self.items.push(FlowItem::Fractional(v));
self.fr += v; self.fr += v;
} }

View File

@ -11,7 +11,7 @@ pub struct GridNode {
pub children: Vec<LayoutNode>, pub children: Vec<LayoutNode>,
} }
#[class] #[node]
impl GridNode { impl GridNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let columns = args.named("columns")?.unwrap_or_default(); let columns = args.named("columns")?.unwrap_or_default();

View File

@ -9,7 +9,7 @@ pub struct PadNode {
pub child: LayoutNode, pub child: LayoutNode,
} }
#[class] #[node]
impl PadNode { impl PadNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let all = args.find()?; let all = args.find()?;

View File

@ -7,7 +7,7 @@ use crate::library::prelude::*;
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq, Hash)]
pub struct PageNode(pub LayoutNode); pub struct PageNode(pub LayoutNode);
#[class] #[node]
impl PageNode { impl PageNode {
/// The unflipped width of the page. /// The unflipped width of the page.
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width()); pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width());
@ -36,7 +36,9 @@ impl PageNode {
Ok(Content::Page(Self(args.expect("body")?))) Ok(Content::Page(Self(args.expect("body")?)))
} }
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { fn set(args: &mut Args) -> TypResult<StyleMap> {
let mut styles = StyleMap::new();
if let Some(paper) = args.named_or_find::<Paper>("paper")? { if let Some(paper) = args.named_or_find::<Paper>("paper")? {
styles.set(Self::WIDTH, Smart::Custom(paper.width())); styles.set(Self::WIDTH, Smart::Custom(paper.width()));
styles.set(Self::HEIGHT, Smart::Custom(paper.height())); 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::HEADER, args.named("header")?);
styles.set_opt(Self::FOOTER, args.named("footer")?); styles.set_opt(Self::FOOTER, args.named("footer")?);
Ok(()) Ok(styles)
} }
} }
@ -153,7 +155,7 @@ impl Debug for PageNode {
/// A page break. /// A page break.
pub struct PagebreakNode; pub struct PagebreakNode;
#[class] #[node]
impl PagebreakNode { impl PagebreakNode {
fn construct(_: &mut Context, _: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, _: &mut Args) -> TypResult<Content> {
Ok(Content::Pagebreak) Ok(Content::Pagebreak)

View File

@ -5,7 +5,7 @@ use crate::library::prelude::*;
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct PlaceNode(pub LayoutNode); pub struct PlaceNode(pub LayoutNode);
#[class] #[node]
impl PlaceNode { impl PlaceNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(Align::Left))); let aligns = args.find()?.unwrap_or(Spec::with_x(Some(Align::Left)));

View File

@ -3,7 +3,7 @@ use crate::library::prelude::*;
/// Horizontal spacing. /// Horizontal spacing.
pub struct HNode; pub struct HNode;
#[class] #[node]
impl HNode { impl HNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::Horizontal(args.expect("spacing")?)) Ok(Content::Horizontal(args.expect("spacing")?))
@ -13,7 +13,7 @@ impl HNode {
/// Vertical spacing. /// Vertical spacing.
pub struct VNode; pub struct VNode;
#[class] #[node]
impl VNode { impl VNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::Vertical(args.expect("spacing")?)) Ok(Content::Vertical(args.expect("spacing")?))
@ -22,28 +22,28 @@ impl VNode {
/// Kinds of spacing. /// Kinds of spacing.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[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. /// A length stated in absolute values and/or relative to the parent's size.
Linear(Linear), Linear(Linear),
/// A length that is the fraction of the remaining free space in the parent. /// A length that is the fraction of the remaining free space in the parent.
Fractional(Fractional), Fractional(Fractional),
} }
impl SpacingKind { impl Spacing {
/// Whether this is fractional spacing. /// Whether this is fractional spacing.
pub fn is_fractional(self) -> bool { pub fn is_fractional(self) -> bool {
matches!(self, Self::Fractional(_)) matches!(self, Self::Fractional(_))
} }
} }
impl From<Length> for SpacingKind { impl From<Length> for Spacing {
fn from(length: Length) -> Self { fn from(length: Length) -> Self {
Self::Linear(length.into()) Self::Linear(length.into())
} }
} }
castable! { castable! {
SpacingKind, Spacing,
Expected: "linear or fractional", Expected: "linear or fractional",
Value::Length(v) => Self::Linear(v.into()), Value::Length(v) => Self::Linear(v.into()),
Value::Relative(v) => Self::Linear(v.into()), Value::Relative(v) => Self::Linear(v.into()),

View File

@ -1,4 +1,4 @@
use super::{AlignNode, SpacingKind}; use super::{AlignNode, Spacing};
use crate::library::prelude::*; use crate::library::prelude::*;
/// Arrange nodes and spacing along an axis. /// Arrange nodes and spacing along an axis.
@ -7,12 +7,12 @@ pub struct StackNode {
/// The stacking direction. /// The stacking direction.
pub dir: Dir, pub dir: Dir,
/// The spacing between non-spacing children. /// The spacing between non-spacing children.
pub spacing: Option<SpacingKind>, pub spacing: Option<Spacing>,
/// The children to be stacked. /// The children to be stacked.
pub children: Vec<StackChild>, pub children: Vec<StackChild>,
} }
#[class] #[node]
impl StackNode { impl StackNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::block(Self { Ok(Content::block(Self {
@ -60,7 +60,7 @@ impl Layout for StackNode {
#[derive(Hash)] #[derive(Hash)]
pub enum StackChild { pub enum StackChild {
/// Spacing between other nodes. /// Spacing between other nodes.
Spacing(SpacingKind), Spacing(Spacing),
/// An arbitrary node. /// An arbitrary node.
Node(LayoutNode), Node(LayoutNode),
} }
@ -77,10 +77,10 @@ impl Debug for StackChild {
castable! { castable! {
StackChild, StackChild,
Expected: "linear, fractional or content", Expected: "linear, fractional or content",
Value::Length(v) => Self::Spacing(SpacingKind::Linear(v.into())), Value::Length(v) => Self::Spacing(Spacing::Linear(v.into())),
Value::Relative(v) => Self::Spacing(SpacingKind::Linear(v.into())), Value::Relative(v) => Self::Spacing(Spacing::Linear(v.into())),
Value::Linear(v) => Self::Spacing(SpacingKind::Linear(v)), Value::Linear(v) => Self::Spacing(Spacing::Linear(v)),
Value::Fractional(v) => Self::Spacing(SpacingKind::Fractional(v)), Value::Fractional(v) => Self::Spacing(Spacing::Fractional(v)),
Value::Content(v) => Self::Node(v.pack()), Value::Content(v) => Self::Node(v.pack()),
} }
@ -142,9 +142,9 @@ impl StackLayouter {
} }
/// Add spacing along the spacing direction. /// Add spacing along the spacing direction.
pub fn layout_spacing(&mut self, spacing: SpacingKind) { pub fn layout_spacing(&mut self, spacing: Spacing) {
match spacing { match spacing {
SpacingKind::Linear(v) => { Spacing::Linear(v) => {
// Resolve the linear and limit it to the remaining space. // Resolve the linear and limit it to the remaining space.
let resolved = v.resolve(self.regions.base.get(self.axis)); let resolved = v.resolve(self.regions.base.get(self.axis));
let remaining = self.regions.first.get_mut(self.axis); let remaining = self.regions.first.get_mut(self.axis);
@ -153,7 +153,7 @@ impl StackLayouter {
self.used.main += limited; self.used.main += limited;
self.items.push(StackItem::Absolute(resolved)); self.items.push(StackItem::Absolute(resolved));
} }
SpacingKind::Fractional(v) => { Spacing::Fractional(v) => {
self.fr += v; self.fr += v;
self.items.push(StackItem::Fractional(v)); self.items.push(StackItem::Fractional(v));
} }

View File

@ -11,7 +11,7 @@ pub struct MathNode {
pub display: bool, pub display: bool,
} }
#[class] #[node(showable)]
impl MathNode { impl MathNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self { Ok(Content::show(Self {

View File

@ -18,77 +18,77 @@ pub fn new() -> Scope {
let mut std = Scope::new(); let mut std = Scope::new();
// Text. // Text.
std.def_class::<text::TextNode>("text"); std.def_node::<text::TextNode>("text");
std.def_class::<text::ParNode>("par"); std.def_node::<text::ParNode>("par");
std.def_class::<text::LinebreakNode>("linebreak"); std.def_node::<text::LinebreakNode>("linebreak");
std.def_class::<text::ParbreakNode>("parbreak"); std.def_node::<text::ParbreakNode>("parbreak");
std.def_class::<text::StrongNode>("strong"); std.def_node::<text::StrongNode>("strong");
std.def_class::<text::EmphNode>("emph"); std.def_node::<text::EmphNode>("emph");
std.def_class::<text::RawNode>("raw"); std.def_node::<text::RawNode>("raw");
std.def_class::<text::UnderlineNode>("underline"); std.def_node::<text::UnderlineNode>("underline");
std.def_class::<text::StrikethroughNode>("strike"); std.def_node::<text::StrikethroughNode>("strike");
std.def_class::<text::OverlineNode>("overline"); std.def_node::<text::OverlineNode>("overline");
std.def_class::<text::LinkNode>("link"); std.def_node::<text::LinkNode>("link");
// Structure. // Structure.
std.def_class::<structure::HeadingNode>("heading"); std.def_node::<structure::HeadingNode>("heading");
std.def_class::<structure::ListNode>("list"); std.def_node::<structure::ListNode>("list");
std.def_class::<structure::EnumNode>("enum"); std.def_node::<structure::EnumNode>("enum");
std.def_class::<structure::TableNode>("table"); std.def_node::<structure::TableNode>("table");
// Layout. // Layout.
std.def_class::<layout::PageNode>("page"); std.def_node::<layout::PageNode>("page");
std.def_class::<layout::PagebreakNode>("pagebreak"); std.def_node::<layout::PagebreakNode>("pagebreak");
std.def_class::<layout::HNode>("h"); std.def_node::<layout::HNode>("h");
std.def_class::<layout::VNode>("v"); std.def_node::<layout::VNode>("v");
std.def_class::<layout::BoxNode>("box"); std.def_node::<layout::BoxNode>("box");
std.def_class::<layout::BlockNode>("block"); std.def_node::<layout::BlockNode>("block");
std.def_class::<layout::AlignNode>("align"); std.def_node::<layout::AlignNode>("align");
std.def_class::<layout::PadNode>("pad"); std.def_node::<layout::PadNode>("pad");
std.def_class::<layout::StackNode>("stack"); std.def_node::<layout::StackNode>("stack");
std.def_class::<layout::GridNode>("grid"); std.def_node::<layout::GridNode>("grid");
std.def_class::<layout::ColumnsNode>("columns"); std.def_node::<layout::ColumnsNode>("columns");
std.def_class::<layout::ColbreakNode>("colbreak"); std.def_node::<layout::ColbreakNode>("colbreak");
std.def_class::<layout::PlaceNode>("place"); std.def_node::<layout::PlaceNode>("place");
// Graphics. // Graphics.
std.def_class::<graphics::ImageNode>("image"); std.def_node::<graphics::ImageNode>("image");
std.def_class::<graphics::RectNode>("rect"); std.def_node::<graphics::RectNode>("rect");
std.def_class::<graphics::SquareNode>("square"); std.def_node::<graphics::SquareNode>("square");
std.def_class::<graphics::EllipseNode>("ellipse"); std.def_node::<graphics::EllipseNode>("ellipse");
std.def_class::<graphics::CircleNode>("circle"); std.def_node::<graphics::CircleNode>("circle");
std.def_class::<graphics::MoveNode>("move"); std.def_node::<graphics::MoveNode>("move");
std.def_class::<graphics::ScaleNode>("scale"); std.def_node::<graphics::ScaleNode>("scale");
std.def_class::<graphics::RotateNode>("rotate"); std.def_node::<graphics::RotateNode>("rotate");
std.def_class::<graphics::HideNode>("hide"); std.def_node::<graphics::HideNode>("hide");
// Math. // Math.
std.def_class::<math::MathNode>("math"); std.def_node::<math::MathNode>("math");
// Utility functions. // Utility functions.
std.def_func("assert", utility::assert); std.def_fn("assert", utility::assert);
std.def_func("type", utility::type_); std.def_fn("type", utility::type_);
std.def_func("repr", utility::repr); std.def_fn("repr", utility::repr);
std.def_func("join", utility::join); std.def_fn("join", utility::join);
std.def_func("int", utility::int); std.def_fn("int", utility::int);
std.def_func("float", utility::float); std.def_fn("float", utility::float);
std.def_func("str", utility::str); std.def_fn("str", utility::str);
std.def_func("abs", utility::abs); std.def_fn("abs", utility::abs);
std.def_func("min", utility::min); std.def_fn("min", utility::min);
std.def_func("max", utility::max); std.def_fn("max", utility::max);
std.def_func("even", utility::even); std.def_fn("even", utility::even);
std.def_func("odd", utility::odd); std.def_fn("odd", utility::odd);
std.def_func("mod", utility::modulo); std.def_fn("mod", utility::modulo);
std.def_func("range", utility::range); std.def_fn("range", utility::range);
std.def_func("rgb", utility::rgb); std.def_fn("rgb", utility::rgb);
std.def_func("cmyk", utility::cmyk); std.def_fn("cmyk", utility::cmyk);
std.def_func("lower", utility::lower); std.def_fn("lower", utility::lower);
std.def_func("upper", utility::upper); std.def_fn("upper", utility::upper);
std.def_func("letter", utility::letter); std.def_fn("letter", utility::letter);
std.def_func("roman", utility::roman); std.def_fn("roman", utility::roman);
std.def_func("symbol", utility::symbol); std.def_fn("symbol", utility::symbol);
std.def_func("len", utility::len); std.def_fn("len", utility::len);
std.def_func("sorted", utility::sorted); std.def_fn("sorted", utility::sorted);
// Predefined colors. // Predefined colors.
std.def_const("black", Color::BLACK); std.def_const("black", Color::BLACK);

View File

@ -5,13 +5,12 @@ pub use std::hash::Hash;
pub use std::num::NonZeroUsize; pub use std::num::NonZeroUsize;
pub use std::sync::Arc; 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::diag::{with_alternative, At, StrResult, TypResult};
pub use crate::eval::{ pub use crate::eval::{
Arg, Args, Array, Cast, Construct, Content, Dict, Func, Layout, LayoutNode, Merge, Arg, Args, Array, Cast, Content, Dict, Func, Key, Layout, LayoutNode, Merge, Node,
Property, Regions, Scope, Set, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Regions, Scope, Show, ShowNode, Smart, StyleChain, StyleMap, StyleVec, Value,
Value,
}; };
pub use crate::frame::*; pub use crate::frame::*;
pub use crate::geom::*; pub use crate::geom::*;

View File

@ -11,7 +11,7 @@ pub struct HeadingNode {
pub body: Content, pub body: Content,
} }
#[class] #[node(showable)]
impl HeadingNode { impl HeadingNode {
/// The heading's font family. Just the normal text family if `auto`. /// The heading's font family. Just the normal text family if `auto`.
pub const FAMILY: Leveled<Smart<FontFamily>> = Leveled::Value(Smart::Auto); pub const FAMILY: Leveled<Smart<FontFamily>> = Leveled::Value(Smart::Auto);

View File

@ -28,7 +28,7 @@ pub struct ListItem {
/// An ordered list. /// An ordered list.
pub type EnumNode = ListNode<ORDERED>; pub type EnumNode = ListNode<ORDERED>;
#[class] #[node(showable)]
impl<const L: ListKind> ListNode<L> { impl<const L: ListKind> ListNode<L> {
/// How the list is labelled. /// How the list is labelled.
pub const LABEL: Label = Label::Default; pub const LABEL: Label = Label::Default;

View File

@ -12,7 +12,7 @@ pub struct TableNode {
pub children: Vec<Content>, pub children: Vec<Content>,
} }
#[class] #[node(showable)]
impl TableNode { impl TableNode {
/// The primary cell fill color. /// The primary cell fill color.
pub const PRIMARY: Option<Paint> = None; pub const PRIMARY: Option<Paint> = None;
@ -41,14 +41,15 @@ impl TableNode {
})) }))
} }
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { fn set(args: &mut Args) -> TypResult<StyleMap> {
let mut styles = StyleMap::new();
let fill = args.named("fill")?; let fill = args.named("fill")?;
styles.set_opt(Self::PRIMARY, args.named("primary")?.or(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::SECONDARY, args.named("secondary")?.or(fill));
styles.set_opt(Self::STROKE, args.named("stroke")?); styles.set_opt(Self::STROKE, args.named("stroke")?);
styles.set_opt(Self::THICKNESS, args.named("thickness")?); styles.set_opt(Self::THICKNESS, args.named("thickness")?);
styles.set_opt(Self::PADDING, args.named("padding")?); styles.set_opt(Self::PADDING, args.named("padding")?);
Ok(()) Ok(styles)
} }
} }

View File

@ -18,7 +18,7 @@ pub type StrikethroughNode = DecoNode<STRIKETHROUGH>;
/// Typeset overlined text. /// Typeset overlined text.
pub type OverlineNode = DecoNode<OVERLINE>; pub type OverlineNode = DecoNode<OVERLINE>;
#[class] #[node(showable)]
impl<const L: DecoLine> DecoNode<L> { impl<const L: DecoLine> DecoNode<L> {
/// Stroke color of the line, defaults to the text color if `None`. /// Stroke color of the line, defaults to the text color if `None`.
#[shorthand] #[shorthand]

View File

@ -11,7 +11,7 @@ pub struct LinkNode {
pub body: Option<Content>, pub body: Option<Content>,
} }
#[class] #[node(showable)]
impl LinkNode { impl LinkNode {
/// The fill color of text in the link. Just the surrounding text color /// The fill color of text in the link. Just the surrounding text color
/// if `auto`. /// if `auto`.

View File

@ -25,7 +25,7 @@ use crate::util::EcoString;
#[derive(Hash)] #[derive(Hash)]
pub struct TextNode; pub struct TextNode;
#[class] #[node]
impl TextNode { impl TextNode {
/// A prioritized sequence of font families. /// A prioritized sequence of font families.
#[variadic] #[variadic]
@ -122,7 +122,7 @@ impl TextNode {
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct StrongNode(pub Content); pub struct StrongNode(pub Content);
#[class] #[node(showable)]
impl StrongNode { impl StrongNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self(args.expect("body")?))) Ok(Content::show(Self(args.expect("body")?)))
@ -141,7 +141,7 @@ impl Show for StrongNode {
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
pub struct EmphNode(pub Content); pub struct EmphNode(pub Content);
#[class] #[node(showable)]
impl EmphNode { impl EmphNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
Ok(Content::show(Self(args.expect("body")?))) Ok(Content::show(Self(args.expect("body")?)))

View File

@ -6,7 +6,7 @@ use xi_unicode::LineBreakIterator;
use super::{shape, ShapedText, TextNode}; use super::{shape, ShapedText, TextNode};
use crate::font::FontStore; use crate::font::FontStore;
use crate::library::layout::SpacingKind; use crate::library::layout::Spacing;
use crate::library::prelude::*; use crate::library::prelude::*;
use crate::util::{ArcExt, EcoString, RangeExt, SliceExt}; use crate::util::{ArcExt, EcoString, RangeExt, SliceExt};
@ -20,12 +20,12 @@ pub enum ParChild {
/// A chunk of text. /// A chunk of text.
Text(EcoString), Text(EcoString),
/// Horizontal spacing between other children. /// Horizontal spacing between other children.
Spacing(SpacingKind), Spacing(Spacing),
/// An arbitrary inline-level node. /// An arbitrary inline-level node.
Node(LayoutNode), Node(LayoutNode),
} }
#[class] #[node]
impl ParNode { impl ParNode {
/// An ISO 639-1 language code. /// An ISO 639-1 language code.
pub const LANG: Option<EcoString> = None; pub const LANG: Option<EcoString> = None;
@ -53,9 +53,10 @@ impl ParNode {
Ok(Content::Block(args.expect("body")?)) Ok(Content::Block(args.expect("body")?))
} }
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> { fn set(args: &mut Args) -> TypResult<StyleMap> {
let lang = args.named::<Option<EcoString>>("lang")?; let mut styles = StyleMap::new();
let lang = args.named::<Option<EcoString>>("lang")?;
let mut dir = let mut dir =
lang.clone().flatten().map(|iso| match iso.to_lowercase().as_str() { lang.clone().flatten().map(|iso| match iso.to_lowercase().as_str() {
"ar" | "dv" | "fa" | "he" | "ks" | "pa" | "ps" | "sd" | "ug" | "ur" "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::SPACING, args.named("spacing")?);
styles.set_opt(Self::INDENT, args.named("indent")?); styles.set_opt(Self::INDENT, args.named("indent")?);
Ok(()) Ok(styles)
} }
} }
@ -183,7 +184,7 @@ impl Merge for ParChild {
/// A paragraph break. /// A paragraph break.
pub struct ParbreakNode; pub struct ParbreakNode;
#[class] #[node]
impl ParbreakNode { impl ParbreakNode {
fn construct(_: &mut Context, _: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, _: &mut Args) -> TypResult<Content> {
Ok(Content::Parbreak) Ok(Content::Parbreak)
@ -193,7 +194,7 @@ impl ParbreakNode {
/// A line break. /// A line break.
pub struct LinebreakNode; pub struct LinebreakNode;
#[class] #[node]
impl LinebreakNode { impl LinebreakNode {
fn construct(_: &mut Context, _: &mut Args) -> TypResult<Content> { fn construct(_: &mut Context, _: &mut Args) -> TypResult<Content> {
Ok(Content::Linebreak) Ok(Content::Linebreak)
@ -256,13 +257,13 @@ impl<'a> ParLayout<'a> {
ranges.push(subrange); ranges.push(subrange);
} }
} }
ParChild::Spacing(kind) => match *kind { ParChild::Spacing(spacing) => match *spacing {
SpacingKind::Linear(v) => { Spacing::Linear(v) => {
let resolved = v.resolve(regions.first.x); let resolved = v.resolve(regions.first.x);
items.push(ParItem::Absolute(resolved)); items.push(ParItem::Absolute(resolved));
ranges.push(range); ranges.push(range);
} }
SpacingKind::Fractional(v) => { Spacing::Fractional(v) => {
items.push(ParItem::Fractional(v)); items.push(ParItem::Fractional(v));
ranges.push(range); ranges.push(range);
} }

View File

@ -24,7 +24,7 @@ pub struct RawNode {
pub block: bool, pub block: bool,
} }
#[class] #[node(showable)]
impl RawNode { impl RawNode {
/// The language to syntax-highlight in. /// The language to syntax-highlight in.
pub const LANG: Option<EcoString> = None; pub const LANG: Option<EcoString> = None;

View File

@ -900,16 +900,14 @@ node! {
} }
impl SetExpr { impl SetExpr {
/// The class to set style properties for. /// The function to set style properties for.
pub fn class(&self) -> Ident { pub fn target(&self) -> Ident {
self.0.cast_first_child().expect("set expression is missing class") self.0.cast_first_child().expect("set rule is missing target")
} }
/// The style properties to set. /// The style properties to set.
pub fn args(&self) -> CallArgs { pub fn args(&self) -> CallArgs {
self.0 self.0.cast_last_child().expect("set rule is missing argument list")
.cast_first_child()
.expect("set expression is missing argument list")
} }
} }
@ -919,14 +917,14 @@ node! {
} }
impl ShowExpr { impl ShowExpr {
/// The class to set the show rule for. /// The function to customize with this show rule.
pub fn class(&self) -> Ident { pub fn target(&self) -> Ident {
self.0.cast_first_child().expect("show expression is missing class") self.0.cast_first_child().expect("show rule is missing target")
} }
/// The closure that defines the rule. /// The closure that defines the rule.
pub fn closure(&self) -> ClosureExpr { pub fn recipe(&self) -> ClosureExpr {
self.0.cast_first_child().expect("show expression is missing closure") self.0.cast_last_child().expect("show rule is missing closure")
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -34,15 +34,19 @@ Another text.
A [= Heading] C A [= Heading] C
--- ---
// Error: 1-22 unexpected argument // Error: 14-22 unexpected argument
#show heading() as [] #show heading() as []
= Heading = Heading
--- ---
// Error: 1-28 expected content, found string // Error: 14-28 expected content, found string
#show heading(_, _) as "hi" #show heading(_, _) as "hi"
= Heading = Heading
---
// Error: 7-12 this function cannot be customized with show
#show upper() as {}
--- ---
// Ref: false // Ref: false
// // Error: 1-29 show rule is recursive // // Error: 1-29 show rule is recursive

View File

@ -73,7 +73,7 @@ fn main() {
let mut std = typst::library::new(); let mut std = typst::library::new();
std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 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::<Value>("left-hand side")?; let lhs = args.expect::<Value>("left-hand side")?;
let rhs = args.expect::<Value>("right-hand side")?; let rhs = args.expect::<Value>("right-hand side")?;
if lhs != rhs { if lhs != rhs {