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::{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<TokenStream2> {
fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
// 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<TokenStream2> {
});
parse_quote! {
fn set(args: &mut Args, styles: &mut StyleMap) -> TypResult<()> {
fn set(args: &mut Args) -> TypResult<StyleMap> {
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<TokenStream2> {
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
}
};

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 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<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 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();

View File

@ -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<Value>,
) -> 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.
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<StyleMap> {
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,
/// The function pointer.
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 {
fn hash<H: Hasher>(&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<Content>;
/// Parse the arguments into style properties for this node.
fn set(args: &mut Args) -> TypResult<StyleMap>;
}
/// 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<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;
#[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<Self::Output> {
let class = self.class();
let class = class.eval(ctx, scp)?.cast::<Class>().at(class.span())?;
let target = self.target();
let target = target.eval(ctx, scp)?.cast::<Func>().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<Self::Output> {
let class = self.class();
let class = class.eval(ctx, scp)?.cast::<Class>().at(class.span())?;
let closure = self.closure();
let func = closure.eval(ctx, scp)?.cast::<Func>().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::<Func>().at(target_span)?;
let recipe = self.recipe();
let recipe_span = recipe.span();
let recipe = recipe.eval(ctx, scp)?.cast::<Func>().at(recipe_span)?;
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::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<Value>,
) {
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<T>(&mut self, name: &'static str)
where
T: Construct + Set + 'static,
{
self.def_const(name, Class::new::<T>(name));
/// Define a function through a native rust node.
pub fn def_node<T: Node>(&mut self, name: &'static str) {
self.def_const(name, Func::from_node::<T>(name));
}
/// 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.
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();
styles.set(key, value);
styles
}
/// 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));
}
/// 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 {
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<P: Property>(key: P, value: P::Value) -> Self {
fn new<K: Key>(key: K, value: K::Value) -> Self {
Self {
pair: Arc::new((key, value)),
scoped: false,
}
}
fn is<P: Property>(&self) -> bool {
fn is<P: Key>(&self) -> bool {
self.pair.style_id() == TypeId::of::<P>()
}
@ -192,7 +196,7 @@ impl Entry {
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()
}
@ -244,18 +248,18 @@ trait Bounds: Sync + Send + 'static {
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 {
&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::<P>() {
&& if let Some(other) = other.downcast::<K>() {
&self.1 == other
} else {
false
@ -270,11 +274,11 @@ impl<P: Property> Bounds for (P, P::Value) {
}
fn node_id(&self) -> TypeId {
P::node_id()
K::node_id()
}
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
/// entry for it.
pub fn get<P: Property>(self, key: P) -> P::Value
pub fn get<K: Key>(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<P: Property>(self, key: P) -> &'a P::Value
pub fn get_ref<K: Key>(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<P: Property>(self, key: P) -> P::Value {
if P::FOLDING {
pub fn get_cloned<K: Key>(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<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;
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::<P>() && (!entry.scoped || depth <= 1))
.filter_map(|entry| entry.downcast::<P>())
.filter(move |entry| entry.is::<K>() && (!entry.scoped || depth <= 1))
.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::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)),
"<function nil>",
);

View File

@ -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<Content> {
Ok(Content::inline(Self(args.expect("body")?)))

View File

@ -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;

View File

@ -19,7 +19,7 @@ pub type CircleNode = ShapeNode<CIRCLE>;
/// Place a node into an ellipse.
pub type EllipseNode = ShapeNode<ELLIPSE>;
#[class]
#[node]
impl<const S: ShapeKind> ShapeNode<S> {
/// How to fill the shape.
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.
pub type ScaleNode = TransformNode<SCALE>;
#[class]
#[node]
impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation.
pub const ORIGIN: Spec<Option<Align>> = Spec::default();

View File

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

View File

@ -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<Content> {
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.
pub struct BoxNode;
#[class]
#[node]
impl BoxNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
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<Content> {
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::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;
}

View File

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

View File

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

View File

@ -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<Length> = 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<StyleMap> {
let mut styles = StyleMap::new();
if let Some(paper) = args.named_or_find::<Paper>("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<Content> {
Ok(Content::Pagebreak)

View File

@ -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<Content> {
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(Align::Left)));

View File

@ -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<Content> {
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<Content> {
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<Length> for SpacingKind {
impl From<Length> 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()),

View File

@ -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<SpacingKind>,
pub spacing: Option<Spacing>,
/// The children to be stacked.
pub children: Vec<StackChild>,
}
#[class]
#[node]
impl StackNode {
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
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));
}

View File

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

View File

@ -18,77 +18,77 @@ pub fn new() -> Scope {
let mut std = Scope::new();
// Text.
std.def_class::<text::TextNode>("text");
std.def_class::<text::ParNode>("par");
std.def_class::<text::LinebreakNode>("linebreak");
std.def_class::<text::ParbreakNode>("parbreak");
std.def_class::<text::StrongNode>("strong");
std.def_class::<text::EmphNode>("emph");
std.def_class::<text::RawNode>("raw");
std.def_class::<text::UnderlineNode>("underline");
std.def_class::<text::StrikethroughNode>("strike");
std.def_class::<text::OverlineNode>("overline");
std.def_class::<text::LinkNode>("link");
std.def_node::<text::TextNode>("text");
std.def_node::<text::ParNode>("par");
std.def_node::<text::LinebreakNode>("linebreak");
std.def_node::<text::ParbreakNode>("parbreak");
std.def_node::<text::StrongNode>("strong");
std.def_node::<text::EmphNode>("emph");
std.def_node::<text::RawNode>("raw");
std.def_node::<text::UnderlineNode>("underline");
std.def_node::<text::StrikethroughNode>("strike");
std.def_node::<text::OverlineNode>("overline");
std.def_node::<text::LinkNode>("link");
// Structure.
std.def_class::<structure::HeadingNode>("heading");
std.def_class::<structure::ListNode>("list");
std.def_class::<structure::EnumNode>("enum");
std.def_class::<structure::TableNode>("table");
std.def_node::<structure::HeadingNode>("heading");
std.def_node::<structure::ListNode>("list");
std.def_node::<structure::EnumNode>("enum");
std.def_node::<structure::TableNode>("table");
// Layout.
std.def_class::<layout::PageNode>("page");
std.def_class::<layout::PagebreakNode>("pagebreak");
std.def_class::<layout::HNode>("h");
std.def_class::<layout::VNode>("v");
std.def_class::<layout::BoxNode>("box");
std.def_class::<layout::BlockNode>("block");
std.def_class::<layout::AlignNode>("align");
std.def_class::<layout::PadNode>("pad");
std.def_class::<layout::StackNode>("stack");
std.def_class::<layout::GridNode>("grid");
std.def_class::<layout::ColumnsNode>("columns");
std.def_class::<layout::ColbreakNode>("colbreak");
std.def_class::<layout::PlaceNode>("place");
std.def_node::<layout::PageNode>("page");
std.def_node::<layout::PagebreakNode>("pagebreak");
std.def_node::<layout::HNode>("h");
std.def_node::<layout::VNode>("v");
std.def_node::<layout::BoxNode>("box");
std.def_node::<layout::BlockNode>("block");
std.def_node::<layout::AlignNode>("align");
std.def_node::<layout::PadNode>("pad");
std.def_node::<layout::StackNode>("stack");
std.def_node::<layout::GridNode>("grid");
std.def_node::<layout::ColumnsNode>("columns");
std.def_node::<layout::ColbreakNode>("colbreak");
std.def_node::<layout::PlaceNode>("place");
// Graphics.
std.def_class::<graphics::ImageNode>("image");
std.def_class::<graphics::RectNode>("rect");
std.def_class::<graphics::SquareNode>("square");
std.def_class::<graphics::EllipseNode>("ellipse");
std.def_class::<graphics::CircleNode>("circle");
std.def_class::<graphics::MoveNode>("move");
std.def_class::<graphics::ScaleNode>("scale");
std.def_class::<graphics::RotateNode>("rotate");
std.def_class::<graphics::HideNode>("hide");
std.def_node::<graphics::ImageNode>("image");
std.def_node::<graphics::RectNode>("rect");
std.def_node::<graphics::SquareNode>("square");
std.def_node::<graphics::EllipseNode>("ellipse");
std.def_node::<graphics::CircleNode>("circle");
std.def_node::<graphics::MoveNode>("move");
std.def_node::<graphics::ScaleNode>("scale");
std.def_node::<graphics::RotateNode>("rotate");
std.def_node::<graphics::HideNode>("hide");
// Math.
std.def_class::<math::MathNode>("math");
std.def_node::<math::MathNode>("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);

View File

@ -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::*;

View File

@ -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<Smart<FontFamily>> = Leveled::Value(Smart::Auto);

View File

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

View File

@ -12,7 +12,7 @@ pub struct TableNode {
pub children: Vec<Content>,
}
#[class]
#[node(showable)]
impl TableNode {
/// The primary cell fill color.
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")?;
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)
}
}

View File

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

View File

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

View File

@ -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<Content> {
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<Content> {
Ok(Content::show(Self(args.expect("body")?)))

View File

@ -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<EcoString> = 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::<Option<EcoString>>("lang")?;
fn set(args: &mut Args) -> TypResult<StyleMap> {
let mut styles = StyleMap::new();
let lang = args.named::<Option<EcoString>>("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<Content> {
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<Content> {
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);
}

View File

@ -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<EcoString> = None;

View File

@ -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")
}
}

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
---
// 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

View File

@ -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::<Value>("left-hand side")?;
let rhs = args.expect::<Value>("right-hand side")?;
if lhs != rhs {