New macro setup

This commit is contained in:
Laurenz 2022-12-14 10:09:44 +01:00
parent 4c73456fc1
commit 9ba4d2c134
54 changed files with 547 additions and 277 deletions

View File

@ -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)]

View File

@ -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<const L: ListKind = LIST> {
/// If true, the items are separated by leading instead of list spacing.
@ -18,7 +20,7 @@ pub type EnumNode = ListNode<ENUM>;
/// A description list.
pub type DescNode = ListNode<DESC>;
#[node(Layout)]
#[node]
impl<const L: ListKind> ListNode<L> {
/// How the list is labelled.
#[property(referenced)]
@ -143,6 +145,7 @@ impl<const L: ListKind> Layout for ListNode<L> {
}
/// An item in a list.
#[capable]
#[derive(Debug, Clone, Hash)]
pub enum ListItem {
/// An item of an unordered list.

View File

@ -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<Content>,
}
#[node(Layout)]
#[node]
impl TableNode {
/// How to fill the cells.
#[property(referenced)]

View File

@ -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<Value> {
#[func]
pub fn abs(args: &mut Args) -> SourceResult<Value> {
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<Value> {
}
/// The minimum of a sequence of values.
pub fn min(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn min(args: &mut Args) -> SourceResult<Value> {
minmax(args, Ordering::Less)
}
/// The maximum of a sequence of values.
pub fn max(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn max(args: &mut Args) -> SourceResult<Value> {
minmax(args, Ordering::Greater)
}
@ -50,17 +53,20 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
}
/// Whether an integer is even.
pub fn even(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn even(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 == 0))
}
/// Whether an integer is odd.
pub fn odd(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn odd(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Bool(args.expect::<i64>("integer")? % 2 != 0))
}
/// The modulo of two numbers.
pub fn mod_(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn mod_(args: &mut Args) -> SourceResult<Value> {
let Spanned { v: v1, span: span1 } = args.expect("integer or float")?;
let Spanned { v: v2, span: span2 } = args.expect("integer or float")?;

View File

@ -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<Value> {
#[func]
pub fn int(args: &mut Args) -> SourceResult<Value> {
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<Value> {
}
/// Convert a value to a float.
pub fn float(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn float(args: &mut Args) -> SourceResult<Value> {
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<Value> {
}
/// Create a grayscale color.
pub fn luma(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn luma(args: &mut Args) -> SourceResult<Value> {
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<Value> {
#[func]
pub fn rgb(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
match RgbaColor::from_str(&string.v) {
Ok(color) => color.into(),
@ -56,7 +60,8 @@ pub fn rgb(_: &Vm, args: &mut Args) -> SourceResult<Value> {
}
/// Create a CMYK color.
pub fn cmyk(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn cmyk(args: &mut Args) -> SourceResult<Value> {
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<Value> {
#[func]
pub fn str(args: &mut Args) -> SourceResult<Value> {
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<Value> {
}
/// Create a label from a string.
pub fn label(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn label(args: &mut Args) -> SourceResult<Value> {
Ok(Value::Label(Label(args.expect("string")?)))
}
/// Create a regular expression from a string.
pub fn regex(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn regex(args: &mut Args) -> SourceResult<Value> {
let Spanned { v, span } = args.expect::<Spanned<EcoString>>("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<Value> {
#[func]
pub fn range(args: &mut Args) -> SourceResult<Value> {
let first = args.expect::<i64>("end")?;
let (start, end) = match args.eat::<i64>()? {
Some(second) => (first, second),

View File

@ -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<Value> {
let Spanned { v: path, span } =
args.expect::<Spanned<EcoString>>("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<Value> {
let Spanned { v: path, span } =
args.expect::<Spanned<EcoString>>("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<Value> {
let Spanned { v: path, span } =
args.expect::<Spanned<EcoString>>("path to xml file")?;

View File

@ -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<Value> {
#[func]
pub fn type_(args: &mut Args) -> SourceResult<Value> {
Ok(args.expect::<Value>("value")?.type_name().into())
}
/// The string representation of a value.
pub fn repr(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn repr(args: &mut Args) -> SourceResult<Value> {
Ok(args.expect::<Value>("value")?.repr().into())
}
/// Ensure that a condition is fulfilled.
pub fn assert(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn assert(args: &mut Args) -> SourceResult<Value> {
let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?;
if !v {
bail!(span, "assertion failed");
@ -24,6 +27,7 @@ pub fn assert(_: &Vm, args: &mut Args) -> SourceResult<Value> {
}
/// Evaluate a string as Typst markup.
#[func]
pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
let source = Source::synthesized(text, span);

View File

@ -4,13 +4,15 @@ use crate::prelude::*;
use crate::text::Case;
/// Create a blind text string.
pub fn lorem(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn lorem(args: &mut Args) -> SourceResult<Value> {
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<Value> {
#[func]
pub fn numbering(args: &mut Args) -> SourceResult<Value> {
let pattern = args.expect::<NumberingPattern>("pattern")?;
let numbers = args.all::<NonZeroUsize>()?;
Ok(Value::Str(pattern.apply(&numbers).into()))

View File

@ -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 {}

View File

@ -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<Content> {
let weak = args.named("weak")?.unwrap_or(false);

View File

@ -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<Content> {
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)]

View File

@ -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<Content>, pub bool);
#[node(Layout)]
impl FlowNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(BlockNode(args.expect("body")?).pack())
}
}
#[node]
impl FlowNode {}
impl Layout for FlowNode {
fn layout(

View File

@ -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<Content>,
}
#[node(Layout)]
#[node]
impl GridNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();

View File

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

View File

@ -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<Content> {
let all = args.named("rest")?.or(args.find()?);

View File

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

View File

@ -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<Content>);
@ -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<Content> {
Ok(Self.pack())

View File

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

View File

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

View File

@ -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<Content> {
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<Content> {
let amount = args.expect("spacing")?;

View File

@ -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<StackChild>,
}
#[node(Layout)]
#[node]
impl StackNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self {

View File

@ -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<Content> {
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<const T: TransformKind> {
/// Transformation to apply to the content.
@ -58,7 +62,7 @@ pub type RotateNode = TransformNode<ROTATE>;
/// Scale content without affecting layout.
pub type ScaleNode = TransformNode<SCALE>;
#[node(Layout, Inline)]
#[node]
impl<const T: TransformKind> TransformNode<T> {
/// The origin of the transformation.
#[property(resolve)]

View File

@ -25,111 +25,110 @@ fn scope() -> Scope {
let mut std = Scope::new();
// Basics.
std.def_node::<basics::HeadingNode>("heading");
std.def_node::<basics::ListNode>("list");
std.def_node::<basics::EnumNode>("enum");
std.def_node::<basics::DescNode>("desc");
std.def_node::<basics::TableNode>("table");
std.def_func::<basics::HeadingNode>("heading");
std.def_func::<basics::ListNode>("list");
std.def_func::<basics::EnumNode>("enum");
std.def_func::<basics::DescNode>("desc");
std.def_func::<basics::TableNode>("table");
// Text.
std.def_node::<text::TextNode>("text");
std.def_node::<text::LinebreakNode>("linebreak");
std.def_node::<text::SymbolNode>("symbol");
std.def_node::<text::SmartQuoteNode>("smartquote");
std.def_node::<text::StrongNode>("strong");
std.def_node::<text::EmphNode>("emph");
std.def_fn("lower", text::lower);
std.def_fn("upper", text::upper);
std.def_fn("smallcaps", text::smallcaps);
std.def_node::<text::SubNode>("sub");
std.def_node::<text::SuperNode>("super");
std.def_node::<text::UnderlineNode>("underline");
std.def_node::<text::StrikeNode>("strike");
std.def_node::<text::OverlineNode>("overline");
std.def_node::<text::RawNode>("raw");
std.def_func::<text::TextNode>("text");
std.def_func::<text::LinebreakNode>("linebreak");
std.def_func::<text::SymbolNode>("symbol");
std.def_func::<text::SmartQuoteNode>("smartquote");
std.def_func::<text::StrongNode>("strong");
std.def_func::<text::EmphNode>("emph");
std.def_func::<text::LowerFunc>("lower");
std.def_func::<text::UpperFunc>("upper");
std.def_func::<text::SmallcapsFunc>("smallcaps");
std.def_func::<text::SubNode>("sub");
std.def_func::<text::SuperNode>("super");
std.def_func::<text::UnderlineNode>("underline");
std.def_func::<text::StrikeNode>("strike");
std.def_func::<text::OverlineNode>("overline");
std.def_func::<text::RawNode>("raw");
// Math.
std.def_node::<math::MathNode>("math");
std.def_node::<math::AtomNode>("atom");
std.def_node::<math::AccNode>("acc");
std.def_node::<math::FracNode>("frac");
std.def_node::<math::BinomNode>("binom");
std.def_node::<math::SqrtNode>("sqrt");
std.def_node::<math::FloorNode>("floor");
std.def_node::<math::CeilNode>("ceil");
std.def_node::<math::VecNode>("vec");
std.def_node::<math::CasesNode>("cases");
std.def_node::<math::SerifNode>("serif");
std.def_node::<math::SansNode>("sans");
std.def_node::<math::BoldNode>("bold");
std.def_node::<math::ItalNode>("ital");
std.def_node::<math::CalNode>("cal");
std.def_node::<math::FrakNode>("frak");
std.def_node::<math::MonoNode>("mono");
std.def_node::<math::BbNode>("bb");
std.def_func::<math::MathNode>("math");
std.def_func::<math::AtomNode>("atom");
std.def_func::<math::AccNode>("acc");
std.def_func::<math::FracNode>("frac");
std.def_func::<math::BinomNode>("binom");
std.def_func::<math::SqrtNode>("sqrt");
std.def_func::<math::FloorNode>("floor");
std.def_func::<math::CeilNode>("ceil");
std.def_func::<math::VecNode>("vec");
std.def_func::<math::CasesNode>("cases");
std.def_func::<math::SerifNode>("serif");
std.def_func::<math::SansNode>("sans");
std.def_func::<math::BoldNode>("bold");
std.def_func::<math::ItalNode>("ital");
std.def_func::<math::CalNode>("cal");
std.def_func::<math::FrakNode>("frak");
std.def_func::<math::MonoNode>("mono");
std.def_func::<math::BbNode>("bb");
// Layout.
std.def_node::<layout::PageNode>("page");
std.def_node::<layout::PagebreakNode>("pagebreak");
std.def_node::<layout::FlowNode>("flow");
std.def_node::<layout::VNode>("v");
std.def_node::<layout::ParNode>("par");
std.def_node::<layout::ParbreakNode>("parbreak");
std.def_node::<layout::HNode>("h");
std.def_node::<layout::BoxNode>("box");
std.def_node::<layout::BlockNode>("block");
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");
std.def_node::<layout::AlignNode>("align");
std.def_node::<layout::PadNode>("pad");
std.def_node::<layout::RepeatNode>("repeat");
std.def_node::<layout::MoveNode>("move");
std.def_node::<layout::ScaleNode>("scale");
std.def_node::<layout::RotateNode>("rotate");
std.def_node::<layout::HideNode>("hide");
std.def_func::<layout::PageNode>("page");
std.def_func::<layout::PagebreakNode>("pagebreak");
std.def_func::<layout::VNode>("v");
std.def_func::<layout::ParNode>("par");
std.def_func::<layout::ParbreakNode>("parbreak");
std.def_func::<layout::HNode>("h");
std.def_func::<layout::BoxNode>("box");
std.def_func::<layout::BlockNode>("block");
std.def_func::<layout::StackNode>("stack");
std.def_func::<layout::GridNode>("grid");
std.def_func::<layout::ColumnsNode>("columns");
std.def_func::<layout::ColbreakNode>("colbreak");
std.def_func::<layout::PlaceNode>("place");
std.def_func::<layout::AlignNode>("align");
std.def_func::<layout::PadNode>("pad");
std.def_func::<layout::RepeatNode>("repeat");
std.def_func::<layout::MoveNode>("move");
std.def_func::<layout::ScaleNode>("scale");
std.def_func::<layout::RotateNode>("rotate");
std.def_func::<layout::HideNode>("hide");
// Visualize.
std.def_node::<visualize::ImageNode>("image");
std.def_node::<visualize::LineNode>("line");
std.def_node::<visualize::RectNode>("rect");
std.def_node::<visualize::SquareNode>("square");
std.def_node::<visualize::EllipseNode>("ellipse");
std.def_node::<visualize::CircleNode>("circle");
std.def_func::<visualize::ImageNode>("image");
std.def_func::<visualize::LineNode>("line");
std.def_func::<visualize::RectNode>("rect");
std.def_func::<visualize::SquareNode>("square");
std.def_func::<visualize::EllipseNode>("ellipse");
std.def_func::<visualize::CircleNode>("circle");
// Meta.
std.def_node::<meta::DocumentNode>("document");
std.def_node::<meta::RefNode>("ref");
std.def_node::<meta::LinkNode>("link");
std.def_node::<meta::OutlineNode>("outline");
std.def_func::<meta::DocumentNode>("document");
std.def_func::<meta::RefNode>("ref");
std.def_func::<meta::LinkNode>("link");
std.def_func::<meta::OutlineNode>("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::<compute::TypeFunc>("type");
std.def_func::<compute::ReprFunc>("repr");
std.def_func::<compute::AssertFunc>("assert");
std.def_func::<compute::EvalFunc>("eval");
std.def_func::<compute::IntFunc>("int");
std.def_func::<compute::FloatFunc>("float");
std.def_func::<compute::LumaFunc>("luma");
std.def_func::<compute::RgbFunc>("rgb");
std.def_func::<compute::CmykFunc>("cmyk");
std.def_func::<compute::StrFunc>("str");
std.def_func::<compute::LabelFunc>("label");
std.def_func::<compute::RegexFunc>("regex");
std.def_func::<compute::RangeFunc>("range");
std.def_func::<compute::AbsFunc>("abs");
std.def_func::<compute::MinFunc>("min");
std.def_func::<compute::MaxFunc>("max");
std.def_func::<compute::EvenFunc>("even");
std.def_func::<compute::OddFunc>("odd");
std.def_func::<compute::ModFunc>("mod");
std.def_func::<compute::CsvFunc>("csv");
std.def_func::<compute::JsonFunc>("json");
std.def_func::<compute::XmlFunc>("xml");
std.def_func::<compute::LoremFunc>("lorem");
std.def_func::<compute::NumberingFunc>("numbering");
// Colors.
std.define("black", Color::BLACK);

View File

@ -1,10 +1,12 @@
use super::*;
/// A column vector.
#[func]
#[capable(Texify)]
#[derive(Debug, Hash)]
pub struct VecNode(Vec<Content>);
#[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<Content>);
#[node(Texify)]
#[node]
impl CasesNode {
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.all()?).pack())

View File

@ -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<Content>,
}
#[node(Show, Layout, Inline, Texify)]
#[node]
impl MathNode {
fn field(&self, name: &str) -> Option<Value> {
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<Content> {
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<Content> {
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<Content> {
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<Content> {
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<Content>,
}
#[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<Content> {
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<Content> {
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<Content> {
Ok(Self(args.expect("body")?).pack())

View File

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

View File

@ -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<PageNode>);
#[node(LayoutRoot)]
#[node]
impl DocumentNode {
/// The document's title.
#[property(referenced)]

View File

@ -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<Content> {
let dest = args.expect::<Destination>("destination")?;

View File

@ -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)]

View File

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

View File

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

View File

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

View File

@ -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<const L: DecoLine>(pub Content);
@ -17,7 +19,7 @@ pub type StrikeNode = DecoNode<STRIKETHROUGH>;
/// Typeset overlined text.
pub type OverlineNode = DecoNode<OVERLINE>;
#[node(Show)]
#[node]
impl<const L: DecoLine> DecoNode<L> {
/// How to stroke the line. The text color and thickness are read from the
/// font tables if `auto`.

View File

@ -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<Content> {
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<Content> {
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<Content> {
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<Value> {
#[func]
pub fn lower(args: &mut Args) -> SourceResult<Value> {
case(Case::Lower, args)
}
/// Convert a string or content to uppercase.
pub fn upper(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn upper(args: &mut Args) -> SourceResult<Value> {
case(Case::Upper, args)
}
@ -161,7 +171,8 @@ impl Case {
}
/// Display text in small capitals.
pub fn smallcaps(_: &Vm, args: &mut Args) -> SourceResult<Value> {
#[func]
pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
let body: Content = args.expect("content")?;
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
}

View File

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

View File

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

View File

@ -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)]

View File

@ -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<const S: ShiftKind>(pub Content);
@ -19,7 +21,7 @@ pub type SuperNode = ShiftNode<SUPERSCRIPT>;
/// Shift the text into subscript.
pub type SubNode = ShiftNode<SUBSCRIPT>;
#[node(Show)]
#[node]
impl<const S: ShiftKind> ShiftNode<S> {
/// Whether to prefer the dedicated sub- and superscript characters of the
/// font.

View File

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

View File

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

View File

@ -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<Rel<Length>>,
}
#[node(Layout, Inline)]
#[node]
impl LineNode {
/// How to stroke the line.
#[property(resolve, fold)]

View File

@ -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<const S: ShapeKind>(pub Option<Content>);
@ -18,7 +20,7 @@ pub type CircleNode = ShapeNode<CIRCLE>;
/// A ellipse with optional content.
pub type EllipseNode = ShapeNode<ELLIPSE>;
#[node(Layout, Inline)]
#[node]
impl<const S: ShapeKind> ShapeNode<S> {
/// How to fill the shape.
pub const FILL: Option<Paint> = None;

View File

@ -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<TokenStream> {
let ident = &body.ident;
pub fn capability(item: syn::ItemTrait) -> Result<TokenStream> {
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<TokenStream> {
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::<Ident, Token![,]>::parse_terminated
.parse2(attr)?
.into_iter()
.map(|capability| {
quote! {
if id == ::std::any::TypeId::of::<dyn #capability>() {
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
}
}
})
}

82
macros/src/func.rs Normal file
View File

@ -0,0 +1,82 @@
use super::*;
/// Expand the `#[func]` macro.
pub fn func(item: syn::Item) -> Result<TokenStream> {
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::<Self>(name, #doc)
}
}
})
}
}
/// Extract the item's documentation.
fn documentation(item: &syn::Item) -> Result<String> {
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())
}

View File

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

View File

@ -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<TokenStream> {
let node = prepare(attr, body)?;
pub fn node(body: syn::ItemImpl) -> Result<TokenStream> {
let node = prepare(body)?;
create(&node)
}
@ -18,7 +17,6 @@ struct Node {
self_ty: syn::Type,
self_name: String,
self_args: Punctuated<syn::GenericArgument, Token![,]>,
capabilities: Vec<syn::Ident>,
properties: Vec<Property>,
construct: Option<syn::ImplItemMethod>,
set: Option<syn::ImplItemMethod>,
@ -47,7 +45,7 @@ enum Shorthand {
}
/// Preprocess the impl block of a node.
fn prepare(attr: TokenStream, body: syn::ItemImpl) -> Result<Node> {
fn prepare(body: syn::ItemImpl) -> Result<Node> {
// Extract the generic type arguments.
let params = body.generics.params.clone();
@ -66,12 +64,6 @@ fn prepare(attr: TokenStream, body: syn::ItemImpl) -> Result<Node> {
_ => Punctuated::new(),
};
// Parse the capabilities.
let capabilities: Vec<_> = Punctuated::<Ident, Token![,]>::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<Node> {
self_ty,
self_name,
self_args,
capabilities,
properties,
construct,
set,
@ -215,7 +206,6 @@ fn create(node: &Node) -> Result<TokenStream> {
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<TokenStream> {
#set_func
#field_method
}
unsafe impl<#params> ::typst::model::Capable for #self_ty {
#vtable_method
}
};
let mut modules: Vec<syn::ItemMod> = 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::<dyn #capability>() {
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;

View File

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

View File

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

View File

@ -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) => {

View File

@ -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<T: FuncType>(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<Value>,
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<T: Node>(name: &'static str) -> Self {
pub fn from_node<T: 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::<T>()),
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<usize> {
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<Value>,
func: fn(&Vm, &mut Args) -> SourceResult<Value>,
/// The set rule.
pub set: Option<fn(&mut Args) -> SourceResult<StyleMap>>,
set: Option<fn(&mut Args) -> SourceResult<StyleMap>>,
/// The id of the node to customize with this function's show rule.
pub node: Option<NodeId>,
node: Option<NodeId>,
/// 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);
}
}

View File

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

View File

@ -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<Value>,
) {
self.define(name, Func::from_fn(name, func));
}
/// Define a function through a native rust node.
pub fn def_node<T: Node>(&mut self, name: &'static str) {
self.define(name, Func::from_node::<T>(name));
pub fn def_func<T: FuncType>(&mut self, name: &'static str) {
self.define(name, Func::from_type::<T>(name));
}
/// Define a captured, immutable binding.
@ -108,8 +99,8 @@ impl Scope {
}
/// Iterate over all definitions.
pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
self.0.iter().map(|(k, v)| (k.as_str(), v.read()))
pub fn iter(&self) -> impl Iterator<Item = (&EcoString, &Value)> {
self.0.iter().map(|(k, v)| (k, v.read()))
}
}

View File

@ -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)), "<function nil>");
test(Dynamic::new(1), "1");
}
}

View File

@ -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<Value> {
let lhs = args.expect::<Value>("left-hand side")?;
let rhs = args.expect::<Value>("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<Value> {
print!("> ");
for (i, value) in args.all::<Value>()?.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::<TestFunc>("test");
lib.scope.def_func::<PrintFunc>("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::<Value>("left-hand side")?;
let rhs = args.expect::<Value>("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::<Value>()?.into_iter().enumerate() {
if i > 0 {
print!(", ")
}
print!("{value:?}");
}
println!();
Ok(Value::None)
});
lib
}