diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs index 0033f021d..80f93e8d1 100644 --- a/library/src/basics/heading.rs +++ b/library/src/basics/heading.rs @@ -6,6 +6,8 @@ use crate::prelude::*; use crate::text::{SpaceNode, TextNode, TextSize}; /// A section heading. +#[func] +#[capable(Prepare, Show, Finalize)] #[derive(Debug, Hash)] pub struct HeadingNode { /// The logical nesting depth of the section, starting from one. In the @@ -15,7 +17,7 @@ pub struct HeadingNode { pub body: Content, } -#[node(Prepare, Show, Finalize)] +#[node] impl HeadingNode { /// How to number the heading. #[property(referenced)] diff --git a/library/src/basics/list.rs b/library/src/basics/list.rs index bcd8ef1c7..0fa8f125b 100644 --- a/library/src/basics/list.rs +++ b/library/src/basics/list.rs @@ -4,6 +4,8 @@ use crate::prelude::*; use crate::text::{SpaceNode, TextNode}; /// An unordered (bulleted) or ordered (numbered) list. +#[func] +#[capable(Layout)] #[derive(Debug, Hash)] pub struct ListNode { /// If true, the items are separated by leading instead of list spacing. @@ -18,7 +20,7 @@ pub type EnumNode = ListNode; /// A description list. pub type DescNode = ListNode; -#[node(Layout)] +#[node] impl ListNode { /// How the list is labelled. #[property(referenced)] @@ -143,6 +145,7 @@ impl Layout for ListNode { } /// An item in a list. +#[capable] #[derive(Debug, Clone, Hash)] pub enum ListItem { /// An item of an unordered list. diff --git a/library/src/basics/table.rs b/library/src/basics/table.rs index 448ad1bd2..5a4e8e818 100644 --- a/library/src/basics/table.rs +++ b/library/src/basics/table.rs @@ -2,6 +2,8 @@ use crate::layout::{GridNode, TrackSizing, TrackSizings}; use crate::prelude::*; /// A table of items. +#[func] +#[capable(Layout)] #[derive(Debug, Hash)] pub struct TableNode { /// Defines sizing for content rows and columns. @@ -12,7 +14,7 @@ pub struct TableNode { pub cells: Vec, } -#[node(Layout)] +#[node] impl TableNode { /// How to fill the cells. #[property(referenced)] diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs index 3541e08cd..71e43e215 100644 --- a/library/src/compute/calc.rs +++ b/library/src/compute/calc.rs @@ -3,7 +3,8 @@ use std::cmp::Ordering; use crate::prelude::*; /// The absolute value of a numeric value. -pub fn abs(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn abs(args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("numeric value")?; Ok(match v { Value::Int(v) => Value::Int(v.abs()), @@ -19,12 +20,14 @@ pub fn abs(_: &Vm, args: &mut Args) -> SourceResult { } /// The minimum of a sequence of values. -pub fn min(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn min(args: &mut Args) -> SourceResult { minmax(args, Ordering::Less) } /// The maximum of a sequence of values. -pub fn max(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn max(args: &mut Args) -> SourceResult { minmax(args, Ordering::Greater) } @@ -50,17 +53,20 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult { } /// Whether an integer is even. -pub fn even(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn even(args: &mut Args) -> SourceResult { Ok(Value::Bool(args.expect::("integer")? % 2 == 0)) } /// Whether an integer is odd. -pub fn odd(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn odd(args: &mut Args) -> SourceResult { Ok(Value::Bool(args.expect::("integer")? % 2 != 0)) } /// The modulo of two numbers. -pub fn mod_(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn mod_(args: &mut Args) -> SourceResult { let Spanned { v: v1, span: span1 } = args.expect("integer or float")?; let Spanned { v: v2, span: span2 } = args.expect("integer or float")?; diff --git a/library/src/compute/create.rs b/library/src/compute/create.rs index 4fd27499c..acd2e31f8 100644 --- a/library/src/compute/create.rs +++ b/library/src/compute/create.rs @@ -5,7 +5,8 @@ use typst::model::Regex; use crate::prelude::*; /// Convert a value to an integer. -pub fn int(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn int(args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("value")?; Ok(Value::Int(match v { Value::Bool(v) => v as i64, @@ -20,7 +21,8 @@ pub fn int(_: &Vm, args: &mut Args) -> SourceResult { } /// Convert a value to a float. -pub fn float(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn float(args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("value")?; Ok(Value::Float(match v { Value::Int(v) => v as f64, @@ -34,13 +36,15 @@ pub fn float(_: &Vm, args: &mut Args) -> SourceResult { } /// Create a grayscale color. -pub fn luma(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn luma(args: &mut Args) -> SourceResult { let Component(luma) = args.expect("gray component")?; Ok(Value::Color(LumaColor::new(luma).into())) } /// Create an RGB(A) color. -pub fn rgb(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn rgb(args: &mut Args) -> SourceResult { Ok(Value::Color(if let Some(string) = args.find::>()? { match RgbaColor::from_str(&string.v) { Ok(color) => color.into(), @@ -56,7 +60,8 @@ pub fn rgb(_: &Vm, args: &mut Args) -> SourceResult { } /// Create a CMYK color. -pub fn cmyk(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn cmyk(args: &mut Args) -> SourceResult { let RatioComponent(c) = args.expect("cyan component")?; let RatioComponent(m) = args.expect("magenta component")?; let RatioComponent(y) = args.expect("yellow component")?; @@ -95,7 +100,8 @@ castable! { } /// Convert a value to a string. -pub fn str(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn str(args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("value")?; Ok(Value::Str(match v { Value::Int(v) => format_str!("{}", v), @@ -107,18 +113,21 @@ pub fn str(_: &Vm, args: &mut Args) -> SourceResult { } /// Create a label from a string. -pub fn label(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn label(args: &mut Args) -> SourceResult { Ok(Value::Label(Label(args.expect("string")?))) } /// Create a regular expression from a string. -pub fn regex(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn regex(args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect::>("regular expression")?; Ok(Regex::new(&v).at(span)?.into()) } /// Create an array consisting of a sequence of numbers. -pub fn range(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn range(args: &mut Args) -> SourceResult { let first = args.expect::("end")?; let (start, end) = match args.eat::()? { Some(second) => (first, second), diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index 4f6e3b67e..57dce5c19 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -5,6 +5,7 @@ use typst::diag::{format_xml_like_error, FileError}; use crate::prelude::*; /// Read structured data from a CSV file. +#[func] pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult { let Spanned { v: path, span } = args.expect::>("path to csv file")?; @@ -45,6 +46,7 @@ fn format_csv_error(error: csv::Error) -> String { } /// Read structured data from a JSON file. +#[func] pub fn json(vm: &Vm, args: &mut Args) -> SourceResult { let Spanned { v: path, span } = args.expect::>("path to json file")?; @@ -85,6 +87,7 @@ fn format_json_error(error: serde_json::Error) -> String { } /// Read structured data from an XML file. +#[func] pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult { let Spanned { v: path, span } = args.expect::>("path to xml file")?; diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 5134d4ac9..abe797dc3 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -5,17 +5,20 @@ use typst::model; use typst::syntax::Source; /// The name of a value's type. -pub fn type_(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn type_(args: &mut Args) -> SourceResult { Ok(args.expect::("value")?.type_name().into()) } /// The string representation of a value. -pub fn repr(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn repr(args: &mut Args) -> SourceResult { Ok(args.expect::("value")?.repr().into()) } /// Ensure that a condition is fulfilled. -pub fn assert(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn assert(args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect::>("condition")?; if !v { bail!(span, "assertion failed"); @@ -24,6 +27,7 @@ pub fn assert(_: &Vm, args: &mut Args) -> SourceResult { } /// Evaluate a string as Typst markup. +#[func] pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult { let Spanned { v: text, span } = args.expect::>("source")?; let source = Source::synthesized(text, span); diff --git a/library/src/compute/utility.rs b/library/src/compute/utility.rs index 196f83685..d48f794ea 100644 --- a/library/src/compute/utility.rs +++ b/library/src/compute/utility.rs @@ -4,13 +4,15 @@ use crate::prelude::*; use crate::text::Case; /// Create a blind text string. -pub fn lorem(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn lorem(args: &mut Args) -> SourceResult { let words: usize = args.expect("number of words")?; Ok(Value::Str(lipsum::lipsum(words).into())) } /// Apply a numbering pattern to a number. -pub fn numbering(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn numbering(args: &mut Args) -> SourceResult { let pattern = args.expect::("pattern")?; let numbers = args.all::()?; Ok(Value::Str(pattern.apply(&numbers).into())) diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs index 506ef6846..4fae3c3cd 100644 --- a/library/src/layout/align.rs +++ b/library/src/layout/align.rs @@ -1,6 +1,8 @@ use crate::prelude::*; -/// Just an empty shell to scope styles. +/// Align content horizontally and vertically. +#[func] +#[capable] #[derive(Debug, Hash)] pub enum AlignNode {} diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index 28576fdd9..0e29bc00e 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -2,6 +2,8 @@ use crate::prelude::*; use crate::text::TextNode; /// Separate a region into multiple equally sized columns. +#[func] +#[capable(Layout)] #[derive(Debug, Hash)] pub struct ColumnsNode { /// How many columns there should be. @@ -11,7 +13,7 @@ pub struct ColumnsNode { pub body: Content, } -#[node(Layout)] +#[node] impl ColumnsNode { /// The size of the gutter space between each column. #[property(resolve)] @@ -101,12 +103,14 @@ impl Layout for ColumnsNode { } /// A column break. +#[func] +#[capable(Behave)] #[derive(Debug, Hash)] pub struct ColbreakNode { pub weak: bool, } -#[node(Behave)] +#[node] impl ColbreakNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let weak = args.named("weak")?.unwrap_or(false); diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index 0b0352732..852577156 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -2,6 +2,8 @@ use super::VNode; use crate::prelude::*; /// An inline-level container that sizes content. +#[func] +#[capable(Layout, Inline)] #[derive(Debug, Hash)] pub struct BoxNode { /// How to size the content horizontally and vertically. @@ -10,7 +12,7 @@ pub struct BoxNode { pub body: Content, } -#[node(Layout, Inline)] +#[node] impl BoxNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let width = args.named("width")?; @@ -60,10 +62,12 @@ impl Layout for BoxNode { impl Inline for BoxNode {} /// A block-level container that places content into a separate flow. +#[func] +#[capable(Layout)] #[derive(Debug, Hash)] pub struct BlockNode(pub Content); -#[node(Layout)] +#[node] impl BlockNode { /// The spacing between the previous and this block. #[property(skip)] diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index b78f3932d..611e9ee13 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -7,15 +7,12 @@ use crate::prelude::*; /// /// This node is reponsible for layouting both the top-level content flow and /// the contents of boxes. +#[capable(Layout)] #[derive(Hash)] pub struct FlowNode(pub StyleVec, pub bool); -#[node(Layout)] -impl FlowNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - Ok(BlockNode(args.expect("body")?).pack()) - } -} +#[node] +impl FlowNode {} impl Layout for FlowNode { fn layout( diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 9ae780f1c..2a6bd4ff3 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -3,6 +3,8 @@ use crate::prelude::*; use super::Spacing; /// Arrange content in a grid. +#[func] +#[capable(Layout)] #[derive(Debug, Hash)] pub struct GridNode { /// Defines sizing for content rows and columns. @@ -13,7 +15,7 @@ pub struct GridNode { pub cells: Vec, } -#[node(Layout)] +#[node] impl GridNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let TrackSizings(columns) = args.named("columns")?.unwrap_or_default(); diff --git a/library/src/layout/hide.rs b/library/src/layout/hide.rs index f6ce21d58..1318b7ed8 100644 --- a/library/src/layout/hide.rs +++ b/library/src/layout/hide.rs @@ -1,10 +1,12 @@ use crate::prelude::*; /// Hide content without affecting layout. +#[func] +#[capable(Layout, Inline)] #[derive(Debug, Hash)] pub struct HideNode(pub Content); -#[node(Layout, Inline)] +#[node] impl HideNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index 7ae738ac1..9c44919d1 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -1,6 +1,8 @@ use crate::prelude::*; /// Pad content at the sides. +#[func] +#[capable(Layout)] #[derive(Debug, Hash)] pub struct PadNode { /// The amount of padding. @@ -9,7 +11,7 @@ pub struct PadNode { pub body: Content, } -#[node(Layout)] +#[node] impl PadNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let all = args.named("rest")?.or(args.find()?); diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index 8ea1eed61..5a23b27b6 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -5,6 +5,8 @@ use crate::prelude::*; use crate::text::TextNode; /// Layouts its child onto one or multiple pages. +#[func] +#[capable] #[derive(Clone, Hash)] pub struct PageNode(pub Content); @@ -143,6 +145,8 @@ impl Debug for PageNode { } /// A page break. +#[func] +#[capable] #[derive(Debug, Copy, Clone, Hash)] pub struct PagebreakNode { pub weak: bool, diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index d93bfba73..925eea542 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -12,6 +12,8 @@ use crate::text::{ }; /// Arrange text, spacing and inline-level nodes into a paragraph. +#[func] +#[capable] #[derive(Hash)] pub struct ParNode(pub StyleVec); @@ -142,10 +144,12 @@ castable! { } /// A paragraph break. +#[func] +#[capable(Unlabellable)] #[derive(Debug, Hash)] pub struct ParbreakNode; -#[node(Unlabellable)] +#[node] impl ParbreakNode { fn construct(_: &Vm, _: &mut Args) -> SourceResult { Ok(Self.pack()) diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index 28d231b73..4c9c0a46f 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -1,10 +1,12 @@ use crate::prelude::*; /// Place content at an absolute position. +#[func] +#[capable(Layout, Behave)] #[derive(Debug, Hash)] pub struct PlaceNode(pub Content, bool); -#[node(Layout, Behave)] +#[node] impl PlaceNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start))); diff --git a/library/src/layout/repeat.rs b/library/src/layout/repeat.rs index 6e0ce39ff..196f19de7 100644 --- a/library/src/layout/repeat.rs +++ b/library/src/layout/repeat.rs @@ -1,10 +1,12 @@ use crate::prelude::*; /// Repeats content to fill a line. +#[func] +#[capable(Layout, Inline)] #[derive(Debug, Hash)] pub struct RepeatNode(pub Content); -#[node(Layout, Inline)] +#[node] impl RepeatNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index f5c39af45..91e45b032 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -3,6 +3,8 @@ use std::cmp::Ordering; use crate::prelude::*; /// Horizontal spacing. +#[func] +#[capable(Behave)] #[derive(Debug, Copy, Clone, Hash)] pub struct HNode { /// The amount of horizontal spacing. @@ -11,7 +13,7 @@ pub struct HNode { pub weak: bool, } -#[node(Behave)] +#[node] impl HNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let amount = args.expect("spacing")?; @@ -50,6 +52,8 @@ impl Behave for HNode { } /// Vertical spacing. +#[func] +#[capable(Behave)] #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)] pub struct VNode { /// The amount of vertical spacing. @@ -58,7 +62,7 @@ pub struct VNode { pub weakness: u8, } -#[node(Behave)] +#[node] impl VNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let amount = args.expect("spacing")?; diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index d44dcb482..1e956669c 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -4,6 +4,8 @@ use super::{AlignNode, Spacing}; use crate::prelude::*; /// Arrange content and spacing along an axis. +#[func] +#[capable(Layout)] #[derive(Debug, Hash)] pub struct StackNode { /// The stacking direction. @@ -14,7 +16,7 @@ pub struct StackNode { pub children: Vec, } -#[node(Layout)] +#[node] impl StackNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self { diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs index 8bf465a9b..35b6709a0 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -3,6 +3,8 @@ use typst::geom::Transform; use crate::prelude::*; /// Move content without affecting layout. +#[func] +#[capable(Layout, Inline)] #[derive(Debug, Hash)] pub struct MoveNode { /// The offset by which to move the content. @@ -11,7 +13,7 @@ pub struct MoveNode { pub body: Content, } -#[node(Layout, Inline)] +#[node] impl MoveNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let dx = args.named("dx")?.unwrap_or_default(); @@ -44,6 +46,8 @@ impl Layout for MoveNode { impl Inline for MoveNode {} /// Transform content without affecting layout. +#[func] +#[capable(Layout, Inline)] #[derive(Debug, Hash)] pub struct TransformNode { /// Transformation to apply to the content. @@ -58,7 +62,7 @@ pub type RotateNode = TransformNode; /// Scale content without affecting layout. pub type ScaleNode = TransformNode; -#[node(Layout, Inline)] +#[node] impl TransformNode { /// The origin of the transformation. #[property(resolve)] diff --git a/library/src/lib.rs b/library/src/lib.rs index 86bef0b94..2d03ef7c8 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -25,111 +25,110 @@ fn scope() -> Scope { let mut std = Scope::new(); // Basics. - std.def_node::("heading"); - std.def_node::("list"); - std.def_node::("enum"); - std.def_node::("desc"); - std.def_node::("table"); + std.def_func::("heading"); + std.def_func::("list"); + std.def_func::("enum"); + std.def_func::("desc"); + std.def_func::("table"); // Text. - std.def_node::("text"); - std.def_node::("linebreak"); - std.def_node::("symbol"); - std.def_node::("smartquote"); - std.def_node::("strong"); - std.def_node::("emph"); - std.def_fn("lower", text::lower); - std.def_fn("upper", text::upper); - std.def_fn("smallcaps", text::smallcaps); - std.def_node::("sub"); - std.def_node::("super"); - std.def_node::("underline"); - std.def_node::("strike"); - std.def_node::("overline"); - std.def_node::("raw"); + std.def_func::("text"); + std.def_func::("linebreak"); + std.def_func::("symbol"); + std.def_func::("smartquote"); + std.def_func::("strong"); + std.def_func::("emph"); + std.def_func::("lower"); + std.def_func::("upper"); + std.def_func::("smallcaps"); + std.def_func::("sub"); + std.def_func::("super"); + std.def_func::("underline"); + std.def_func::("strike"); + std.def_func::("overline"); + std.def_func::("raw"); // Math. - std.def_node::("math"); - std.def_node::("atom"); - std.def_node::("acc"); - std.def_node::("frac"); - std.def_node::("binom"); - std.def_node::("sqrt"); - std.def_node::("floor"); - std.def_node::("ceil"); - std.def_node::("vec"); - std.def_node::("cases"); - std.def_node::("serif"); - std.def_node::("sans"); - std.def_node::("bold"); - std.def_node::("ital"); - std.def_node::("cal"); - std.def_node::("frak"); - std.def_node::("mono"); - std.def_node::("bb"); + std.def_func::("math"); + std.def_func::("atom"); + std.def_func::("acc"); + std.def_func::("frac"); + std.def_func::("binom"); + std.def_func::("sqrt"); + std.def_func::("floor"); + std.def_func::("ceil"); + std.def_func::("vec"); + std.def_func::("cases"); + std.def_func::("serif"); + std.def_func::("sans"); + std.def_func::("bold"); + std.def_func::("ital"); + std.def_func::("cal"); + std.def_func::("frak"); + std.def_func::("mono"); + std.def_func::("bb"); // Layout. - std.def_node::("page"); - std.def_node::("pagebreak"); - std.def_node::("flow"); - std.def_node::("v"); - std.def_node::("par"); - std.def_node::("parbreak"); - std.def_node::("h"); - std.def_node::("box"); - std.def_node::("block"); - std.def_node::("stack"); - std.def_node::("grid"); - std.def_node::("columns"); - std.def_node::("colbreak"); - std.def_node::("place"); - std.def_node::("align"); - std.def_node::("pad"); - std.def_node::("repeat"); - std.def_node::("move"); - std.def_node::("scale"); - std.def_node::("rotate"); - std.def_node::("hide"); + std.def_func::("page"); + std.def_func::("pagebreak"); + std.def_func::("v"); + std.def_func::("par"); + std.def_func::("parbreak"); + std.def_func::("h"); + std.def_func::("box"); + std.def_func::("block"); + std.def_func::("stack"); + std.def_func::("grid"); + std.def_func::("columns"); + std.def_func::("colbreak"); + std.def_func::("place"); + std.def_func::("align"); + std.def_func::("pad"); + std.def_func::("repeat"); + std.def_func::("move"); + std.def_func::("scale"); + std.def_func::("rotate"); + std.def_func::("hide"); // Visualize. - std.def_node::("image"); - std.def_node::("line"); - std.def_node::("rect"); - std.def_node::("square"); - std.def_node::("ellipse"); - std.def_node::("circle"); + std.def_func::("image"); + std.def_func::("line"); + std.def_func::("rect"); + std.def_func::("square"); + std.def_func::("ellipse"); + std.def_func::("circle"); // Meta. - std.def_node::("document"); - std.def_node::("ref"); - std.def_node::("link"); - std.def_node::("outline"); + std.def_func::("document"); + std.def_func::("ref"); + std.def_func::("link"); + std.def_func::("outline"); // Compute. - std.def_fn("type", compute::type_); - std.def_fn("repr", compute::repr); - std.def_fn("assert", compute::assert); - std.def_fn("eval", compute::eval); - std.def_fn("int", compute::int); - std.def_fn("float", compute::float); - std.def_fn("luma", compute::luma); - std.def_fn("rgb", compute::rgb); - std.def_fn("cmyk", compute::cmyk); - std.def_fn("str", compute::str); - std.def_fn("label", compute::label); - std.def_fn("regex", compute::regex); - std.def_fn("range", compute::range); - std.def_fn("abs", compute::abs); - std.def_fn("min", compute::min); - std.def_fn("max", compute::max); - std.def_fn("even", compute::even); - std.def_fn("odd", compute::odd); - std.def_fn("mod", compute::mod_); - std.def_fn("csv", compute::csv); - std.def_fn("json", compute::json); - std.def_fn("xml", compute::xml); - std.def_fn("lorem", compute::lorem); - std.def_fn("numbering", compute::numbering); + std.def_func::("type"); + std.def_func::("repr"); + std.def_func::("assert"); + std.def_func::("eval"); + std.def_func::("int"); + std.def_func::("float"); + std.def_func::("luma"); + std.def_func::("rgb"); + std.def_func::("cmyk"); + std.def_func::("str"); + std.def_func::("label"); + std.def_func::("regex"); + std.def_func::("range"); + std.def_func::("abs"); + std.def_func::("min"); + std.def_func::("max"); + std.def_func::("even"); + std.def_func::("odd"); + std.def_func::("mod"); + std.def_func::("csv"); + std.def_func::("json"); + std.def_func::("xml"); + std.def_func::("lorem"); + std.def_func::("numbering"); // Colors. std.define("black", Color::BLACK); diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index bc5e542a5..2d32f4b58 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -1,10 +1,12 @@ use super::*; /// A column vector. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct VecNode(Vec); -#[node(Texify)] +#[node] impl VecNode { /// The kind of delimiter. pub const DELIM: Delimiter = Delimiter::Paren; @@ -61,10 +63,12 @@ castable! { } /// A case distinction. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct CasesNode(Vec); -#[node(Texify)] +#[node] impl CasesNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.all()?).pack()) diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 317bc1d4e..59c621e8f 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -15,6 +15,8 @@ use crate::prelude::*; use crate::text::{FontFamily, LinebreakNode, SpaceNode, SymbolNode, TextNode}; /// A piece of a mathematical formula. +#[func] +#[capable(Show, Layout, Inline, Texify)] #[derive(Debug, Clone, Hash)] pub struct MathNode { /// Whether the formula is display-level. @@ -23,7 +25,7 @@ pub struct MathNode { pub children: Vec, } -#[node(Show, Layout, Inline, Texify)] +#[node] impl MathNode { fn field(&self, name: &str) -> Option { match name { @@ -242,10 +244,12 @@ impl Texify for Content { } /// An atom in a math formula: `x`, `+`, `12`. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct AtomNode(pub EcoString); -#[node(Texify)] +#[node] impl AtomNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("text")?).pack()) @@ -279,6 +283,8 @@ impl Texify for AtomNode { } /// An accented node. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct AccNode { /// The accent base. @@ -287,7 +293,7 @@ pub struct AccNode { pub accent: char, } -#[node(Texify)] +#[node] impl AccNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let base = args.expect("base")?; @@ -352,6 +358,8 @@ impl Texify for AccNode { } /// A fraction. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct FracNode { /// The numerator. @@ -360,7 +368,7 @@ pub struct FracNode { pub denom: Content, } -#[node(Texify)] +#[node] impl FracNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let num = args.expect("numerator")?; @@ -381,6 +389,8 @@ impl Texify for FracNode { } /// A binomial. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct BinomNode { /// The upper index. @@ -389,7 +399,7 @@ pub struct BinomNode { pub lower: Content, } -#[node(Texify)] +#[node] impl BinomNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let upper = args.expect("upper index")?; @@ -410,6 +420,8 @@ impl Texify for BinomNode { } /// A sub- and/or superscript. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct ScriptNode { /// The base. @@ -420,7 +432,7 @@ pub struct ScriptNode { pub sup: Option, } -#[node(Texify)] +#[node] impl ScriptNode {} impl Texify for ScriptNode { @@ -444,10 +456,12 @@ impl Texify for ScriptNode { } /// A math alignment point: `&`, `&&`. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct AlignPointNode(pub usize); -#[node(Texify)] +#[node] impl AlignPointNode {} impl Texify for AlignPointNode { @@ -457,10 +471,12 @@ impl Texify for AlignPointNode { } /// A square root. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct SqrtNode(pub Content); -#[node(Texify)] +#[node] impl SqrtNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -477,10 +493,12 @@ impl Texify for SqrtNode { } /// A floored expression. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct FloorNode(pub Content); -#[node(Texify)] +#[node] impl FloorNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -497,10 +515,12 @@ impl Texify for FloorNode { } /// A ceiled expression. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct CeilNode(pub Content); -#[node(Texify)] +#[node] impl CeilNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) diff --git a/library/src/math/style.rs b/library/src/math/style.rs index 9e81a549f..0fdff740b 100644 --- a/library/src/math/style.rs +++ b/library/src/math/style.rs @@ -1,10 +1,12 @@ use super::*; /// Serif (roman) font style. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct SerifNode(pub Content); -#[node(Texify)] +#[node] impl SerifNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -21,10 +23,12 @@ impl Texify for SerifNode { } /// Sans-serif font style. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct SansNode(pub Content); -#[node(Texify)] +#[node] impl SansNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -41,10 +45,12 @@ impl Texify for SansNode { } /// Bold font style. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct BoldNode(pub Content); -#[node(Texify)] +#[node] impl BoldNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -61,10 +67,12 @@ impl Texify for BoldNode { } /// Italic font style. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct ItalNode(pub Content); -#[node(Texify)] +#[node] impl ItalNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -81,10 +89,12 @@ impl Texify for ItalNode { } /// Calligraphic font style. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct CalNode(pub Content); -#[node(Texify)] +#[node] impl CalNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -101,10 +111,12 @@ impl Texify for CalNode { } /// Fraktur font style. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct FrakNode(pub Content); -#[node(Texify)] +#[node] impl FrakNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -121,10 +133,12 @@ impl Texify for FrakNode { } /// Monospace font style. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct MonoNode(pub Content); -#[node(Texify)] +#[node] impl MonoNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -141,10 +155,12 @@ impl Texify for MonoNode { } /// Blackboard bold (double-struck) font style. +#[func] +#[capable(Texify)] #[derive(Debug, Hash)] pub struct BbNode(pub Content); -#[node(Texify)] +#[node] impl BbNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) diff --git a/library/src/meta/document.rs b/library/src/meta/document.rs index fbb62b6b5..1dae4a2a3 100644 --- a/library/src/meta/document.rs +++ b/library/src/meta/document.rs @@ -2,10 +2,12 @@ use crate::layout::{LayoutRoot, PageNode}; use crate::prelude::*; /// The root node that represents a full document. +#[func] +#[capable(LayoutRoot)] #[derive(Hash)] pub struct DocumentNode(pub StyleVec); -#[node(LayoutRoot)] +#[node] impl DocumentNode { /// The document's title. #[property(referenced)] diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index 34304ea9c..94328b00a 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -2,6 +2,8 @@ use crate::prelude::*; use crate::text::TextNode; /// Link text and other elements to a destination. +#[func] +#[capable(Show, Finalize)] #[derive(Debug, Hash)] pub struct LinkNode { /// The destination the link points to. @@ -23,7 +25,7 @@ impl LinkNode { } } -#[node(Show, Finalize)] +#[node] impl LinkNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let dest = args.expect::("destination")?; diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs index 53535be59..d0fbc3f71 100644 --- a/library/src/meta/outline.rs +++ b/library/src/meta/outline.rs @@ -4,10 +4,12 @@ use crate::prelude::*; use crate::text::{LinebreakNode, SpaceNode, TextNode}; /// A section outline (table of contents). +#[func] +#[capable(Prepare, Show)] #[derive(Debug, Hash)] pub struct OutlineNode; -#[node(Prepare, Show)] +#[node] impl OutlineNode { /// The title of the outline. #[property(referenced)] diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs index a04fd13f1..657e5ef7c 100644 --- a/library/src/meta/reference.rs +++ b/library/src/meta/reference.rs @@ -2,10 +2,12 @@ use crate::prelude::*; use crate::text::TextNode; /// A reference to a label. +#[func] +#[capable(Show)] #[derive(Debug, Hash)] pub struct RefNode(pub EcoString); -#[node(Show)] +#[node] impl RefNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("target")?).pack()) diff --git a/library/src/prelude.rs b/library/src/prelude.rs index 26a3c6c32..9b461389a 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -15,10 +15,10 @@ pub use typst::doc::*; pub use typst::geom::*; #[doc(no_inline)] pub use typst::model::{ - array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, - Content, Dict, Finalize, Fold, Func, Introspector, Label, Node, NodeId, Prepare, - Resolve, Selector, Show, Smart, StabilityProvider, Str, StyleChain, StyleMap, - StyleVec, Unlabellable, Value, Vm, Vt, + array, capability, capable, castable, dict, dynamic, format_str, func, node, Args, + Array, Cast, Content, Dict, Finalize, Fold, Func, Introspector, Label, Node, NodeId, + Prepare, Resolve, Selector, Show, Smart, StabilityProvider, Str, StyleChain, + StyleMap, StyleVec, Unlabellable, Value, Vm, Vt, }; #[doc(no_inline)] pub use typst::syntax::{Span, Spanned}; diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs index 23db71f91..ba3579e7e 100644 --- a/library/src/shared/ext.rs +++ b/library/src/shared/ext.rs @@ -98,6 +98,7 @@ impl StyleMapExt for StyleMap { } /// Fill the frames resulting from content. +#[capable(Layout)] #[derive(Debug, Hash)] struct FillNode { /// How to fill the frames resulting from the `child`. @@ -106,7 +107,7 @@ struct FillNode { child: Content, } -#[node(Layout)] +#[node] impl FillNode {} impl Layout for FillNode { @@ -126,6 +127,7 @@ impl Layout for FillNode { } /// Stroke the frames resulting from content. +#[capable(Layout)] #[derive(Debug, Hash)] struct StrokeNode { /// How to stroke the frames resulting from the `child`. @@ -134,7 +136,7 @@ struct StrokeNode { child: Content, } -#[node(Layout)] +#[node] impl StrokeNode {} impl Layout for StrokeNode { diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index a6fa490f4..fceb4cfdf 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -5,6 +5,8 @@ use super::TextNode; use crate::prelude::*; /// Typeset underline, stricken-through or overlined text. +#[func] +#[capable(Show)] #[derive(Debug, Hash)] pub struct DecoNode(pub Content); @@ -17,7 +19,7 @@ pub type StrikeNode = DecoNode; /// Typeset overlined text. pub type OverlineNode = DecoNode; -#[node(Show)] +#[node] impl DecoNode { /// How to stroke the line. The text color and thickness are read from the /// font tables if `auto`. diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index 896c03ac6..1c5a32b45 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -2,10 +2,12 @@ use super::TextNode; use crate::prelude::*; /// A text space. +#[func] +#[capable(Unlabellable, Behave)] #[derive(Debug, Hash)] pub struct SpaceNode; -#[node(Unlabellable, Behave)] +#[node] impl SpaceNode { fn construct(_: &Vm, _: &mut Args) -> SourceResult { Ok(Self.pack()) @@ -21,12 +23,14 @@ impl Behave for SpaceNode { } /// A line break. +#[func] +#[capable(Behave)] #[derive(Debug, Hash)] pub struct LinebreakNode { pub justify: bool, } -#[node(Behave)] +#[node] impl LinebreakNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let justify = args.named("justify")?.unwrap_or(false); @@ -41,10 +45,12 @@ impl Behave for LinebreakNode { } /// Strongly emphasizes content by increasing the font weight. +#[func] +#[capable(Show)] #[derive(Debug, Hash)] pub struct StrongNode(pub Content); -#[node(Show)] +#[node] impl StrongNode { /// The delta to apply on the font weight. pub const DELTA: i64 = 300; @@ -86,10 +92,12 @@ impl Fold for Delta { } /// Emphasizes content by flipping the italicness. +#[func] +#[capable(Show)] #[derive(Debug, Hash)] pub struct EmphNode(pub Content); -#[node(Show)] +#[node] impl EmphNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) @@ -122,12 +130,14 @@ impl Fold for Toggle { } /// Convert a string or content to lowercase. -pub fn lower(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn lower(args: &mut Args) -> SourceResult { case(Case::Lower, args) } /// Convert a string or content to uppercase. -pub fn upper(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn upper(args: &mut Args) -> SourceResult { case(Case::Upper, args) } @@ -161,7 +171,8 @@ impl Case { } /// Display text in small capitals. -pub fn smallcaps(_: &Vm, args: &mut Args) -> SourceResult { +#[func] +pub fn smallcaps(args: &mut Args) -> SourceResult { let body: Content = args.expect("content")?; Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true))) } diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 5466637e2..d09d8f28e 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -26,6 +26,8 @@ use crate::layout::ParNode; use crate::prelude::*; /// A single run of text with the same style. +#[func] +#[capable] #[derive(Clone, Hash)] pub struct TextNode(pub EcoString); diff --git a/library/src/text/quotes.rs b/library/src/text/quotes.rs index 95cf1ad9b..0f678de32 100644 --- a/library/src/text/quotes.rs +++ b/library/src/text/quotes.rs @@ -3,6 +3,8 @@ use typst::syntax::is_newline; use crate::prelude::*; /// A smart quote. +#[func] +#[capable] #[derive(Debug, Hash)] pub struct SmartQuoteNode { pub double: bool, diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 21a0531af..4ad70654d 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -7,6 +7,8 @@ use crate::layout::BlockNode; use crate::prelude::*; /// Raw text with optional syntax highlighting. +#[func] +#[capable(Show)] #[derive(Debug, Hash)] pub struct RawNode { /// The raw text. @@ -15,7 +17,7 @@ pub struct RawNode { pub block: bool, } -#[node(Show)] +#[node] impl RawNode { /// The language to syntax-highlight in. #[property(referenced)] diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index 92e963e80..65adc0279 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -10,6 +10,8 @@ use crate::prelude::*; /// typography possible, we first try to transform the text to superscript /// codepoints. If that fails, we fall back to rendering shrunk normal letters /// in a raised way. +#[func] +#[capable(Show)] #[derive(Debug, Hash)] pub struct ShiftNode(pub Content); @@ -19,7 +21,7 @@ pub type SuperNode = ShiftNode; /// Shift the text into subscript. pub type SubNode = ShiftNode; -#[node(Show)] +#[node] impl ShiftNode { /// Whether to prefer the dedicated sub- and superscript characters of the /// font. diff --git a/library/src/text/symbol.rs b/library/src/text/symbol.rs index cc12afb9b..fc746eb23 100644 --- a/library/src/text/symbol.rs +++ b/library/src/text/symbol.rs @@ -2,10 +2,12 @@ use crate::prelude::*; use crate::text::TextNode; /// A symbol identified by symmie notation. +#[func] +#[capable(Show)] #[derive(Debug, Hash)] pub struct SymbolNode(pub EcoString); -#[node(Show)] +#[node] impl SymbolNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("notation")?).pack()) diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index 205c8a7d4..b8b05aec7 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -5,10 +5,12 @@ use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat}; use crate::prelude::*; /// Show a raster or vector graphic. +#[func] +#[capable(Layout, Inline)] #[derive(Debug, Hash)] pub struct ImageNode(pub Image); -#[node(Layout, Inline)] +#[node] impl ImageNode { /// How the image should adjust itself to a given area. pub const FIT: ImageFit = ImageFit::Cover; diff --git a/library/src/visualize/line.rs b/library/src/visualize/line.rs index ef6ce2c39..ed6a3d92d 100644 --- a/library/src/visualize/line.rs +++ b/library/src/visualize/line.rs @@ -1,6 +1,8 @@ use crate::prelude::*; /// Display a line without affecting the layout. +#[func] +#[capable(Layout, Inline)] #[derive(Debug, Hash)] pub struct LineNode { /// Where the line starts. @@ -9,7 +11,7 @@ pub struct LineNode { pub delta: Axes>, } -#[node(Layout, Inline)] +#[node] impl LineNode { /// How to stroke the line. #[property(resolve, fold)] diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index 4517380a5..702fc6f8b 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -3,6 +3,8 @@ use std::f64::consts::SQRT_2; use crate::prelude::*; /// A sizable and fillable shape with optional content. +#[func] +#[capable(Layout, Inline)] #[derive(Debug, Hash)] pub struct ShapeNode(pub Option); @@ -18,7 +20,7 @@ pub type CircleNode = ShapeNode; /// A ellipse with optional content. pub type EllipseNode = ShapeNode; -#[node(Layout, Inline)] +#[node] impl ShapeNode { /// How to fill the shape. pub const FILL: Option = None; diff --git a/macros/src/capability.rs b/macros/src/capability.rs index 7dd4c42a2..aa98f5843 100644 --- a/macros/src/capability.rs +++ b/macros/src/capability.rs @@ -1,10 +1,48 @@ use super::*; +use syn::parse::Parser; +use syn::punctuated::Punctuated; +use syn::Token; + /// Expand the `#[capability]` macro. -pub fn expand(body: syn::ItemTrait) -> Result { - let ident = &body.ident; +pub fn capability(item: syn::ItemTrait) -> Result { + let ident = &item.ident; Ok(quote! { - #body + #item impl ::typst::model::Capability for dyn #ident {} }) } + +/// Expand the `#[capable(..)]` macro. +pub fn capable(attr: TokenStream, item: syn::Item) -> Result { + let (ident, generics) = match &item { + syn::Item::Struct(s) => (&s.ident, &s.generics), + syn::Item::Enum(s) => (&s.ident, &s.generics), + _ => bail!(item, "only structs and enums are supported"), + }; + + let (params, args, clause) = generics.split_for_impl(); + let checks = Punctuated::::parse_terminated + .parse2(attr)? + .into_iter() + .map(|capability| { + quote! { + if id == ::std::any::TypeId::of::() { + return Some(unsafe { + ::typst::util::fat::vtable(self as &dyn #capability) + }); + } + } + }); + + Ok(quote! { + #item + + unsafe impl #params ::typst::model::Capable for #ident #args #clause { + fn vtable(&self, id: ::std::any::TypeId) -> ::std::option::Option<*const ()> { + #(#checks)* + None + } + } + }) +} diff --git a/macros/src/func.rs b/macros/src/func.rs new file mode 100644 index 000000000..af558f966 --- /dev/null +++ b/macros/src/func.rs @@ -0,0 +1,82 @@ +use super::*; + +/// Expand the `#[func]` macro. +pub fn func(item: syn::Item) -> Result { + let doc = documentation(&item)?; + + if let syn::Item::Fn(item) = &item { + let vis = &item.vis; + let ident = &item.sig.ident; + let s = ident.to_string(); + let mut chars = s.trim_end_matches("_").chars(); + let ty = quote::format_ident!( + "{}{}Func", + chars.next().unwrap().to_ascii_uppercase(), + chars.as_str() + ); + + let full = if item.sig.inputs.len() == 1 { + quote! { |_, args| #ident(args) } + } else { + quote! { #ident } + }; + + Ok(quote! { + #item + + #[doc(hidden)] + #vis enum #ty {} + + impl::typst::model::FuncType for #ty { + fn create_func(name: &'static str) -> ::typst::model::Func { + ::typst::model::Func::from_fn(name, #full, #doc) + } + } + }) + } else { + let (ident, generics) = match &item { + syn::Item::Struct(s) => (&s.ident, &s.generics), + syn::Item::Enum(s) => (&s.ident, &s.generics), + _ => bail!(item, "only structs, enums, and functions are supported"), + }; + + let (params, args, clause) = generics.split_for_impl(); + + Ok(quote! { + #item + + impl #params ::typst::model::FuncType for #ident #args #clause { + fn create_func(name: &'static str) -> ::typst::model::Func { + ::typst::model::Func::from_node::(name, #doc) + } + } + }) + } +} + +/// Extract the item's documentation. +fn documentation(item: &syn::Item) -> Result { + let mut doc = String::new(); + + // Extract attributes. + let attrs = match item { + syn::Item::Struct(item) => &item.attrs, + syn::Item::Enum(item) => &item.attrs, + syn::Item::Fn(item) => &item.attrs, + _ => return Ok(doc), + }; + + // Parse doc comments. + for attr in attrs { + if let syn::Meta::NameValue(meta) = attr.parse_meta()? { + if meta.path.is_ident("doc") { + if let syn::Lit::Str(string) = &meta.lit { + doc.push_str(&string.value()); + doc.push('\n'); + } + } + } + } + + Ok(doc.trim().into()) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 699deee9b..7f6a4a6cf 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -13,6 +13,7 @@ macro_rules! bail { } mod capability; +mod func; mod node; use proc_macro::TokenStream as BoundaryStream; @@ -21,20 +22,34 @@ use quote::{quote, quote_spanned}; use syn::parse_quote; use syn::{Error, Ident, Result}; +/// Implement `FuncType` for a type or function. +#[proc_macro_attribute] +pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { + let item = syn::parse_macro_input!(item as syn::Item); + func::func(item).unwrap_or_else(|err| err.to_compile_error()).into() +} + /// Implement `Node` for a struct. #[proc_macro_attribute] -pub fn node(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { +pub fn node(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { let item = syn::parse_macro_input!(item as syn::ItemImpl); - node::expand(stream.into(), item) - .unwrap_or_else(|err| err.to_compile_error()) - .into() + node::node(item).unwrap_or_else(|err| err.to_compile_error()).into() } /// Implement `Capability` for a trait. #[proc_macro_attribute] pub fn capability(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream { let item = syn::parse_macro_input!(item as syn::ItemTrait); - capability::expand(item) + capability::capability(item) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + +/// Implement `Capable` for a type. +#[proc_macro_attribute] +pub fn capable(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream { + let item = syn::parse_macro_input!(item as syn::Item); + capability::capable(stream.into(), item) .unwrap_or_else(|err| err.to_compile_error()) .into() } diff --git a/macros/src/node.rs b/macros/src/node.rs index 4add9bc38..192ca6674 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -1,4 +1,3 @@ -use syn::parse::Parser; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::Token; @@ -6,8 +5,8 @@ use syn::Token; use super::*; /// Expand the `#[node]` macro. -pub fn expand(attr: TokenStream, body: syn::ItemImpl) -> Result { - let node = prepare(attr, body)?; +pub fn node(body: syn::ItemImpl) -> Result { + let node = prepare(body)?; create(&node) } @@ -18,7 +17,6 @@ struct Node { self_ty: syn::Type, self_name: String, self_args: Punctuated, - capabilities: Vec, properties: Vec, construct: Option, set: Option, @@ -47,7 +45,7 @@ enum Shorthand { } /// Preprocess the impl block of a node. -fn prepare(attr: TokenStream, body: syn::ItemImpl) -> Result { +fn prepare(body: syn::ItemImpl) -> Result { // Extract the generic type arguments. let params = body.generics.params.clone(); @@ -66,12 +64,6 @@ fn prepare(attr: TokenStream, body: syn::ItemImpl) -> Result { _ => Punctuated::new(), }; - // Parse the capabilities. - let capabilities: Vec<_> = Punctuated::::parse_terminated - .parse2(attr)? - .into_iter() - .collect(); - let mut properties = vec![]; let mut construct = None; let mut set = None; @@ -101,7 +93,6 @@ fn prepare(attr: TokenStream, body: syn::ItemImpl) -> Result { self_ty, self_name, self_args, - capabilities, properties, construct, set, @@ -215,7 +206,6 @@ fn create(node: &Node) -> Result { let construct_func = create_node_construct_func(node); let set_func = create_node_set_func(node); let field_method = create_node_field_method(node); - let vtable_method = create_node_vtable_method(node); let node_impl = quote! { impl<#params> ::typst::model::Node for #self_ty { @@ -225,10 +215,6 @@ fn create(node: &Node) -> Result { #set_func #field_method } - - unsafe impl<#params> ::typst::model::Capable for #self_ty { - #vtable_method - } }; let mut modules: Vec = vec![]; @@ -359,26 +345,6 @@ fn create_node_field_method(node: &Node) -> syn::ImplItemMethod { }) } -/// Create the node's capability accessor method. -fn create_node_vtable_method(node: &Node) -> syn::ImplItemMethod { - let checks = node.capabilities.iter().map(|capability| { - quote! { - if id == ::std::any::TypeId::of::() { - return Some(unsafe { - ::typst::util::fat::vtable(self as &dyn #capability) - }); - } - } - }); - - parse_quote! { - fn vtable(&self, id: ::std::any::TypeId) -> ::std::option::Option<*const ()> { - #(#checks)* - None - } - } -} - /// Process a single const item. fn create_property_module(node: &Node, property: &Property) -> (syn::Type, syn::ItemMod) { let params = &node.params; diff --git a/src/doc.rs b/src/doc.rs index d412a371c..90cca9696 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -10,7 +10,9 @@ use crate::geom::{ Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform, }; use crate::image::Image; -use crate::model::{dict, node, Content, Dict, Fold, StableId, StyleChain, Value}; +use crate::model::{ + capable, dict, node, Content, Dict, Fold, StableId, StyleChain, Value, +}; use crate::util::EcoString; /// A finished document with metadata and page frames. @@ -539,6 +541,7 @@ impl FromStr for Region { } /// Meta information that isn't visible or renderable. +#[capable] #[derive(Debug, Clone, Hash)] pub enum Meta { /// An internal or external link. diff --git a/src/model/content.rs b/src/model/content.rs index f261f9b16..e73fa4a8b 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -10,7 +10,9 @@ use siphasher::sip128::{Hasher128, SipHasher}; use thin_vec::ThinVec; use typst_macros::node; -use super::{capability, Args, Guard, Key, Property, Recipe, Style, StyleMap, Value, Vm}; +use super::{ + capability, capable, Args, Guard, Key, Property, Recipe, Style, StyleMap, Value, Vm, +}; use crate::diag::{SourceResult, StrResult}; use crate::syntax::Span; use crate::util::{EcoString, ReadableTypeId}; @@ -342,6 +344,7 @@ impl Hash for dyn Bounds { } /// A node with applied styles. +#[capable] #[derive(Clone, Hash)] pub struct StyledNode { /// The styled content. @@ -364,6 +367,7 @@ impl Debug for StyledNode { /// /// Combines other arbitrary content. So, when you write `[Hi] + [you]` in /// Typst, the two text nodes are combined into a single sequence node. +#[capable] #[derive(Clone, Hash)] pub struct SequenceNode(pub Vec); diff --git a/src/model/eval.rs b/src/model/eval.rs index ef9ba0a8c..d54acf0ad 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -1147,7 +1147,7 @@ impl Eval for ast::ModuleImport { match self.imports() { ast::Imports::Wildcard => { for (var, value) in module.scope.iter() { - vm.scopes.top.define(var, value.clone()); + vm.scopes.top.define(var.clone(), value.clone()); } } ast::Imports::Items(idents) => { diff --git a/src/model/func.rs b/src/model/func.rs index 60f36bd40..0261b5e2d 100644 --- a/src/model/func.rs +++ b/src/model/func.rs @@ -30,16 +30,22 @@ enum Repr { } impl Func { + /// Create a new function from a type that can be turned into a function. + pub fn from_type(name: &'static str) -> Self { + T::create_func(name) + } + /// Create a new function from a native rust function. pub fn from_fn( name: &'static str, func: fn(&Vm, &mut Args) -> SourceResult, + doc: &'static str, ) -> Self { - Self(Arc::new(Repr::Native(Native { name, func, set: None, node: None }))) + Self(Arc::new(Repr::Native(Native { name, func, set: None, node: None, doc }))) } /// Create a new function from a native rust node. - pub fn from_node(name: &'static str) -> Self { + pub fn from_node(name: &'static str, doc: &'static str) -> Self { Self(Arc::new(Repr::Native(Native { name, func: |ctx, args| { @@ -49,6 +55,7 @@ impl Func { }, set: Some(|args| T::set(args, false)), node: Some(NodeId::of::()), + doc, }))) } @@ -66,6 +73,15 @@ impl Func { } } + /// Documentation for the function. + pub fn doc(&self) -> Option<&str> { + match self.0.as_ref() { + Repr::Native(native) => Some(native.doc), + Repr::With(func, _) => func.doc(), + _ => None, + } + } + /// The number of positional arguments this function takes, if known. pub fn argc(&self) -> Option { match self.0.as_ref() { @@ -159,16 +175,24 @@ impl PartialEq for Func { } } +/// Types that can be turned into functions. +pub trait FuncType { + /// Create a function with the given name from this type. + fn create_func(name: &'static str) -> Func; +} + /// A function defined by a native rust function or node. struct Native { /// The name of the function. - pub name: &'static str, + name: &'static str, /// The function pointer. - pub func: fn(&Vm, &mut Args) -> SourceResult, + func: fn(&Vm, &mut Args) -> SourceResult, /// The set rule. - pub set: Option SourceResult>, + set: Option SourceResult>, /// The id of the node to customize with this function's show rule. - pub node: Option, + node: Option, + /// Documentation of the function. + doc: &'static str, } impl Hash for Native { @@ -177,6 +201,7 @@ impl Hash for Native { (self.func as usize).hash(state); self.set.map(|set| set as usize).hash(state); self.node.hash(state); + self.doc.hash(state); } } diff --git a/src/model/mod.rs b/src/model/mod.rs index fa0477464..015df9b35 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -26,7 +26,7 @@ mod typeset; #[doc(hidden)] pub use once_cell; -pub use typst_macros::{capability, node}; +pub use typst_macros::{capability, capable, func, node}; pub use self::args::*; pub use self::array::*; diff --git a/src/model/scope.rs b/src/model/scope.rs index d21d05876..c54cf1b34 100644 --- a/src/model/scope.rs +++ b/src/model/scope.rs @@ -2,8 +2,8 @@ use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; -use super::{Args, Func, Node, Value, Vm}; -use crate::diag::{SourceResult, StrResult}; +use super::{Func, FuncType, Value}; +use crate::diag::StrResult; use crate::util::EcoString; /// A stack of scopes. @@ -75,17 +75,8 @@ impl Scope { } /// Define a function through a native rust function. - pub fn def_fn( - &mut self, - name: &'static str, - func: fn(&Vm, &mut Args) -> SourceResult, - ) { - self.define(name, Func::from_fn(name, func)); - } - - /// Define a function through a native rust node. - pub fn def_node(&mut self, name: &'static str) { - self.define(name, Func::from_node::(name)); + pub fn def_func(&mut self, name: &'static str) { + self.define(name, Func::from_type::(name)); } /// Define a captured, immutable binding. @@ -108,8 +99,8 @@ impl Scope { } /// Iterate over all definitions. - pub fn iter(&self) -> impl Iterator { - self.0.iter().map(|(k, v)| (k.as_str(), v.read())) + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|(k, v)| (k, v.read())) } } diff --git a/src/model/value.rs b/src/model/value.rs index 59dac7201..98d11e155 100644 --- a/src/model/value.rs +++ b/src/model/value.rs @@ -424,9 +424,5 @@ mod tests { test(dict![], "(:)"); test(dict!["one" => 1], "(one: 1)"); test(dict!["two" => false, "one" => 1], "(one: 1, two: false)"); - - // Functions, content and dynamics. - test(Func::from_fn("nil", |_, _| Ok(Value::None)), ""); - test(Dynamic::new(1), "1"); } } diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 700a99267..06162df0f 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -11,11 +11,11 @@ use comemo::{Prehashed, Track}; use elsa::FrozenVec; use once_cell::unsync::OnceCell; use tiny_skia as sk; -use typst::diag::{bail, FileError, FileResult}; +use typst::diag::{bail, FileError, FileResult, SourceResult}; use typst::doc::{Document, Element, Frame, Meta}; use typst::font::{Font, FontBook}; use typst::geom::{Abs, RgbaColor, Sides}; -use typst::model::{Library, Smart, Value}; +use typst::model::{func, Library, Smart, Value}; use typst::syntax::{Source, SourceId, SyntaxNode}; use typst::util::{Buffer, PathExt}; use typst::World; @@ -145,6 +145,29 @@ impl Args { } fn library() -> Library { + #[func] + fn test(args: &mut typst::model::Args) -> SourceResult { + let lhs = args.expect::("left-hand side")?; + let rhs = args.expect::("right-hand side")?; + if lhs != rhs { + bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,); + } + Ok(Value::None) + } + + #[func] + fn print(args: &mut typst::model::Args) -> SourceResult { + print!("> "); + for (i, value) in args.all::()?.into_iter().enumerate() { + if i > 0 { + print!(", ") + } + print!("{value:?}"); + } + println!(); + Ok(Value::None) + } + let mut lib = typst_library::build(); // Set page width to 120pt with 10pt margins, so that the inner page is @@ -157,27 +180,10 @@ fn library() -> Library { lib.styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into())); // Hook up helpers into the global scope. + lib.scope.def_func::("test"); + lib.scope.def_func::("print"); lib.scope.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); lib.scope.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); - lib.scope.def_fn("test", move |_, args| { - let lhs = args.expect::("left-hand side")?; - let rhs = args.expect::("right-hand side")?; - if lhs != rhs { - bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,); - } - Ok(Value::None) - }); - lib.scope.def_fn("print", move |_, args| { - print!("> "); - for (i, value) in args.all::()?.into_iter().enumerate() { - if i > 0 { - print!(", ") - } - print!("{value:?}"); - } - println!(); - Ok(Value::None) - }); lib }