summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/compute/calc.rs443
-rw-r--r--library/src/compute/construct.rs264
-rw-r--r--library/src/compute/data.rs97
-rw-r--r--library/src/compute/foundations.rs83
-rw-r--r--library/src/layout/align.rs1
-rw-r--r--library/src/layout/columns.rs1
-rw-r--r--library/src/layout/container.rs28
-rw-r--r--library/src/layout/enum.rs1
-rw-r--r--library/src/layout/grid.rs12
-rw-r--r--library/src/layout/hide.rs1
-rw-r--r--library/src/layout/list.rs1
-rw-r--r--library/src/layout/pad.rs25
-rw-r--r--library/src/layout/page.rs11
-rw-r--r--library/src/layout/par.rs8
-rw-r--r--library/src/layout/place.rs1
-rw-r--r--library/src/layout/repeat.rs1
-rw-r--r--library/src/layout/spacing.rs34
-rw-r--r--library/src/layout/table.rs28
-rw-r--r--library/src/layout/terms.rs2
-rw-r--r--library/src/layout/transform.rs3
-rw-r--r--library/src/lib.rs140
-rw-r--r--library/src/math/accent.rs2
-rw-r--r--library/src/math/attach.rs3
-rw-r--r--library/src/math/delimited.rs56
-rw-r--r--library/src/math/frac.rs4
-rw-r--r--library/src/math/mod.rs69
-rw-r--r--library/src/math/op.rs1
-rw-r--r--library/src/math/root.rs3
-rw-r--r--library/src/math/style.rs9
-rw-r--r--library/src/math/underover.rs6
-rw-r--r--library/src/meta/heading.rs1
-rw-r--r--library/src/meta/link.rs2
-rw-r--r--library/src/meta/numbering.rs70
-rw-r--r--library/src/meta/reference.rs1
-rw-r--r--library/src/text/deco.rs3
-rw-r--r--library/src/text/misc.rs60
-rw-r--r--library/src/text/mod.rs10
-rw-r--r--library/src/text/raw.rs1
-rw-r--r--library/src/text/shift.rs2
-rw-r--r--library/src/visualize/image.rs1
-rw-r--r--library/src/visualize/line.rs8
-rw-r--r--library/src/visualize/shape.rs20
-rw-r--r--macros/Cargo.toml2
-rw-r--r--macros/src/func.rs312
-rw-r--r--macros/src/lib.rs5
-rw-r--r--macros/src/node.rs162
-rw-r--r--macros/src/util.rs16
-rw-r--r--src/eval/cast.rs26
-rw-r--r--src/eval/func.rs160
-rw-r--r--src/eval/mod.rs5
-rw-r--r--src/eval/scope.rs7
-rw-r--r--src/ide/complete.rs6
-rw-r--r--src/model/content.rs17
-rw-r--r--src/model/mod.rs2
-rw-r--r--src/model/styles.rs2
-rw-r--r--tests/src/tests.rs20
-rw-r--r--tests/typ/compute/calc.typ4
-rw-r--r--tests/typ/text/lorem.typ2
58 files changed, 1097 insertions, 1168 deletions
diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs
index d4a4bcf6..25df817d 100644
--- a/library/src/compute/calc.rs
+++ b/library/src/compute/calc.rs
@@ -10,28 +10,28 @@ use crate::prelude::*;
/// A module with computational functions.
pub fn module() -> Module {
let mut scope = Scope::new();
- scope.def_func::<AbsFunc>("abs");
- scope.def_func::<PowFunc>("pow");
- scope.def_func::<SqrtFunc>("sqrt");
- scope.def_func::<SinFunc>("sin");
- scope.def_func::<CosFunc>("cos");
- scope.def_func::<TanFunc>("tan");
- scope.def_func::<AsinFunc>("asin");
- scope.def_func::<AcosFunc>("acos");
- scope.def_func::<AtanFunc>("atan");
- scope.def_func::<SinhFunc>("sinh");
- scope.def_func::<CoshFunc>("cosh");
- scope.def_func::<TanhFunc>("tanh");
- scope.def_func::<LogFunc>("log");
- scope.def_func::<FloorFunc>("floor");
- scope.def_func::<CeilFunc>("ceil");
- scope.def_func::<RoundFunc>("round");
- scope.def_func::<ClampFunc>("clamp");
- scope.def_func::<MinFunc>("min");
- scope.def_func::<MaxFunc>("max");
- scope.def_func::<EvenFunc>("even");
- scope.def_func::<OddFunc>("odd");
- scope.def_func::<ModFunc>("mod");
+ scope.define("abs", abs);
+ scope.define("pow", pow);
+ scope.define("sqrt", sqrt);
+ scope.define("sin", sin);
+ scope.define("cos", cos);
+ scope.define("tan", tan);
+ scope.define("asin", asin);
+ scope.define("acos", acos);
+ scope.define("atan", atan);
+ scope.define("sinh", sinh);
+ scope.define("cosh", cosh);
+ scope.define("tanh", tanh);
+ scope.define("log", log);
+ scope.define("floor", floor);
+ scope.define("ceil", ceil);
+ scope.define("round", round);
+ scope.define("clamp", clamp);
+ scope.define("min", min);
+ scope.define("max", max);
+ scope.define("even", even);
+ scope.define("odd", odd);
+ scope.define("mod", mod_);
scope.define("inf", Value::Float(f64::INFINITY));
scope.define("nan", Value::Float(f64::NAN));
scope.define("pi", Value::Float(std::f64::consts::PI));
@@ -48,15 +48,15 @@ pub fn module() -> Module {
/// #calc.abs(2fr)
/// ```
///
-/// ## Parameters
-/// - value: `ToAbs` (positional, required)
-/// The value whose absolute value to calculate.
-///
/// Display: Absolute
/// Category: calculate
+/// Returns: any
#[func]
-pub fn abs(args: &mut Args) -> SourceResult<Value> {
- Ok(args.expect::<ToAbs>("value")?.0)
+pub fn abs(
+ /// The value whose absolute value to calculate.
+ value: ToAbs,
+) -> Value {
+ value.0
}
/// A value of which the absolute value can be taken.
@@ -80,27 +80,27 @@ cast_from_value! {
/// #calc.pow(2, 3)
/// ```
///
-/// ## Parameters
-/// - base: `Num` (positional, required)
-/// The base of the power.
-/// - exponent: `Num` (positional, required)
-/// The exponent of the power. Must be non-negative.
-///
/// Display: Power
/// Category: calculate
+/// Returns: integer or float
#[func]
-pub fn pow(args: &mut Args) -> SourceResult<Value> {
- let base = args.expect::<Num>("base")?;
- let exponent = args
- .expect::<Spanned<Num>>("exponent")
- .and_then(|n| match n.v {
- Num::Int(i) if i > u32::MAX as i64 => bail!(n.span, "exponent too large"),
- Num::Int(i) if i >= 0 => Ok(n),
- Num::Float(f) if f >= 0.0 => Ok(n),
- _ => bail!(n.span, "exponent must be non-negative"),
- })?
- .v;
- Ok(base.apply2(exponent, |a, b| a.pow(b as u32), f64::powf))
+pub fn pow(
+ /// The base of the power.
+ base: Num,
+ /// The exponent of the power. Must be non-negative.
+ exponent: Spanned<Num>,
+) -> Value {
+ let exponent = match exponent.v {
+ Num::Int(i) if i > u32::MAX as i64 => {
+ bail!(exponent.span, "exponent too large");
+ }
+ Num::Int(0..) => exponent.v,
+ Num::Float(f) if f >= 0.0 => exponent.v,
+ _ => {
+ bail!(exponent.span, "exponent must be non-negative");
+ }
+ };
+ base.apply2(exponent, |a, b| a.pow(b as u32), f64::powf)
}
/// Calculate the square root of a number.
@@ -111,19 +111,18 @@ pub fn pow(args: &mut Args) -> SourceResult<Value> {
/// #calc.sqrt(2.5)
/// ```
///
-/// ## Parameters
-/// - value: `Num` (positional, required)
-/// The number whose square root to calculate. Must be non-negative.
-///
/// Display: Square Root
/// Category: calculate
+/// Returns: float
#[func]
-pub fn sqrt(args: &mut Args) -> SourceResult<Value> {
- let value = args.expect::<Spanned<Num>>("value")?;
+pub fn sqrt(
+ /// The number whose square root to calculate. Must be non-negative.
+ value: Spanned<Num>,
+) -> Value {
if value.v.float() < 0.0 {
bail!(value.span, "cannot take square root of negative number");
}
- Ok(Value::Float(value.v.float().sqrt()))
+ Value::Float(value.v.float().sqrt())
}
/// Calculate the sine of an angle.
@@ -138,20 +137,19 @@ pub fn sqrt(args: &mut Args) -> SourceResult<Value> {
/// #calc.sin(90deg)
/// ```
///
-/// ## Parameters
-/// - angle: `AngleLike` (positional, required)
-/// The angle whose sine to calculate.
-///
/// Display: Sine
/// Category: calculate
+/// Returns: float
#[func]
-pub fn sin(args: &mut Args) -> SourceResult<Value> {
- let arg = args.expect::<AngleLike>("angle")?;
- Ok(Value::Float(match arg {
+pub fn sin(
+ /// The angle whose sine to calculate.
+ angle: AngleLike,
+) -> Value {
+ Value::Float(match angle {
AngleLike::Angle(a) => a.sin(),
AngleLike::Int(n) => (n as f64).sin(),
AngleLike::Float(n) => n.sin(),
- }))
+ })
}
/// Calculate the cosine of an angle.
@@ -161,25 +159,24 @@ pub fn sin(args: &mut Args) -> SourceResult<Value> {
///
/// ## Example
/// ```example
-/// #calc.cos(90deg)
+/// #calc.cos(90deg) \
/// #calc.cos(1.5) \
/// #calc.cos(90deg)
/// ```
///
-/// ## Parameters
-/// - angle: `AngleLike` (positional, required)
-/// The angle whose cosine to calculate.
-///
/// Display: Cosine
/// Category: calculate
+/// Returns: float
#[func]
-pub fn cos(args: &mut Args) -> SourceResult<Value> {
- let arg = args.expect::<AngleLike>("angle")?;
- Ok(Value::Float(match arg {
+pub fn cos(
+ /// The angle whose cosine to calculate.
+ angle: AngleLike,
+) -> Value {
+ Value::Float(match angle {
AngleLike::Angle(a) => a.cos(),
AngleLike::Int(n) => (n as f64).cos(),
AngleLike::Float(n) => n.cos(),
- }))
+ })
}
/// Calculate the tangent of an angle.
@@ -193,20 +190,19 @@ pub fn cos(args: &mut Args) -> SourceResult<Value> {
/// #calc.tan(90deg)
/// ```
///
-/// ## Parameters
-/// - angle: `AngleLike` (positional, required)
-/// The angle whose tangent to calculate.
-///
/// Display: Tangent
/// Category: calculate
+/// Returns: float
#[func]
-pub fn tan(args: &mut Args) -> SourceResult<Value> {
- let arg = args.expect::<AngleLike>("angle")?;
- Ok(Value::Float(match arg {
+pub fn tan(
+ /// The angle whose tangent to calculate.
+ angle: AngleLike,
+) -> Value {
+ Value::Float(match angle {
AngleLike::Angle(a) => a.tan(),
AngleLike::Int(n) => (n as f64).tan(),
AngleLike::Float(n) => n.tan(),
- }))
+ })
}
/// Calculate the arcsine of a number.
@@ -217,20 +213,19 @@ pub fn tan(args: &mut Args) -> SourceResult<Value> {
/// #calc.asin(1)
/// ```
///
-/// ## Parameters
-/// - value: `Num` (positional, required)
-/// The number whose arcsine to calculate. Must be between -1 and 1.
-///
/// Display: Arcsine
/// Category: calculate
+/// Returns: angle
#[func]
-pub fn asin(args: &mut Args) -> SourceResult<Value> {
- let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?;
- let val = v.float();
+pub fn asin(
+ /// The number whose arcsine to calculate. Must be between -1 and 1.
+ value: Spanned<Num>,
+) -> Value {
+ let val = value.v.float();
if val < -1.0 || val > 1.0 {
- bail!(span, "arcsin must be between -1 and 1");
+ bail!(value.span, "arcsin must be between -1 and 1");
}
- Ok(Value::Angle(Angle::rad(val.asin())))
+ Value::Angle(Angle::rad(val.asin()))
}
/// Calculate the arccosine of a number.
@@ -241,20 +236,19 @@ pub fn asin(args: &mut Args) -> SourceResult<Value> {
/// #calc.acos(1)
/// ```
///
-/// ## Parameters
-/// - value: `Num` (positional, required)
-/// The number whose arccosine to calculate. Must be between -1 and 1.
-///
/// Display: Arccosine
/// Category: calculate
+/// Returns: angle
#[func]
-pub fn acos(args: &mut Args) -> SourceResult<Value> {
- let Spanned { v, span } = args.expect::<Spanned<Num>>("value")?;
- let val = v.float();
+pub fn acos(
+ /// The number whose arcsine to calculate. Must be between -1 and 1.
+ value: Spanned<Num>,
+) -> Value {
+ let val = value.v.float();
if val < -1.0 || val > 1.0 {
- bail!(span, "arccos must be between -1 and 1");
+ bail!(value.span, "arccos must be between -1 and 1");
}
- Ok(Value::Angle(Angle::rad(val.acos())))
+ Value::Angle(Angle::rad(val.acos()))
}
/// Calculate the arctangent of a number.
@@ -265,16 +259,15 @@ pub fn acos(args: &mut Args) -> SourceResult<Value> {
/// #calc.atan(1)
/// ```
///
-/// ## Parameters
-/// - value: `Num` (positional, required)
-/// The number whose arctangent to calculate.
-///
/// Display: Arctangent
/// Category: calculate
+/// Returns: angle
#[func]
-pub fn atan(args: &mut Args) -> SourceResult<Value> {
- let value = args.expect::<Num>("value")?;
- Ok(Value::Angle(Angle::rad(value.float().atan())))
+pub fn atan(
+ /// The number whose arctangent to calculate.
+ value: Num,
+) -> Value {
+ Value::Angle(Angle::rad(value.float().atan()))
}
/// Calculate the hyperbolic sine of an angle.
@@ -287,20 +280,19 @@ pub fn atan(args: &mut Args) -> SourceResult<Value> {
/// #calc.sinh(45deg)
/// ```
///
-/// ## Parameters
-/// - angle: `AngleLike` (positional, required)
-/// The angle whose hyperbolic sine to calculate.
-///
/// Display: Hyperbolic sine
/// Category: calculate
+/// Returns: float
#[func]
-pub fn sinh(args: &mut Args) -> SourceResult<Value> {
- let arg = args.expect::<AngleLike>("angle")?;
- Ok(Value::Float(match arg {
+pub fn sinh(
+ /// The angle whose hyperbolic sine to calculate.
+ angle: AngleLike,
+) -> Value {
+ Value::Float(match angle {
AngleLike::Angle(a) => a.to_rad().sinh(),
AngleLike::Int(n) => (n as f64).sinh(),
AngleLike::Float(n) => n.sinh(),
- }))
+ })
}
/// Calculate the hyperbolic cosine of an angle.
@@ -313,20 +305,19 @@ pub fn sinh(args: &mut Args) -> SourceResult<Value> {
/// #calc.cosh(45deg)
/// ```
///
-/// ## Parameters
-/// - angle: `AngleLike` (positional, required)
-/// The angle whose hyperbolic cosine to calculate.
-///
/// Display: Hyperbolic cosine
/// Category: calculate
+/// Returns: float
#[func]
-pub fn cosh(args: &mut Args) -> SourceResult<Value> {
- let arg = args.expect::<AngleLike>("angle")?;
- Ok(Value::Float(match arg {
+pub fn cosh(
+ /// The angle whose hyperbolic cosine to calculate.
+ angle: AngleLike,
+) -> Value {
+ Value::Float(match angle {
AngleLike::Angle(a) => a.to_rad().cosh(),
AngleLike::Int(n) => (n as f64).cosh(),
AngleLike::Float(n) => n.cosh(),
- }))
+ })
}
/// Calculate the hyperbolic tangent of an angle.
@@ -339,20 +330,19 @@ pub fn cosh(args: &mut Args) -> SourceResult<Value> {
/// #calc.tanh(45deg)
/// ```
///
-/// ## Parameters
-/// - angle: `AngleLike` (positional, required)
-/// The angle whose hyperbolic tangent to calculate.
-///
/// Display: Hyperbolic tangent
/// Category: calculate
+/// Returns: float
#[func]
-pub fn tanh(args: &mut Args) -> SourceResult<Value> {
- let arg = args.expect::<AngleLike>("angle")?;
- Ok(Value::Float(match arg {
+pub fn tanh(
+ /// The angle whose hyperbolic tangent to calculate.
+ angle: AngleLike,
+) -> Value {
+ Value::Float(match angle {
AngleLike::Angle(a) => a.to_rad().tanh(),
AngleLike::Int(n) => (n as f64).tanh(),
AngleLike::Float(n) => n.tanh(),
- }))
+ })
}
/// Calculate the logarithm of a number.
@@ -361,22 +351,22 @@ pub fn tanh(args: &mut Args) -> SourceResult<Value> {
///
/// ## Example
/// ```example
-/// #calc.log(100) \
+/// #calc.log(100)
/// ```
///
-/// ## Parameters
-/// - value: `Num` (positional, required)
-/// The number whose logarithm to calculate.
-/// - base: `Num` (named)
-/// The base of the logarithm.
-///
/// Display: Logarithm
/// Category: calculate
+/// Returns: float
#[func]
-pub fn log(args: &mut Args) -> SourceResult<Value> {
- let value = args.expect::<f64>("value")?;
- let base = args.named::<f64>("base")?.unwrap_or_else(|| 10.0);
- Ok(Value::Float(value.log(base)))
+pub fn log(
+ /// The number whose logarithm to calculate.
+ value: f64,
+ /// The base of the logarithm.
+ #[named]
+ #[default(10.0)]
+ base: f64,
+) -> Value {
+ Value::Float(value.log(base))
}
/// Round a number down to the nearest integer.
@@ -390,19 +380,18 @@ pub fn log(args: &mut Args) -> SourceResult<Value> {
/// #calc.floor(500.1)
/// ```
///
-/// ## Parameters
-/// - value: `Num` (positional, required)
-/// The number to round down.
-///
/// Display: Round down
/// Category: calculate
+/// Returns: integer
#[func]
-pub fn floor(args: &mut Args) -> SourceResult<Value> {
- let value = args.expect::<Num>("value")?;
- Ok(match value {
+pub fn floor(
+ /// The number to round down.
+ value: Num,
+) -> Value {
+ match value {
Num::Int(n) => Value::Int(n),
Num::Float(n) => Value::Int(n.floor() as i64),
- })
+ }
}
/// Round a number up to the nearest integer.
@@ -416,19 +405,18 @@ pub fn floor(args: &mut Args) -> SourceResult<Value> {
/// #calc.ceil(500.1)
/// ```
///
-/// ## Parameters
-/// - value: `Num` (positional, required)
-/// The number to round up.
-///
/// Display: Round up
/// Category: calculate
+/// Returns: integer
#[func]
-pub fn ceil(args: &mut Args) -> SourceResult<Value> {
- let value = args.expect::<Num>("value")?;
- Ok(match value {
+pub fn ceil(
+ /// The number to round up.
+ value: Num,
+) -> Value {
+ match value {
Num::Int(n) => Value::Int(n),
Num::Float(n) => Value::Int(n.ceil() as i64),
- })
+ }
}
/// Round a number to the nearest integer.
@@ -442,25 +430,26 @@ pub fn ceil(args: &mut Args) -> SourceResult<Value> {
/// #calc.round(3.1415, digits: 2)
/// ```
///
-/// ## Parameters
-/// - value: `Num` (positional, required)
-/// The number to round.
-/// - digits: `i64` (named)
-///
/// Display: Round
/// Category: calculate
+/// Returns: integer or float
#[func]
-pub fn round(args: &mut Args) -> SourceResult<Value> {
- let value = args.expect::<Num>("value")?;
- let digits = args.named::<u32>("digits")?.unwrap_or(0);
- Ok(match value {
+pub fn round(
+ /// The number to round.
+ value: Num,
+ /// The number of decimal places.
+ #[named]
+ #[default(0)]
+ digits: i64,
+) -> Value {
+ match value {
Num::Int(n) if digits == 0 => Value::Int(n),
_ => {
let n = value.float();
let factor = 10.0_f64.powi(digits as i32) as f64;
Value::Float((n * factor).round() / factor)
}
- })
+ }
}
/// Clamp a number between a minimum and maximum value.
@@ -472,25 +461,22 @@ pub fn round(args: &mut Args) -> SourceResult<Value> {
/// #calc.clamp(5, 0, 4)
/// ```
///
-/// ## Parameters
-/// - value: `Num` (positional, required)
-/// The number to clamp.
-/// - min: `Num` (positional, required)
-/// The inclusive minimum value.
-/// - max: `Num` (positional, required)
-/// The inclusive maximum value.
-///
/// Display: Clamp
/// Category: calculate
+/// Returns: integer or float
#[func]
-pub fn clamp(args: &mut Args) -> SourceResult<Value> {
- let value = args.expect::<Num>("value")?;
- let min = args.expect::<Num>("min")?;
- let max = args.expect::<Spanned<Num>>("max")?;
+pub fn clamp(
+ /// The number to clamp.
+ value: Num,
+ /// The inclusive minimum value.
+ min: Num,
+ /// The inclusive maximum value.
+ max: Spanned<Num>,
+) -> Value {
if max.v.float() < min.float() {
bail!(max.span, "max must be greater than or equal to min")
}
- Ok(value.apply3(min, max.v, i64::clamp, f64::clamp))
+ value.apply3(min, max.v, i64::clamp, f64::clamp)
}
/// Determine the minimum of a sequence of values.
@@ -501,18 +487,17 @@ pub fn clamp(args: &mut Args) -> SourceResult<Value> {
/// #calc.min("typst", "in", "beta")
/// ```
///
-/// ## Parameters
-/// - values: `Value` (positional, variadic)
-/// The sequence of values from which to extract the minimum.
-/// Must not be empty.
-///
-/// - returns: any
-///
/// Display: Minimum
/// Category: calculate
+/// Returns: any
#[func]
-pub fn min(args: &mut Args) -> SourceResult<Value> {
- minmax(args, Ordering::Less)
+pub fn min(
+ /// The sequence of values from which to extract the minimum.
+ /// Must not be empty.
+ #[variadic]
+ values: Vec<Spanned<Value>>,
+) -> Value {
+ minmax(args.span, values, Ordering::Less)?
}
/// Determine the maximum of a sequence of values.
@@ -523,24 +508,31 @@ pub fn min(args: &mut Args) -> SourceResult<Value> {
/// #calc.max("typst", "in", "beta")
/// ```
///
-/// ## Parameters
-/// - values: `Value` (positional, variadic)
-/// The sequence of values from which to extract the maximum.
-/// Must not be empty.
-///
-/// - returns: any
-///
/// Display: Maximum
/// Category: calculate
+/// Returns: any
#[func]
-pub fn max(args: &mut Args) -> SourceResult<Value> {
- minmax(args, Ordering::Greater)
+pub fn max(
+ /// The sequence of values from which to extract the maximum.
+ /// Must not be empty.
+ #[variadic]
+ values: Vec<Spanned<Value>>,
+) -> Value {
+ minmax(args.span, values, Ordering::Greater)?
}
/// Find the minimum or maximum of a sequence of values.
-fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
- let mut extremum = args.expect::<Value>("value")?;
- for Spanned { v, span } in args.all::<Spanned<Value>>()? {
+fn minmax(
+ span: Span,
+ values: Vec<Spanned<Value>>,
+ goal: Ordering,
+) -> SourceResult<Value> {
+ let mut iter = values.into_iter();
+ let Some(Spanned { v: mut extremum, ..}) = iter.next() else {
+ bail!(span, "expected at least one value");
+ };
+
+ for Spanned { v, span } in iter {
match v.partial_cmp(&extremum) {
Some(ordering) => {
if ordering == goal {
@@ -555,6 +547,7 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
),
}
}
+
Ok(extremum)
}
@@ -567,17 +560,15 @@ fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> {
/// #range(10).filter(calc.even)
/// ```
///
-/// ## Parameters
-/// - value: `i64` (positional, required)
-/// The number to check for evenness.
-///
-/// - returns: boolean
-///
/// Display: Even
/// Category: calculate
+/// Returns: boolean
#[func]
-pub fn even(args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Bool(args.expect::<i64>("value")? % 2 == 0))
+pub fn even(
+ /// The number to check for evenness.
+ value: i64,
+) -> Value {
+ Value::Bool(value % 2 == 0)
}
/// Determine whether an integer is odd.
@@ -589,18 +580,15 @@ pub fn even(args: &mut Args) -> SourceResult<Value> {
/// #range(10).filter(calc.odd)
/// ```
///
-///
-/// ## Parameters
-/// - value: `i64` (positional, required)
-/// The number to check for oddness.
-///
-/// - returns: boolean
-///
/// Display: Odd
/// Category: calculate
+/// Returns: boolean
#[func]
-pub fn odd(args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Bool(args.expect::<i64>("value")? % 2 != 0))
+pub fn odd(
+ /// The number to check for oddness.
+ value: i64,
+) -> Value {
+ Value::Bool(value % 2 != 0)
}
/// Calculate the modulus of two numbers.
@@ -611,25 +599,20 @@ pub fn odd(args: &mut Args) -> SourceResult<Value> {
/// #calc.mod(1.75, 0.5)
/// ```
///
-/// ## Parameters
-/// - dividend: `Num` (positional, required)
-/// The dividend of the modulus.
-///
-/// - divisor: `Num` (positional, required)
-/// The divisor of the modulus.
-///
-/// - returns: integer or float
-///
/// Display: Modulus
/// Category: calculate
+/// Returns: integer or float
#[func]
-pub fn mod_(args: &mut Args) -> SourceResult<Value> {
- let dividend = args.expect::<Num>("dividend")?;
- let Spanned { v: divisor, span } = args.expect::<Spanned<Num>>("divisor")?;
- if divisor.float() == 0.0 {
- bail!(span, "divisor must not be zero");
+pub fn mod_(
+ /// The dividend of the modulus.
+ dividend: Num,
+ /// The divisor of the modulus.
+ divisor: Spanned<Num>,
+) -> Value {
+ if divisor.v.float() == 0.0 {
+ bail!(divisor.span, "divisor must not be zero");
}
- Ok(dividend.apply2(divisor, Rem::rem, Rem::rem))
+ dividend.apply2(divisor.v, Rem::rem, Rem::rem)
}
/// A value which can be passed to functions that work with integers and floats.
diff --git a/library/src/compute/construct.rs b/library/src/compute/construct.rs
index db442327..4d6068a1 100644
--- a/library/src/compute/construct.rs
+++ b/library/src/compute/construct.rs
@@ -1,3 +1,4 @@
+use std::num::NonZeroI64;
use std::str::FromStr;
use ecow::EcoVec;
@@ -19,17 +20,15 @@ use crate::prelude::*;
/// #{ int("27") + int("4") }
/// ```
///
-/// ## Parameters
-/// - value: `ToInt` (positional, required)
-/// The value that should be converted to an integer.
-///
-/// - returns: integer
-///
/// Display: Integer
/// Category: construct
+/// Returns: integer
#[func]
-pub fn int(args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Int(args.expect::<ToInt>("value")?.0))
+pub fn int(
+ /// The value that should be converted to an integer.
+ value: ToInt,
+) -> Value {
+ Value::Int(value.0)
}
/// A value that can be cast to an integer.
@@ -59,17 +58,15 @@ cast_from_value! {
/// #float("1e5")
/// ```
///
-/// ## Parameters
-/// - value: `ToFloat` (positional, required)
-/// The value that should be converted to a float.
-///
-/// - returns: float
-///
/// Display: Float
/// Category: construct
+/// Returns: float
#[func]
-pub fn float(args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Float(args.expect::<ToFloat>("value")?.0))
+pub fn float(
+ /// The value that should be converted to a float.
+ value: ToFloat,
+) -> Value {
+ Value::Float(value.0)
}
/// A value that can be cast to a float.
@@ -92,18 +89,15 @@ cast_from_value! {
/// }
/// ```
///
-/// ## Parameters
-/// - gray: `Component` (positional, required)
-/// The gray component.
-///
-/// - returns: color
-///
/// Display: Luma
/// Category: construct
+/// Returns: color
#[func]
-pub fn luma(args: &mut Args) -> SourceResult<Value> {
- let Component(luma) = args.expect("gray component")?;
- Ok(Value::Color(LumaColor::new(luma).into()))
+pub fn luma(
+ /// The gray component.
+ gray: Component,
+) -> Value {
+ Value::Color(LumaColor::new(gray.0).into())
}
/// Create an RGB(A) color.
@@ -121,40 +115,44 @@ pub fn luma(args: &mut Args) -> SourceResult<Value> {
/// #square(fill: rgb(25%, 13%, 65%))
/// ```
///
-/// ## Parameters
-/// - hex: `EcoString` (positional)
-/// The color in hexadecimal notation.
-///
-/// Accepts three, four, six or eight hexadecimal digits and optionally
-/// a leading hashtag.
-///
-/// If this string is given, the individual components should not be given.
-///
-/// ```example
-/// #text(16pt, rgb("#239dad"))[
-/// *Typst*
-/// ]
-/// ```
-///
-/// - red: `Component` (positional)
-/// The red component.
-///
-/// - green: `Component` (positional)
-/// The green component.
-///
-/// - blue: `Component` (positional)
-/// The blue component.
-///
-/// - alpha: `Component` (positional)
-/// The alpha component.
-///
-/// - returns: color
-///
-/// Display: RGBA
+/// Display: RGB
/// Category: construct
+/// Returns: color
#[func]
-pub fn rgb(args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
+pub fn rgb(
+ /// The color in hexadecimal notation.
+ ///
+ /// Accepts three, four, six or eight hexadecimal digits and optionally
+ /// a leading hashtag.
+ ///
+ /// If this string is given, the individual components should not be given.
+ ///
+ /// ```example
+ /// #text(16pt, rgb("#239dad"))[
+ /// *Typst*
+ /// ]
+ /// ```
+ #[external]
+ #[default]
+ hex: EcoString,
+ /// The red component.
+ #[external]
+ #[default]
+ red: Component,
+ /// The green component.
+ #[external]
+ #[default]
+ green: Component,
+ /// The blue component.
+ #[external]
+ #[default]
+ blue: Component,
+ /// The alpha component.
+ #[external]
+ #[default]
+ alpha: Component,
+) -> Value {
+ Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? {
match RgbaColor::from_str(&string.v) {
Ok(color) => color.into(),
Err(msg) => bail!(string.span, msg),
@@ -165,7 +163,7 @@ pub fn rgb(args: &mut Args) -> SourceResult<Value> {
let Component(b) = args.expect("blue component")?;
let Component(a) = args.eat()?.unwrap_or(Component(255));
RgbaColor::new(r, g, b, a).into()
- }))
+ })
}
/// An integer or ratio component.
@@ -197,30 +195,21 @@ cast_from_value! {
/// )
/// ````
///
-/// ## Parameters
-/// - cyan: `RatioComponent` (positional, required)
-/// The cyan component.
-///
-/// - magenta: `RatioComponent` (positional, required)
-/// The magenta component.
-///
-/// - yellow: `RatioComponent` (positional, required)
-/// The yellow component.
-///
-/// - key: `RatioComponent` (positional, required)
-/// The key component.
-///
-/// - returns: color
-///
/// Display: CMYK
/// Category: construct
+/// Returns: color
#[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")?;
- let RatioComponent(k) = args.expect("key component")?;
- Ok(Value::Color(CmykColor::new(c, m, y, k).into()))
+pub fn cmyk(
+ /// The cyan component.
+ cyan: RatioComponent,
+ /// The magenta component.
+ magenta: RatioComponent,
+ /// The yellow component.
+ yellow: RatioComponent,
+ /// The key component.
+ key: RatioComponent,
+) -> Value {
+ Value::Color(CmykColor::new(cyan.0, magenta.0, yellow.0, key.0).into())
}
/// A component that must be a ratio.
@@ -254,30 +243,29 @@ cast_from_value! {
/// #envelope.fly
/// ```
///
-/// ## Parameters
-/// - variants: `Variant` (positional, variadic)
-/// The variants of the symbol.
-///
-/// Can be a just a string consisting of a single character for the
-/// modifierless variant or an array with two strings specifying the modifiers
-/// and the symbol. Individual modifiers should be separated by dots. When
-/// displaying a symbol, Typst selects the first from the variants that have
-/// all attached modifiers and the minimum number of other modifiers.
-///
-/// - returns: symbol
-///
/// Display: Symbol
/// Category: construct
+/// Returns: symbol
#[func]
-pub fn symbol(args: &mut Args) -> SourceResult<Value> {
+pub fn symbol(
+ /// The variants of the symbol.
+ ///
+ /// Can be a just a string consisting of a single character for the
+ /// modifierless variant or an array with two strings specifying the modifiers
+ /// and the symbol. Individual modifiers should be separated by dots. When
+ /// displaying a symbol, Typst selects the first from the variants that have
+ /// all attached modifiers and the minimum number of other modifiers.
+ #[variadic]
+ variants: Vec<Spanned<Variant>>,
+) -> Value {
let mut list = EcoVec::new();
- for Spanned { v, span } in args.all::<Spanned<Variant>>()? {
+ for Spanned { v, span } in variants {
if list.iter().any(|(prev, _)| &v.0 == prev) {
bail!(span, "duplicate variant");
}
list.push((v.0, v.1));
}
- Ok(Value::Symbol(Symbol::runtime(list)))
+ Value::Symbol(Symbol::runtime(list))
}
/// A value that can be cast to a symbol.
@@ -309,17 +297,15 @@ cast_from_value! {
/// #str(<intro>)
/// ```
///
-/// ## Parameters
-/// - value: `ToStr` (positional, required)
-/// The value that should be converted to a string.
-///
-/// - returns: string
-///
/// Display: String
/// Category: construct
+/// Returns: string
#[func]
-pub fn str(args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Str(args.expect::<ToStr>("value")?.0))
+pub fn str(
+ /// The value that should be converted to a string.
+ value: ToStr,
+) -> Value {
+ Value::Str(value.0)
}
/// A value that can be cast to a string.
@@ -352,17 +338,15 @@ cast_from_value! {
/// This function also has dedicated syntax: You can create a label by enclosing
/// its name in angle brackets. This works both in markup and code.
///
-/// ## Parameters
-/// - name: `EcoString` (positional, required)
-/// The name of the label.
-///
-/// - returns: label
-///
/// Display: Label
/// Category: construct
+/// Returns: label
#[func]
-pub fn label(args: &mut Args) -> SourceResult<Value> {
- Ok(Value::Label(Label(args.expect("string")?)))
+pub fn label(
+ /// The name of the label.
+ name: EcoString,
+) -> Value {
+ Value::Label(Label(name))
}
/// Create a regular expression from a string.
@@ -386,23 +370,20 @@ pub fn label(args: &mut Args) -> SourceResult<Value> {
/// .split(regex("[,;]")))
/// ```
///
-/// ## Parameters
-/// - regex: `EcoString` (positional, required)
-/// The regular expression as a string.
-///
-/// Most regex escape sequences just work because they are not valid Typst
-/// escape sequences. To produce regex escape sequences that are also valid in
-/// Typst (e.g. `[\\]`), you need to escape twice. Thus, to match a verbatim
-/// backslash, you would need to write `{regex("\\\\")}`.
-///
-/// - returns: regex
-///
/// Display: Regex
/// Category: construct
+/// Returns: regex
#[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())
+pub fn regex(
+ /// The regular expression as a string.
+ ///
+ /// Most regex escape sequences just work because they are not valid Typst
+ /// escape sequences. To produce regex escape sequences that are also valid in
+ /// Typst (e.g. `[\\]`), you need to escape twice. Thus, to match a verbatim
+ /// backslash, you would need to write `{regex("\\\\")}`.
+ regex: Spanned<EcoString>,
+) -> Value {
+ Regex::new(&regex.v).at(regex.span)?.into()
}
/// Create an array consisting of a sequence of numbers.
@@ -420,33 +401,30 @@ pub fn regex(args: &mut Args) -> SourceResult<Value> {
/// #range(5, 2, step: -1)
/// ```
///
-/// ## Parameters
-/// - start: `i64` (positional)
-/// The start of the range (inclusive).
-///
-/// - end: `i64` (positional, required)
-/// The end of the range (exclusive).
-///
-/// - step: `i64` (named)
-/// The distance between the generated numbers.
-///
-/// - returns: array
-///
/// Display: Range
/// Category: construct
+/// Returns: array
#[func]
-pub fn range(args: &mut Args) -> SourceResult<Value> {
+pub fn range(
+ /// The start of the range (inclusive).
+ #[external]
+ #[default]
+ start: i64,
+ /// The end of the range (exclusive).
+ #[external]
+ end: i64,
+ /// The distance between the generated numbers.
+ #[named]
+ #[default(NonZeroI64::new(1).unwrap())]
+ step: NonZeroI64,
+) -> Value {
let first = args.expect::<i64>("end")?;
let (start, end) = match args.eat::<i64>()? {
Some(second) => (first, second),
None => (0, first),
};
- let step: i64 = match args.named("step")? {
- Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"),
- Some(Spanned { v, .. }) => v,
- None => 1,
- };
+ let step = step.get();
let mut x = start;
let mut array = Array::new();
@@ -456,5 +434,5 @@ pub fn range(args: &mut Args) -> SourceResult<Value> {
x += step;
}
- Ok(Value::Array(array))
+ Value::Array(array)
}
diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs
index 90d72ade..7addff78 100644
--- a/library/src/compute/data.rs
+++ b/library/src/compute/data.rs
@@ -16,25 +16,21 @@ use crate::prelude::*;
/// #raw(text, lang: "html")
/// ```
///
-/// ## Parameters
-/// - path: `EcoString` (positional, required)
-/// Path to a file.
-///
-/// - returns: string
-///
/// Display: Plain text
/// Category: data-loading
+/// Returns: string
#[func]
-pub fn read(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: path, span } = args.expect::<Spanned<EcoString>>("path to file")?;
-
+pub fn read(
+ /// Path to a file.
+ path: Spanned<EcoString>,
+) -> Value {
+ let Spanned { v: path, span } = path;
let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&path).at(span)?;
-
let text = String::from_utf8(data.to_vec())
.map_err(|_| "file is not valid utf-8")
.at(span)?;
- Ok(Value::Str(text.into()))
+ Value::Str(text.into())
}
/// Read structured data from a CSV file.
@@ -55,33 +51,27 @@ pub fn read(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
/// )
/// ```
///
-/// ## Parameters
-/// - path: `EcoString` (positional, required)
-/// Path to a CSV file.
-///
-/// - delimiter: `Delimiter` (named)
-/// The delimiter that separates columns in the CSV file.
-/// Must be a single ASCII character.
-/// Defaults to a comma.
-///
-/// - returns: array
-///
/// Display: CSV
/// Category: data-loading
+/// Returns: array
#[func]
-pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: path, span } =
- args.expect::<Spanned<EcoString>>("path to csv file")?;
-
+pub fn csv(
+ /// Path to a CSV file.
+ path: Spanned<EcoString>,
+ /// The delimiter that separates columns in the CSV file.
+ /// Must be a single ASCII character.
+ /// Defaults to a comma.
+ #[named]
+ #[default]
+ delimiter: Delimiter,
+) -> Value {
+ let Spanned { v: path, span } = path;
let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&path).at(span)?;
let mut builder = csv::ReaderBuilder::new();
builder.has_headers(false);
-
- if let Some(delimiter) = args.named::<Delimiter>("delimiter")? {
- builder.delimiter(delimiter.0);
- }
+ builder.delimiter(delimiter.0);
let mut reader = builder.from_reader(data.as_slice());
let mut array = Array::new();
@@ -92,7 +82,7 @@ pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
array.push(Value::Array(sub))
}
- Ok(Value::Array(array))
+ Value::Array(array)
}
/// The delimiter to use when parsing CSV files.
@@ -115,6 +105,12 @@ cast_from_value! {
},
}
+impl Default for Delimiter {
+ fn default() -> Self {
+ Self(b',')
+ }
+}
+
/// Format the user-facing CSV error message.
fn format_csv_error(error: csv::Error) -> String {
match error.kind() {
@@ -170,25 +166,20 @@ fn format_csv_error(error: csv::Error) -> String {
/// #forecast(json("tuesday.json"))
/// ```
///
-/// ## Parameters
-/// - path: `EcoString` (positional, required)
-/// Path to a JSON file.
-///
-/// - returns: dictionary or array
-///
/// Display: JSON
/// Category: data-loading
+/// Returns: array or dictionary
#[func]
-pub fn json(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: path, span } =
- args.expect::<Spanned<EcoString>>("path to json file")?;
-
+pub fn json(
+ /// Path to a JSON file.
+ path: Spanned<EcoString>,
+) -> Value {
+ let Spanned { v: path, span } = path;
let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&path).at(span)?;
let value: serde_json::Value =
serde_json::from_slice(&data).map_err(format_json_error).at(span)?;
-
- Ok(convert_json(value))
+ convert_json(value)
}
/// Convert a JSON value to a Typst value.
@@ -268,26 +259,20 @@ fn format_json_error(error: serde_json::Error) -> String {
/// }
/// ```
///
-/// ## Parameters
-/// - path: `EcoString` (positional, required)
-/// Path to an XML file.
-///
-/// - returns: array
-///
/// Display: XML
/// Category: data-loading
+/// Returns: array
#[func]
-pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: path, span } =
- args.expect::<Spanned<EcoString>>("path to xml file")?;
-
+pub fn xml(
+ /// Path to an XML file.
+ path: Spanned<EcoString>,
+) -> Value {
+ let Spanned { v: path, span } = path;
let path = vm.locate(&path).at(span)?;
let data = vm.world().file(&path).at(span)?;
let text = std::str::from_utf8(&data).map_err(FileError::from).at(span)?;
-
let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?;
-
- Ok(convert_xml(document.root()))
+ convert_xml(document.root())
}
/// Convert an XML node to a Typst value.
diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs
index 710ec68e..41a6bc35 100644
--- a/library/src/compute/foundations.rs
+++ b/library/src/compute/foundations.rs
@@ -14,17 +14,15 @@ use crate::prelude::*;
/// #type(x => x + 1)
/// ```
///
-/// ## Parameters
-/// - value: `Value` (positional, required)
-/// The value whose type's to determine.
-///
-/// - returns: string
-///
/// Display: Type
/// Category: foundations
+/// Returns: string
#[func]
-pub fn type_(args: &mut Args) -> SourceResult<Value> {
- Ok(args.expect::<Value>("value")?.type_name().into())
+pub fn type_(
+ /// The value whose type's to determine.
+ value: Value,
+) -> Value {
+ value.type_name().into()
}
/// The string representation of a value.
@@ -41,17 +39,15 @@ pub fn type_(args: &mut Args) -> SourceResult<Value> {
/// #[*Hi*] vs #repr([*Hi*])
/// ```
///
-/// ## Parameters
-/// - value: `Value` (positional, required)
-/// The value whose string representation to produce.
-///
-/// - returns: string
-///
/// Display: Representation
/// Category: foundations
+/// Returns: string
#[func]
-pub fn repr(args: &mut Args) -> SourceResult<Value> {
- Ok(args.expect::<Value>("value")?.repr().into())
+pub fn repr(
+ /// The value whose string representation to produce.
+ value: Value,
+) -> Value {
+ value.repr().into()
}
/// Fail with an error.
@@ -62,15 +58,16 @@ pub fn repr(args: &mut Args) -> SourceResult<Value> {
/// #panic("this is wrong")
/// ```
///
-/// ## Parameters
-/// - payload: `Value` (positional)
-/// The value (or message) to panic with.
-///
/// Display: Panic
/// Category: foundations
+/// Returns:
#[func]
-pub fn panic(args: &mut Args) -> SourceResult<Value> {
- match args.eat::<Value>()? {
+pub fn panic(
+ /// The value (or message) to panic with.
+ #[default]
+ payload: Option<Value>,
+) -> Value {
+ match payload {
Some(v) => bail!(args.span, "panicked with: {}", v.repr()),
None => bail!(args.span, "panicked"),
}
@@ -86,26 +83,26 @@ pub fn panic(args: &mut Args) -> SourceResult<Value> {
/// #assert(1 < 2, message: "math broke")
/// ```
///
-/// ## Parameters
-/// - condition: `bool` (positional, required)
-/// The condition that must be true for the assertion to pass.
-/// - message: `EcoString` (named)
-/// The error message when the assertion fails.
-///
/// Display: Assert
/// Category: foundations
+/// Returns:
#[func]
-pub fn assert(args: &mut Args) -> SourceResult<Value> {
- let check = args.expect::<bool>("condition")?;
- let message = args.named::<EcoString>("message")?;
- if !check {
+pub fn assert(
+ /// The condition that must be true for the assertion to pass.
+ condition: bool,
+ /// The error message when the assertion fails.
+ #[named]
+ #[default]
+ message: Option<EcoString>,
+) -> Value {
+ if !condition {
if let Some(message) = message {
bail!(args.span, "assertion failed: {}", message);
} else {
bail!(args.span, "assertion failed");
}
}
- Ok(Value::None)
+ Value::None
}
/// Evaluate a string as Typst code.
@@ -119,18 +116,16 @@ pub fn assert(args: &mut Args) -> SourceResult<Value> {
/// #eval("[*Strong text*]")
/// ```
///
-/// ## Parameters
-/// - source: `String` (positional, required)
-/// A string of Typst code to evaluate.
-///
-/// The code in the string cannot interact with the file system.
-///
-/// - returns: any
-///
/// Display: Evaluate
/// Category: foundations
+/// Returns: any
#[func]
-pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
- let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?;
- typst::eval::eval_code_str(vm.world(), &text, span)
+pub fn eval(
+ /// A string of Typst code to evaluate.
+ ///
+ /// The code in the string cannot interact with the file system.
+ source: Spanned<String>,
+) -> Value {
+ let Spanned { v: text, span } = source;
+ typst::eval::eval_code_str(vm.world(), &text, span)?
}
diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs
index 88815dc9..cf6d08f2 100644
--- a/library/src/layout/align.rs
+++ b/library/src/layout/align.rs
@@ -53,7 +53,6 @@ pub struct AlignNode {
pub alignment: Axes<Option<GenAlign>>,
/// The content to align.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs
index 58b369c6..7704e9c4 100644
--- a/library/src/layout/columns.rs
+++ b/library/src/layout/columns.rs
@@ -45,7 +45,6 @@ pub struct ColumnsNode {
pub gutter: Rel<Length>,
/// The content that should be layouted into the columns.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs
index 31a80aa2..009063f0 100644
--- a/library/src/layout/container.rs
+++ b/library/src/layout/container.rs
@@ -181,20 +181,6 @@ impl Layout for BoxNode {
/// More text.
/// ```
///
-/// ## Parameters
-/// - spacing: `Spacing` (named, settable)
-/// The spacing around this block. This is shorthand to set `above` and
-/// `below` to the same value.
-///
-/// ```example
-/// #set align(center)
-/// #show math.formula: set block(above: 8pt, below: 16pt)
-///
-/// This sum of $x$ and $y$:
-/// $ x + y = z $
-/// A second paragraph.
-/// ```
-///
/// Display: Block
/// Category: layout
#[node(Layout)]
@@ -270,6 +256,20 @@ pub struct BlockNode {
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
+ /// The spacing around this block. This is shorthand to set `above` and
+ /// `below` to the same value.
+ ///
+ /// ```example
+ /// #set align(center)
+ /// #show math.formula: set block(above: 8pt, below: 16pt)
+ ///
+ /// This sum of $x$ and $y$:
+ /// $ x + y = z $
+ /// A second paragraph.
+ /// ```
+ #[external]
+ pub spacing: Spacing,
+
/// The spacing between this block and its predecessor. Takes precedence
/// over `spacing`. Can be used in combination with a show rule to adjust
/// the spacing around arbitrary block-level elements.
diff --git a/library/src/layout/enum.rs b/library/src/layout/enum.rs
index ee09d339..33b297e7 100644
--- a/library/src/layout/enum.rs
+++ b/library/src/layout/enum.rs
@@ -228,7 +228,6 @@ pub struct EnumItem {
pub number: Option<NonZeroUsize>,
/// The item's body.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs
index 34514eac..b6e86afd 100644
--- a/library/src/layout/grid.rs
+++ b/library/src/layout/grid.rs
@@ -59,12 +59,6 @@ use super::Sizing;
/// )
/// ```
///
-/// ## Parameters
-/// - gutter: `TrackSizings` (named, settable)
-/// Defines the gaps between rows & columns.
-///
-/// If there are more gutters than defined sizes, the last gutter is repeated.
-///
/// Display: Grid
/// Category: layout
#[node(Layout)]
@@ -83,6 +77,12 @@ pub struct GridNode {
/// repeated until there are no more cells.
pub rows: TrackSizings,
+ /// Defines the gaps between rows & columns.
+ ///
+ /// If there are more gutters than defined sizes, the last gutter is repeated.
+ #[external]
+ pub gutter: TrackSizings,
+
/// Defines the gaps between columns. Takes precedence over `gutter`.
#[parse(
let gutter = args.named("gutter")?;
diff --git a/library/src/layout/hide.rs b/library/src/layout/hide.rs
index 43d9a2a8..62628445 100644
--- a/library/src/layout/hide.rs
+++ b/library/src/layout/hide.rs
@@ -18,7 +18,6 @@ use crate::prelude::*;
#[node(Show)]
pub struct HideNode {
/// The content to hide.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/layout/list.rs b/library/src/layout/list.rs
index 57b653c0..6d605868 100644
--- a/library/src/layout/list.rs
+++ b/library/src/layout/list.rs
@@ -163,7 +163,6 @@ impl Layout for ListNode {
#[node]
pub struct ListItem {
/// The item's body.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs
index 7d0bbe04..e8171560 100644
--- a/library/src/layout/pad.rs
+++ b/library/src/layout/pad.rs
@@ -15,16 +15,6 @@ use crate::prelude::*;
/// measured in words per minute._
/// ```
///
-/// ## Parameters
-/// - x: `Rel<Length>` (named, settable)
-/// The horizontal padding. Both `left` and `right` take precedence over this.
-///
-/// - y: `Rel<Length>` (named, settable)
-/// The vertical padding. Both `top` and `bottom` take precedence over this.
-///
-/// - rest: `Rel<Length>` (named, settable)
-/// The padding for all sides. All other parameters take precedence over this.
-///
/// Display: Padding
/// Category: layout
#[node(Layout)]
@@ -50,8 +40,21 @@ pub struct PadNode {
#[parse(args.named("bottom")?.or(y))]
pub bottom: Rel<Length>,
+ /// The horizontal padding. Both `left` and `right` take precedence over
+ /// this.
+ #[external]
+ pub x: Rel<Length>,
+
+ /// The vertical padding. Both `top` and `bottom` take precedence over this.
+ #[external]
+ pub y: Rel<Length>,
+
+ /// The padding for all sides. All other parameters take precedence over
+ /// this.
+ #[external]
+ pub rest: Rel<Length>,
+
/// The content to pad at the sides.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs
index 5fe3c90a..e469bf10 100644
--- a/library/src/layout/page.rs
+++ b/library/src/layout/page.rs
@@ -20,15 +20,15 @@ use crate::prelude::*;
/// There you go, US friends!
/// ```
///
-/// ## Parameters
-/// - paper: `Paper` (positional, named, settable)
-/// A standard paper size to set width and height. When this is not specified,
-/// Typst defaults to `{"a4"}` paper.
-///
/// Display: Page
/// Category: layout
#[node]
pub struct PageNode {
+ /// A standard paper size to set width and height. When this is not
+ /// specified, Typst defaults to `{"a4"}` paper.
+ #[external]
+ pub paper: Paper,
+
/// The width of the page.
///
/// ```example
@@ -232,7 +232,6 @@ pub struct PageNode {
/// Multiple pages will be created if the content does not fit on a single
/// page. A new page with the page properties prior to the function invocation
/// will be created after the body has been typeset.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs
index 93cca452..e5644a2e 100644
--- a/library/src/layout/par.rs
+++ b/library/src/layout/par.rs
@@ -35,10 +35,6 @@ use crate::text::{
/// three integers. Then, we ...
/// ```
///
-/// ## Parameters
-/// - body: `Content` (positional, required)
-/// The contents of the paragraph.
-///
/// Display: Paragraph
/// Category: layout
#[node(Construct)]
@@ -99,6 +95,10 @@ pub struct ParNode {
#[default]
pub linebreaks: Smart<Linebreaks>,
+ /// The contents of the paragraph.
+ #[external]
+ pub body: Content,
+
/// The paragraph's children.
#[internal]
#[variadic]
diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs
index 8d7aa229..bfabd0f3 100644
--- a/library/src/layout/place.rs
+++ b/library/src/layout/place.rs
@@ -49,7 +49,6 @@ pub struct PlaceNode {
pub dy: Rel<Length>,
/// The content to place.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/layout/repeat.rs b/library/src/layout/repeat.rs
index 0fd9ad83..c8f63ac3 100644
--- a/library/src/layout/repeat.rs
+++ b/library/src/layout/repeat.rs
@@ -26,7 +26,6 @@ use super::AlignNode;
#[node(Layout)]
pub struct RepeatNode {
/// The content to repeat.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs
index c11a2f06..dbdf0c11 100644
--- a/library/src/layout/spacing.rs
+++ b/library/src/layout/spacing.rs
@@ -24,7 +24,6 @@ use crate::prelude::*;
#[node(Behave)]
pub struct HNode {
/// How much spacing to insert.
- #[positional]
#[required]
pub amount: Spacing,
@@ -84,31 +83,30 @@ impl Behave for HNode {
/// )
/// ```
///
-/// ## Parameters
-/// - weak: `bool` (named, settable)
-/// If true, the spacing collapses at the start or end of a flow. Moreover,
-/// from multiple adjacent weak spacings all but the largest one collapse.
-/// Weak spacings will always collapse adjacent paragraph spacing, even if the
-/// paragraph spacing is larger.
-///
-/// ```example
-/// The following theorem is
-/// foundational to the field:
-/// #v(4pt, weak: true)
-/// $ x^2 + y^2 = r^2 $
-/// #v(4pt, weak: true)
-/// The proof is simple:
-/// ```
-///
/// Display: Spacing (V)
/// Category: layout
#[node(Behave)]
pub struct VNode {
/// How much spacing to insert.
- #[positional]
#[required]
pub amount: Spacing,
+ /// If true, the spacing collapses at the start or end of a flow. Moreover,
+ /// from multiple adjacent weak spacings all but the largest one collapse.
+ /// Weak spacings will always collapse adjacent paragraph spacing, even if the
+ /// paragraph spacing is larger.
+ ///
+ /// ```example
+ /// The following theorem is
+ /// foundational to the field:
+ /// #v(4pt, weak: true)
+ /// $ x^2 + y^2 = r^2 $
+ /// #v(4pt, weak: true)
+ /// The proof is simple:
+ /// ```
+ #[external]
+ pub weak: bool,
+
/// The node's weakness level, see also [`Behaviour`].
#[internal]
#[parse(args.named("weak")?.map(|v: bool| v as usize))]
diff --git a/library/src/layout/table.rs b/library/src/layout/table.rs
index 59635119..fabe8c33 100644
--- a/library/src/layout/table.rs
+++ b/library/src/layout/table.rs
@@ -29,35 +29,33 @@ use crate::prelude::*;
/// )
/// ```
///
-/// ## Parameters
-/// - gutter: `TrackSizings` (named, settable)
-/// Defines the gaps between rows & columns.
-/// See the [grid documentation]($func/grid) for more information on gutters.
-///
/// Display: Table
/// Category: layout
#[node(Layout)]
pub struct TableNode {
- /// Defines the column sizes.
- /// See the [grid documentation]($func/grid) for more information on track
- /// sizing.
+ /// Defines the column sizes. See the [grid documentation]($func/grid) for
+ /// more information on track sizing.
pub columns: TrackSizings,
- /// Defines the row sizes.
- /// See the [grid documentation]($func/grid) for more information on track
- /// sizing.
+ /// Defines the row sizes. See the [grid documentation]($func/grid) for more
+ /// information on track sizing.
pub rows: TrackSizings,
- /// Defines the gaps between columns. Takes precedence over `gutter`.
- /// See the [grid documentation]($func/grid) for more information on gutters.
+ /// Defines the gaps between rows & columns. See the [grid
+ /// documentation]($func/grid) for more information on gutters.
+ #[external]
+ pub gutter: TrackSizings,
+
+ /// Defines the gaps between columns. Takes precedence over `gutter`. See
+ /// the [grid documentation]($func/grid) for more information on gutters.
#[parse(
let gutter = args.named("gutter")?;
args.named("column-gutter")?.or_else(|| gutter.clone())
)]
pub column_gutter: TrackSizings,
- /// Defines the gaps between rows. Takes precedence over `gutter`.
- /// See the [grid documentation]($func/grid) for more information on gutters.
+ /// Defines the gaps between rows. Takes precedence over `gutter`. See the
+ /// [grid documentation]($func/grid) for more information on gutters.
#[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
pub row_gutter: TrackSizings,
diff --git a/library/src/layout/terms.rs b/library/src/layout/terms.rs
index 8ab4edc6..b2f45446 100644
--- a/library/src/layout/terms.rs
+++ b/library/src/layout/terms.rs
@@ -126,12 +126,10 @@ impl Layout for TermsNode {
#[node]
pub struct TermItem {
/// The term described by the list item.
- #[positional]
#[required]
pub term: Content,
/// The description of the term.
- #[positional]
#[required]
pub description: Content,
}
diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs
index 4521da32..2afe8201 100644
--- a/library/src/layout/transform.rs
+++ b/library/src/layout/transform.rs
@@ -32,7 +32,6 @@ pub struct MoveNode {
pub dy: Rel<Length>,
/// The content to move.
- #[positional]
#[required]
pub body: Content,
}
@@ -101,7 +100,6 @@ pub struct RotateNode {
pub origin: Axes<Option<GenAlign>>,
/// The content to rotate.
- #[positional]
#[required]
pub body: Content,
}
@@ -170,7 +168,6 @@ pub struct ScaleNode {
pub origin: Axes<Option<GenAlign>>,
/// The content to scale.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/lib.rs b/library/src/lib.rs
index bcbd703d..ad8a2ac4 100644
--- a/library/src/lib.rs
+++ b/library/src/lib.rs
@@ -29,91 +29,91 @@ fn global(math: Module, calc: Module) -> Module {
let mut global = Scope::deduplicating();
// Text.
- global.def_func::<text::TextNode>("text");
- global.def_func::<text::LinebreakNode>("linebreak");
- global.def_func::<text::SmartQuoteNode>("smartquote");
- global.def_func::<text::StrongNode>("strong");
- global.def_func::<text::EmphNode>("emph");
- global.def_func::<text::LowerFunc>("lower");
- global.def_func::<text::UpperFunc>("upper");
- global.def_func::<text::SmallcapsFunc>("smallcaps");
- global.def_func::<text::SubNode>("sub");
- global.def_func::<text::SuperNode>("super");
- global.def_func::<text::UnderlineNode>("underline");
- global.def_func::<text::StrikeNode>("strike");
- global.def_func::<text::OverlineNode>("overline");
- global.def_func::<text::RawNode>("raw");
- global.def_func::<text::LoremFunc>("lorem");
+ global.define("text", text::TextNode::func());
+ global.define("linebreak", text::LinebreakNode::func());
+ global.define("smartquote", text::SmartQuoteNode::func());
+ global.define("strong", text::StrongNode::func());
+ global.define("emph", text::EmphNode::func());
+ global.define("lower", text::lower);
+ global.define("upper", text::upper);
+ global.define("smallcaps", text::smallcaps);
+ global.define("sub", text::SubNode::func());
+ global.define("super", text::SuperNode::func());
+ global.define("underline", text::UnderlineNode::func());
+ global.define("strike", text::StrikeNode::func());
+ global.define("overline", text::OverlineNode::func());
+ global.define("raw", text::RawNode::func());
+ global.define("lorem", text::lorem);
// Math.
global.define("math", math);
// Layout.
- global.def_func::<layout::PageNode>("page");
- global.def_func::<layout::PagebreakNode>("pagebreak");
- global.def_func::<layout::VNode>("v");
- global.def_func::<layout::ParNode>("par");
- global.def_func::<layout::ParbreakNode>("parbreak");
- global.def_func::<layout::HNode>("h");
- global.def_func::<layout::BoxNode>("box");
- global.def_func::<layout::BlockNode>("block");
- global.def_func::<layout::ListNode>("list");
- global.def_func::<layout::EnumNode>("enum");
- global.def_func::<layout::TermsNode>("terms");
- global.def_func::<layout::TableNode>("table");
- global.def_func::<layout::StackNode>("stack");
- global.def_func::<layout::GridNode>("grid");
- global.def_func::<layout::ColumnsNode>("columns");
- global.def_func::<layout::ColbreakNode>("colbreak");
- global.def_func::<layout::PlaceNode>("place");
- global.def_func::<layout::AlignNode>("align");
- global.def_func::<layout::PadNode>("pad");
- global.def_func::<layout::RepeatNode>("repeat");
- global.def_func::<layout::MoveNode>("move");
- global.def_func::<layout::ScaleNode>("scale");
- global.def_func::<layout::RotateNode>("rotate");
- global.def_func::<layout::HideNode>("hide");
+ global.define("page", layout::PageNode::func());
+ global.define("pagebreak", layout::PagebreakNode::func());
+ global.define("v", layout::VNode::func());
+ global.define("par", layout::ParNode::func());
+ global.define("parbreak", layout::ParbreakNode::func());
+ global.define("h", layout::HNode::func());
+ global.define("box", layout::BoxNode::func());
+ global.define("block", layout::BlockNode::func());
+ global.define("list", layout::ListNode::func());
+ global.define("enum", layout::EnumNode::func());
+ global.define("terms", layout::TermsNode::func());
+ global.define("table", layout::TableNode::func());
+ global.define("stack", layout::StackNode::func());
+ global.define("grid", layout::GridNode::func());
+ global.define("columns", layout::ColumnsNode::func());
+ global.define("colbreak", layout::ColbreakNode::func());
+ global.define("place", layout::PlaceNode::func());
+ global.define("align", layout::AlignNode::func());
+ global.define("pad", layout::PadNode::func());
+ global.define("repeat", layout::RepeatNode::func());
+ global.define("move", layout::MoveNode::func());
+ global.define("scale", layout::ScaleNode::func());
+ global.define("rotate", layout::RotateNode::func());
+ global.define("hide", layout::HideNode::func());
// Visualize.
- global.def_func::<visualize::ImageNode>("image");
- global.def_func::<visualize::LineNode>("line");
- global.def_func::<visualize::RectNode>("rect");
- global.def_func::<visualize::SquareNode>("square");
- global.def_func::<visualize::EllipseNode>("ellipse");
- global.def_func::<visualize::CircleNode>("circle");
+ global.define("image", visualize::ImageNode::func());
+ global.define("line", visualize::LineNode::func());
+ global.define("rect", visualize::RectNode::func());
+ global.define("square", visualize::SquareNode::func());
+ global.define("ellipse", visualize::EllipseNode::func());
+ global.define("circle", visualize::CircleNode::func());
// Meta.
- global.def_func::<meta::DocumentNode>("document");
- global.def_func::<meta::RefNode>("ref");
- global.def_func::<meta::LinkNode>("link");
- global.def_func::<meta::OutlineNode>("outline");
- global.def_func::<meta::HeadingNode>("heading");
- global.def_func::<meta::NumberingFunc>("numbering");
+ global.define("document", meta::DocumentNode::func());
+ global.define("ref", meta::RefNode::func());
+ global.define("link", meta::LinkNode::func());
+ global.define("outline", meta::OutlineNode::func());
+ global.define("heading", meta::HeadingNode::func());
+ global.define("numbering", meta::numbering);
// Symbols.
global.define("sym", symbols::sym());
global.define("emoji", symbols::emoji());
// Compute.
- global.def_func::<compute::TypeFunc>("type");
- global.def_func::<compute::ReprFunc>("repr");
- global.def_func::<compute::PanicFunc>("panic");
- global.def_func::<compute::AssertFunc>("assert");
- global.def_func::<compute::EvalFunc>("eval");
- global.def_func::<compute::IntFunc>("int");
- global.def_func::<compute::FloatFunc>("float");
- global.def_func::<compute::LumaFunc>("luma");
- global.def_func::<compute::RgbFunc>("rgb");
- global.def_func::<compute::CmykFunc>("cmyk");
- global.def_func::<compute::SymbolFunc>("symbol");
- global.def_func::<compute::StrFunc>("str");
- global.def_func::<compute::LabelFunc>("label");
- global.def_func::<compute::RegexFunc>("regex");
- global.def_func::<compute::RangeFunc>("range");
- global.def_func::<compute::ReadFunc>("read");
- global.def_func::<compute::CsvFunc>("csv");
- global.def_func::<compute::JsonFunc>("json");
- global.def_func::<compute::XmlFunc>("xml");
+ global.define("type", compute::type_);
+ global.define("repr", compute::repr);
+ global.define("panic", compute::panic);
+ global.define("assert", compute::assert);
+ global.define("eval", compute::eval);
+ global.define("int", compute::int);
+ global.define("float", compute::float);
+ global.define("luma", compute::luma);
+ global.define("rgb", compute::rgb);
+ global.define("cmyk", compute::cmyk);
+ global.define("symbol", compute::symbol);
+ global.define("str", compute::str);
+ global.define("label", compute::label);
+ global.define("regex", compute::regex);
+ global.define("range", compute::range);
+ global.define("read", compute::read);
+ global.define("csv", compute::csv);
+ global.define("json", compute::json);
+ global.define("xml", compute::xml);
// Calc.
global.define("calc", calc);
diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs
index 164247de..03caf411 100644
--- a/library/src/math/accent.rs
+++ b/library/src/math/accent.rs
@@ -24,7 +24,6 @@ pub struct AccentNode {
/// ```example
/// $arrow(A B C)$
/// ```
- #[positional]
#[required]
pub base: Content,
@@ -47,7 +46,6 @@ pub struct AccentNode {
/// | Caron | `caron` | `ˇ` |
/// | Right arrow | `arrow`, `->` | `→` |
/// | Left arrow | `arrow.l`, `<-` | `←` |
- #[positional]
#[required]
pub accent: Accent,
}
diff --git a/library/src/math/attach.rs b/library/src/math/attach.rs
index c2d7703d..7d8749f2 100644
--- a/library/src/math/attach.rs
+++ b/library/src/math/attach.rs
@@ -16,7 +16,6 @@ use super::*;
#[node(LayoutMath)]
pub struct AttachNode {
/// The base to which things are attached.
- #[positional]
#[required]
pub base: Content,
@@ -79,7 +78,6 @@ impl LayoutMath for AttachNode {
#[node(LayoutMath)]
pub struct ScriptsNode {
/// The base to attach the scripts to.
- #[positional]
#[required]
pub body: Content,
}
@@ -102,7 +100,6 @@ impl LayoutMath for ScriptsNode {
#[node(LayoutMath)]
pub struct LimitsNode {
/// The base to attach the limits to.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/math/delimited.rs b/library/src/math/delimited.rs
index f9d22c43..6f468af7 100644
--- a/library/src/math/delimited.rs
+++ b/library/src/math/delimited.rs
@@ -24,7 +24,6 @@ pub struct LrNode {
pub size: Smart<Rel<Length>>,
/// The delimited content, including the delimiters.
- #[positional]
#[required]
#[parse(
let mut body = Content::empty();
@@ -111,15 +110,15 @@ fn scale(
/// $ floor(x/2) $
/// ```
///
-/// ## Parameters
-/// - body: `Content` (positional, required)
-/// The expression to floor.
-///
/// Display: Floor
/// Category: math
+/// Returns: content
#[func]
-pub fn floor(args: &mut Args) -> SourceResult<Value> {
- delimited(args, '⌊', '⌋')
+pub fn floor(
+ /// The expression to floor.
+ body: Content,
+) -> Value {
+ delimited(body, '⌊', '⌋')
}
/// Ceil an expression.
@@ -129,15 +128,15 @@ pub fn floor(args: &mut Args) -> SourceResult<Value> {
/// $ ceil(x/2) $
/// ```
///
-/// ## Parameters
-/// - body: `Content` (positional, required)
-/// The expression to ceil.
-///
/// Display: Ceil
/// Category: math
+/// Returns: content
#[func]
-pub fn ceil(args: &mut Args) -> SourceResult<Value> {
- delimited(args, '⌈', '⌉')
+pub fn ceil(
+ /// The expression to ceil.
+ body: Content,
+) -> Value {
+ delimited(body, '⌈', '⌉')
}
/// Take the absolute value of an expression.
@@ -147,15 +146,16 @@ pub fn ceil(args: &mut Args) -> SourceResult<Value> {
/// $ abs(x/2) $
/// ```
///
-/// ## Parameters
-/// - body: `Content` (positional, required)
-/// The expression to take the absolute value of.
///
/// Display: Abs
/// Category: math
+/// Returns: content
#[func]
-pub fn abs(args: &mut Args) -> SourceResult<Value> {
- delimited(args, '|', '|')
+pub fn abs(
+ /// The expression to take the absolute value of.
+ body: Content,
+) -> Value {
+ delimited(body, '|', '|')
}
/// Take the norm of an expression.
@@ -165,24 +165,24 @@ pub fn abs(args: &mut Args) -> SourceResult<Value> {
/// $ norm(x/2) $
/// ```
///
-/// ## Parameters
-/// - body: `Content` (positional, required)
-/// The expression to take the norm of.
-///
/// Display: Norm
/// Category: math
+/// Returns: content
#[func]
-pub fn norm(args: &mut Args) -> SourceResult<Value> {
- delimited(args, '‖', '‖')
+pub fn norm(
+ /// The expression to take the norm of.
+ body: Content,
+) -> Value {
+ delimited(body, '‖', '‖')
}
-fn delimited(args: &mut Args, left: char, right: char) -> SourceResult<Value> {
- Ok(Value::Content(
+fn delimited(body: Content, left: char, right: char) -> Value {
+ Value::Content(
LrNode::new(Content::sequence(vec![
TextNode::packed(left),
- args.expect::<Content>("body")?,
+ body,
TextNode::packed(right),
]))
.pack(),
- ))
+ )
}
diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs
index c1f4065b..0624832a 100644
--- a/library/src/math/frac.rs
+++ b/library/src/math/frac.rs
@@ -22,12 +22,10 @@ const FRAC_AROUND: Em = Em::new(0.1);
#[node(LayoutMath)]
pub struct FracNode {
/// The fraction's numerator.
- #[positional]
#[required]
pub num: Content,
/// The fraction's denominator.
- #[positional]
#[required]
pub denom: Content,
}
@@ -50,12 +48,10 @@ impl LayoutMath for FracNode {
#[node(LayoutMath)]
pub struct BinomNode {
/// The binomial's upper index.
- #[positional]
#[required]
pub upper: Content,
/// The binomial's lower index.
- #[positional]
#[required]
pub lower: Content,
}
diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs
index 0c9dc338..8f89e028 100644
--- a/library/src/math/mod.rs
+++ b/library/src/math/mod.rs
@@ -47,52 +47,52 @@ use crate::text::{
/// Create a module with all math definitions.
pub fn module() -> Module {
let mut math = Scope::deduplicating();
- math.def_func::<FormulaNode>("formula");
- math.def_func::<TextNode>("text");
+ math.define("formula", FormulaNode::func());
+ math.define("text", TextNode::func());
// Grouping.
- math.def_func::<LrNode>("lr");
- math.def_func::<AbsFunc>("abs");
- math.def_func::<NormFunc>("norm");
- math.def_func::<FloorFunc>("floor");
- math.def_func::<CeilFunc>("ceil");
+ math.define("lr", LrNode::func());
+ math.define("abs", abs);
+ math.define("norm", norm);
+ math.define("floor", floor);
+ math.define("ceil", ceil);
// Attachments and accents.
- math.def_func::<AttachNode>("attach");
- math.def_func::<ScriptsNode>("scripts");
- math.def_func::<LimitsNode>("limits");
- math.def_func::<AccentNode>("accent");
- math.def_func::<UnderlineNode>("underline");
- math.def_func::<OverlineNode>("overline");
- math.def_func::<UnderbraceNode>("underbrace");
- math.def_func::<OverbraceNode>("overbrace");
- math.def_func::<UnderbracketNode>("underbracket");
- math.def_func::<OverbracketNode>("overbracket");
+ math.define("attach", AttachNode::func());
+ math.define("scripts", ScriptsNode::func());
+ math.define("limits", LimitsNode::func());
+ math.define("accent", AccentNode::func());
+ math.define("underline", UnderlineNode::func());
+ math.define("overline", OverlineNode::func());
+ math.define("underbrace", UnderbraceNode::func());
+ math.define("overbrace", OverbraceNode::func());
+ math.define("underbracket", UnderbracketNode::func());
+ math.define("overbracket", OverbracketNode::func());
// Fractions and matrix-likes.
- math.def_func::<FracNode>("frac");
- math.def_func::<BinomNode>("binom");
- math.def_func::<VecNode>("vec");
- math.def_func::<MatNode>("mat");
- math.def_func::<CasesNode>("cases");
+ math.define("frac", FracNode::func());
+ math.define("binom", BinomNode::func());
+ math.define("vec", VecNode::func());
+ math.define("mat", MatNode::func());
+ math.define("cases", CasesNode::func());
// Roots.
- math.def_func::<SqrtNode>("sqrt");
- math.def_func::<RootNode>("root");
+ math.define("sqrt", SqrtNode::func());
+ math.define("root", RootNode::func());
// Styles.
- math.def_func::<UprightNode>("upright");
- math.def_func::<BoldNode>("bold");
- math.def_func::<ItalicNode>("italic");
- math.def_func::<SerifNode>("serif");
- math.def_func::<SansNode>("sans");
- math.def_func::<CalNode>("cal");
- math.def_func::<FrakNode>("frak");
- math.def_func::<MonoNode>("mono");
- math.def_func::<BbNode>("bb");
+ math.define("upright", UprightNode::func());
+ math.define("bold", BoldNode::func());
+ math.define("italic", ItalicNode::func());
+ math.define("serif", SerifNode::func());
+ math.define("sans", SansNode::func());
+ math.define("cal", CalNode::func());
+ math.define("frak", FrakNode::func());
+ math.define("mono", MonoNode::func());
+ math.define("bb", BbNode::func());
// Text operators.
- math.def_func::<OpNode>("op");
+ math.define("op", OpNode::func());
op::define(&mut math);
// Spacings.
@@ -139,7 +139,6 @@ pub struct FormulaNode {
pub block: bool,
/// The content of the formula.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/math/op.rs b/library/src/math/op.rs
index aa2e4cf7..bd81aa0e 100644
--- a/library/src/math/op.rs
+++ b/library/src/math/op.rs
@@ -23,7 +23,6 @@ use super::*;
#[node(LayoutMath)]
pub struct OpNode {
/// The operator's text.
- #[positional]
#[required]
pub text: EcoString,
diff --git a/library/src/math/root.rs b/library/src/math/root.rs
index e190c65f..cb01e6a1 100644
--- a/library/src/math/root.rs
+++ b/library/src/math/root.rs
@@ -12,7 +12,6 @@ use super::*;
#[node(LayoutMath)]
pub struct SqrtNode {
/// The expression to take the square root of.
- #[positional]
#[required]
pub radicand: Content,
}
@@ -35,12 +34,10 @@ impl LayoutMath for SqrtNode {
#[node(LayoutMath)]
pub struct RootNode {
/// Which root of the radicand to take.
- #[positional]
#[required]
index: Content,
/// The expression to take the root of.
- #[positional]
#[required]
radicand: Content,
}
diff --git a/library/src/math/style.rs b/library/src/math/style.rs
index 43c1f391..60bad6a5 100644
--- a/library/src/math/style.rs
+++ b/library/src/math/style.rs
@@ -12,7 +12,6 @@ use super::*;
#[node(LayoutMath)]
pub struct BoldNode {
/// The piece of formula to style.
- #[positional]
#[required]
pub body: Content,
}
@@ -38,7 +37,6 @@ impl LayoutMath for BoldNode {
#[node(LayoutMath)]
pub struct UprightNode {
/// The piece of formula to style.
- #[positional]
#[required]
pub body: Content,
}
@@ -61,7 +59,6 @@ impl LayoutMath for UprightNode {
#[node(LayoutMath)]
pub struct ItalicNode {
/// The piece of formula to style.
- #[positional]
#[required]
pub body: Content,
}
@@ -84,7 +81,6 @@ impl LayoutMath for ItalicNode {
#[node(LayoutMath)]
pub struct SerifNode {
/// The piece of formula to style.
- #[positional]
#[required]
pub body: Content,
}
@@ -110,7 +106,6 @@ impl LayoutMath for SerifNode {
#[node(LayoutMath)]
pub struct SansNode {
/// The piece of formula to style.
- #[positional]
#[required]
pub body: Content,
}
@@ -136,7 +131,6 @@ impl LayoutMath for SansNode {
#[node(LayoutMath)]
pub struct CalNode {
/// The piece of formula to style.
- #[positional]
#[required]
pub body: Content,
}
@@ -162,7 +156,6 @@ impl LayoutMath for CalNode {
#[node(LayoutMath)]
pub struct FrakNode {
/// The piece of formula to style.
- #[positional]
#[required]
pub body: Content,
}
@@ -188,7 +181,6 @@ impl LayoutMath for FrakNode {
#[node(LayoutMath)]
pub struct MonoNode {
/// The piece of formula to style.
- #[positional]
#[required]
pub body: Content,
}
@@ -219,7 +211,6 @@ impl LayoutMath for MonoNode {
#[node(LayoutMath)]
pub struct BbNode {
/// The piece of formula to style.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/math/underover.rs b/library/src/math/underover.rs
index 2aabf132..a723ae97 100644
--- a/library/src/math/underover.rs
+++ b/library/src/math/underover.rs
@@ -16,7 +16,6 @@ const BRACKET_GAP: Em = Em::new(0.25);
#[node(LayoutMath)]
pub struct UnderlineNode {
/// The content above the line.
- #[positional]
#[required]
pub body: Content,
}
@@ -39,7 +38,6 @@ impl LayoutMath for UnderlineNode {
#[node(LayoutMath)]
pub struct OverlineNode {
/// The content below the line.
- #[positional]
#[required]
pub body: Content,
}
@@ -62,7 +60,6 @@ impl LayoutMath for OverlineNode {
#[node(LayoutMath)]
pub struct UnderbraceNode {
/// The content above the brace.
- #[positional]
#[required]
pub body: Content,
@@ -89,7 +86,6 @@ impl LayoutMath for UnderbraceNode {
#[node(LayoutMath)]
pub struct OverbraceNode {
/// The content below the brace.
- #[positional]
#[required]
pub body: Content,
@@ -116,7 +112,6 @@ impl LayoutMath for OverbraceNode {
#[node(LayoutMath)]
pub struct UnderbracketNode {
/// The content above the bracket.
- #[positional]
#[required]
pub body: Content,
@@ -143,7 +138,6 @@ impl LayoutMath for UnderbracketNode {
#[node(LayoutMath)]
pub struct OverbracketNode {
/// The content below the bracket.
- #[positional]
#[required]
pub body: Content,
diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs
index 09cbc8b1..3a8d811c 100644
--- a/library/src/meta/heading.rs
+++ b/library/src/meta/heading.rs
@@ -74,7 +74,6 @@ pub struct HeadingNode {
pub outlined: bool,
/// The heading's title.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs
index d4d4d8ca..572c55b4 100644
--- a/library/src/meta/link.rs
+++ b/library/src/meta/link.rs
@@ -46,7 +46,6 @@ pub struct LinkNode {
/// ]
/// ```
///
- #[positional]
#[required]
#[parse(
let dest = args.expect::<Destination>("destination")?;
@@ -59,7 +58,6 @@ pub struct LinkNode {
/// The content that should become a link. If `dest` is an URL string, the
/// parameter can be omitted. In this case, the URL will be shown as the
/// link.
- #[positional]
#[required]
#[parse(match &dest {
Destination::Url(url) => match args.eat()? {
diff --git a/library/src/meta/numbering.rs b/library/src/meta/numbering.rs
index d3e1ee4d..4e6e1aed 100644
--- a/library/src/meta/numbering.rs
+++ b/library/src/meta/numbering.rs
@@ -27,46 +27,42 @@ use crate::text::Case;
/// )
/// ```
///
-/// ## Parameters
-/// - numbering: `Numbering` (positional, required)
-/// Defines how the numbering works.
-///
-/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are
-/// replaced by the number in the sequence, in the given case.
-///
-/// The `*` character means that symbols should be used to count, in the
-/// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six
-/// items, the number is represented using multiple symbols.
-///
-/// **Suffixes** are all characters after the last counting symbol. They are
-/// repeated as-is at the end of any rendered number.
-///
-/// **Prefixes** are all characters that are neither counting symbols nor
-/// suffixes. They are repeated as-is at in front of their rendered
-/// equivalent of their counting symbol.
-///
-/// This parameter can also be an arbitrary function that gets each number as
-/// an individual argument. When given a function, the `numbering` function
-/// just forwards the arguments to that function. While this is not
-/// particularly useful in itself, it means that you can just give arbitrary
-/// numberings to the `numbering` function without caring whether they are
-/// defined as a pattern or function.
-///
-/// - numbers: `NonZeroUsize` (positional, variadic)
-/// The numbers to apply the numbering to. Must be positive.
-///
-/// If `numbering` is a pattern and more numbers than counting symbols are
-/// given, the last counting symbol with its prefix is repeated.
-///
-/// - returns: any
-///
/// Display: Numbering
/// Category: meta
+/// Returns: any
#[func]
-pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
- let numbering = args.expect::<Numbering>("pattern or function")?;
- let numbers = args.all::<NonZeroUsize>()?;
- numbering.apply(vm.world(), &numbers)
+pub fn numbering(
+ /// Defines how the numbering works.
+ ///
+ /// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are
+ /// replaced by the number in the sequence, in the given case.
+ ///
+ /// The `*` character means that symbols should be used to count, in the
+ /// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six
+ /// items, the number is represented using multiple symbols.
+ ///
+ /// **Suffixes** are all characters after the last counting symbol. They are
+ /// repeated as-is at the end of any rendered number.
+ ///
+ /// **Prefixes** are all characters that are neither counting symbols nor
+ /// suffixes. They are repeated as-is at in front of their rendered
+ /// equivalent of their counting symbol.
+ ///
+ /// This parameter can also be an arbitrary function that gets each number as
+ /// an individual argument. When given a function, the `numbering` function
+ /// just forwards the arguments to that function. While this is not
+ /// particularly useful in itself, it means that you can just give arbitrary
+ /// numberings to the `numbering` function without caring whether they are
+ /// defined as a pattern or function.
+ numbering: Numbering,
+ /// The numbers to apply the numbering to. Must be positive.
+ ///
+ /// If `numbering` is a pattern and more numbers than counting symbols are
+ /// given, the last counting symbol with its prefix is repeated.
+ #[variadic]
+ numbers: Vec<NonZeroUsize>,
+) -> Value {
+ numbering.apply(vm.world(), &numbers)?
}
/// How to number an enumeration.
diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs
index 55051b5e..20354556 100644
--- a/library/src/meta/reference.rs
+++ b/library/src/meta/reference.rs
@@ -20,7 +20,6 @@ use crate::text::TextNode;
#[node(Show)]
pub struct RefNode {
/// The label that should be referenced.
- #[positional]
#[required]
pub target: EcoString,
}
diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs
index 2a552226..4dadf45a 100644
--- a/library/src/text/deco.rs
+++ b/library/src/text/deco.rs
@@ -61,7 +61,6 @@ pub struct UnderlineNode {
pub evade: bool,
/// The content to underline.
- #[positional]
#[required]
pub body: Content,
}
@@ -141,7 +140,6 @@ pub struct OverlineNode {
pub evade: bool,
/// The content to add a line over.
- #[positional]
#[required]
pub body: Content,
}
@@ -206,7 +204,6 @@ pub struct StrikeNode {
pub extent: Length,
/// The content to strike through.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs
index 64ab5bd2..60521f12 100644
--- a/library/src/text/misc.rs
+++ b/library/src/text/misc.rs
@@ -94,7 +94,6 @@ pub struct StrongNode {
pub delta: i64,
/// The content to strongly emphasize.
- #[positional]
#[required]
pub body: Content,
}
@@ -155,7 +154,6 @@ impl Fold for Delta {
#[node(Show)]
pub struct EmphNode {
/// The content to emphasize.
- #[positional]
#[required]
pub body: Content,
}
@@ -196,15 +194,15 @@ impl Fold for Toggle {
/// #lower[already low]
/// ```
///
-/// ## Parameters
-/// - text: `ToCase` (positional, required)
-/// The text to convert to lowercase.
-///
/// Display: Lowercase
/// Category: text
+/// Returns: string or content
#[func]
-pub fn lower(args: &mut Args) -> SourceResult<Value> {
- case(Case::Lower, args)
+pub fn lower(
+ /// The text to convert to lowercase.
+ text: ToCase,
+) -> Value {
+ case(text, Case::Lower)
}
/// Convert text or content to uppercase.
@@ -216,23 +214,23 @@ pub fn lower(args: &mut Args) -> SourceResult<Value> {
/// #upper[ALREADY HIGH]
/// ```
///
-/// ## Parameters
-/// - text: `ToCase` (positional, required)
-/// The text to convert to uppercase.
-///
/// Display: Uppercase
/// Category: text
+/// Returns: string or content
#[func]
-pub fn upper(args: &mut Args) -> SourceResult<Value> {
- case(Case::Upper, args)
+pub fn upper(
+ /// The text to convert to uppercase.
+ text: ToCase,
+) -> Value {
+ case(text, Case::Upper)
}
/// Change the case of text.
-fn case(case: Case, args: &mut Args) -> SourceResult<Value> {
- Ok(match args.expect("string or content")? {
+fn case(text: ToCase, case: Case) -> Value {
+ match text {
ToCase::Str(v) => Value::Str(case.apply(&v).into()),
ToCase::Content(v) => Value::Content(v.styled(TextNode::set_case(Some(case)))),
- })
+ }
}
/// A value whose case can be changed.
@@ -302,16 +300,15 @@ cast_to_value! {
/// #lorem(40)
/// ```
///
-/// ## Parameters
-/// - text: `Content` (positional, required)
-/// The text to display to small capitals.
-///
/// Display: Small Capitals
/// Category: text
+/// Returns: content
#[func]
-pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
- let body: Content = args.expect("content")?;
- Ok(Value::Content(body.styled(TextNode::set_smallcaps(true))))
+pub fn smallcaps(
+ /// The text to display to small capitals.
+ body: Content,
+) -> Value {
+ Value::Content(body.styled(TextNode::set_smallcaps(true)))
}
/// Create blind text.
@@ -330,16 +327,13 @@ pub fn smallcaps(args: &mut Args) -> SourceResult<Value> {
/// #lorem(15)
/// ```
///
-/// ## Parameters
-/// - words: `usize` (positional, required)
-/// The length of the blind text in words.
-///
-/// - returns: string
-///
/// Display: Blind Text
/// Category: text
+/// Returns: string
#[func]
-pub fn lorem(args: &mut Args) -> SourceResult<Value> {
- let words: usize = args.expect("number of words")?;
- Ok(Value::Str(lipsum::lipsum(words).replace("--", "–").into()))
+pub fn lorem(
+ /// The length of the blind text in words.
+ words: usize,
+) -> Value {
+ Value::Str(lipsum::lipsum(words).replace("--", "–").into())
}
diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs
index 83f4e2d7..a81ef3d7 100644
--- a/library/src/text/mod.rs
+++ b/library/src/text/mod.rs
@@ -38,10 +38,6 @@ use crate::prelude::*;
/// ])
/// ```
///
-/// ## Parameters
-/// - body: `Content` (positional, required)
-/// Content in which all text is styled according to the other arguments.
-///
/// Display: Text
/// Category: text
#[node(Construct)]
@@ -447,9 +443,13 @@ pub struct TextNode {
#[fold]
pub features: FontFeatures,
+ /// Content in which all text is styled according to the other arguments.
+ #[external]
+ #[required]
+ pub body: Content,
+
/// The text.
#[internal]
- #[positional]
#[required]
pub text: EcoString,
diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs
index 36a0fc78..3768e65e 100644
--- a/library/src/text/raw.rs
+++ b/library/src/text/raw.rs
@@ -57,7 +57,6 @@ pub struct RawNode {
/// 1 + 2 + 3 + 4 + 5
/// ```
/// ````
- #[positional]
#[required]
pub text: EcoString,
diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs
index ccdb0197..acd46d4e 100644
--- a/library/src/text/shift.rs
+++ b/library/src/text/shift.rs
@@ -42,7 +42,6 @@ pub struct SubNode {
pub size: TextSize,
/// The text to display in subscript.
- #[positional]
#[required]
pub body: Content,
}
@@ -110,7 +109,6 @@ pub struct SuperNode {
pub size: TextSize,
/// The text to display in superscript.
- #[positional]
#[required]
pub body: Content,
}
diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs
index 1fdb418b..78f477f6 100644
--- a/library/src/visualize/image.rs
+++ b/library/src/visualize/image.rs
@@ -23,7 +23,6 @@ use crate::prelude::*;
#[node(Layout)]
pub struct ImageNode {
/// Path to an image file.
- #[positional]
#[required]
#[parse(
let Spanned { v: path, span } =
diff --git a/library/src/visualize/line.rs b/library/src/visualize/line.rs
index 015abbb0..b39170f0 100644
--- a/library/src/visualize/line.rs
+++ b/library/src/visualize/line.rs
@@ -21,15 +21,15 @@ pub struct LineNode {
/// The offset from `start` where the line ends.
#[resolve]
- pub end: Smart<Axes<Rel<Length>>>,
+ pub end: Option<Axes<Rel<Length>>>,
- /// The line's length. Mutually exclusive with `end`.
+ /// The line's length. This is only respected if `end` is `none`.
#[resolve]
#[default(Abs::pt(30.0).into())]
pub length: Rel<Length>,
- /// The angle at which the line points away from the origin. Mutually
- /// exclusive with `end`.
+ /// The angle at which the line points away from the origin. This is only
+ /// respected if `end` is `none`.
pub angle: Angle,
/// How to stroke the line. This can be:
diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs
index ab953846..02b45ed5 100644
--- a/library/src/visualize/shape.rs
+++ b/library/src/visualize/shape.rs
@@ -176,15 +176,15 @@ impl Layout for RectNode {
/// ]
/// ```
///
-/// ## Parameters
-/// - size: `Smart<Length>` (named, settable)
-/// The square's side length. This is mutually exclusive with `width` and
-/// `height`.
-///
/// Display: Square
/// Category: visualize
#[node(Layout)]
pub struct SquareNode {
+ /// The square's side length. This is mutually exclusive with `width` and
+ /// `height`.
+ #[external]
+ pub size: Smart<Length>,
+
/// The square's width. This is mutually exclusive with `size` and `height`.
///
/// In contrast to `size`, this can be relative to the parent container's
@@ -367,15 +367,15 @@ impl Layout for EllipseNode {
/// ]
/// ```
///
-/// ## Parameters
-/// - radius: `Length` (named, settable)
-/// The circle's radius. This is mutually exclusive with `width` and
-/// `height`.
-///
/// Display: Circle
/// Category: visualize
#[node(Layout)]
pub struct CircleNode {
+ /// The circle's radius. This is mutually exclusive with `width` and
+ /// `height`.
+ #[external]
+ pub radius: Length,
+
/// The circle's width. This is mutually exclusive with `radius` and
/// `height`.
///
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index 1a06b1d3..8b4280fe 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -13,5 +13,5 @@ bench = false
[dependencies]
proc-macro2 = "1"
quote = "1"
-syn = { version = "1", features = ["full"] }
+syn = { version = "1", features = ["full", "extra-traits"] }
unscanny = "0.1"
diff --git a/macros/src/func.rs b/macros/src/func.rs
index 843c193e..87324120 100644
--- a/macros/src/func.rs
+++ b/macros/src/func.rs
@@ -1,183 +1,183 @@
+use quote::ToTokens;
+
use super::*;
/// Expand the `#[func]` macro.
-pub fn func(mut item: syn::Item) -> Result<TokenStream> {
- let attrs = match &mut item {
- syn::Item::Struct(item) => &mut item.attrs,
- syn::Item::Fn(item) => &mut item.attrs,
- _ => bail!(item, "expected struct or fn"),
- };
+pub fn func(item: syn::ItemFn) -> Result<TokenStream> {
+ let func = prepare(&item)?;
+ Ok(create(&func))
+}
- let docs = documentation(&attrs);
+struct Func {
+ name: String,
+ display: String,
+ category: String,
+ docs: String,
+ vis: syn::Visibility,
+ ident: Ident,
+ params: Vec<Param>,
+ returns: Vec<String>,
+ body: syn::Block,
+}
- let mut lines: Vec<_> = docs.lines().collect();
- let Some(category) = lines.pop().and_then(|s| s.strip_prefix("Category: ")) else {
- bail!(item, "expected category");
- };
- let Some(display) = lines.pop().and_then(|s| s.strip_prefix("Display: ")) else {
- bail!(item, "expected display name");
- };
+struct Param {
+ name: String,
+ docs: String,
+ external: bool,
+ named: bool,
+ variadic: bool,
+ default: Option<syn::Expr>,
+ ident: Ident,
+ ty: syn::Type,
+}
- let mut docs = lines.join("\n");
- let (params, returns) = params(&mut docs)?;
- let docs = docs.trim();
- attrs.retain(|attr| !attr.path.is_ident("doc"));
- attrs.push(parse_quote! { #[doc = #docs] });
-
- let info = quote! {
- ::typst::eval::FuncInfo {
- name,
- display: #display,
- category: #category,
- docs: #docs,
- params: ::std::vec![#(#params),*],
- returns: ::std::vec![#(#returns),*]
- }
- };
+fn prepare(item: &syn::ItemFn) -> Result<Func> {
+ let sig = &item.sig;
- 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 }
+ let mut params = vec![];
+ for input in &sig.inputs {
+ let syn::FnArg::Typed(typed) = input else {
+ bail!(input, "self is not allowed here");
};
- Ok(quote! {
- #item
-
- #[doc(hidden)]
- #vis enum #ty {}
-
- impl::typst::eval::FuncType for #ty {
- fn create_func(name: &'static str) -> ::typst::eval::Func {
- ::typst::eval::Func::from_fn(#full, #info)
- }
- }
- })
- } 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 syn::Pat::Ident(syn::PatIdent {
+ by_ref: None,
+ mutability: None,
+ ident,
+ ..
+ }) = &*typed.pat else {
+ bail!(typed.pat, "expected identifier");
};
- let (params, args, clause) = generics.split_for_impl();
+ if sig.output.to_token_stream().to_string() != "-> Value" {
+ bail!(sig.output, "must return `Value`");
+ }
- Ok(quote! {
- #item
+ let mut attrs = typed.attrs.clone();
+ params.push(Param {
+ name: kebab_case(ident),
+ docs: documentation(&attrs),
+ external: has_attr(&mut attrs, "external"),
+ named: has_attr(&mut attrs, "named"),
+ variadic: has_attr(&mut attrs, "variadic"),
+ default: parse_attr(&mut attrs, "default")?.map(|expr| {
+ expr.unwrap_or_else(
+ || parse_quote! { ::std::default::Default::default() },
+ )
+ }),
+ ident: ident.clone(),
+ ty: (*typed.ty).clone(),
+ });
- impl #params ::typst::eval::FuncType for #ident #args #clause {
- fn create_func(name: &'static str) -> ::typst::eval::Func {
- ::typst::eval::Func::from_node::<Self>(#info)
- }
- }
- })
+ validate_attrs(&attrs)?;
}
-}
-
-/// Extract a section.
-fn section(docs: &mut String, title: &str, level: usize) -> Option<String> {
- let hashtags = "#".repeat(level);
- let needle = format!("\n{hashtags} {title}\n");
- let start = docs.find(&needle)?;
- let rest = &docs[start..];
- let len = rest[1..]
- .find("\n# ")
- .or_else(|| rest[1..].find("\n## "))
- .or_else(|| rest[1..].find("\n### "))
- .map(|x| 1 + x)
- .unwrap_or(rest.len());
- let end = start + len;
- let section = docs[start + needle.len()..end].trim().to_owned();
- docs.replace_range(start..end, "");
- Some(section)
-}
-/// Parse the parameter section.
-fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> {
- let Some(section) = section(docs, "Parameters", 2) else {
- return Ok((vec![], vec![]));
+ let docs = documentation(&item.attrs);
+ let mut lines = docs.split("\n").collect();
+ let returns = meta_line(&mut lines, "Returns")?
+ .split(" or ")
+ .map(Into::into)
+ .collect();
+ let category = meta_line(&mut lines, "Category")?.into();
+ let display = meta_line(&mut lines, "Display")?.into();
+ let docs = lines.join("\n").trim().into();
+
+ let func = Func {
+ name: sig.ident.to_string().replace('_', ""),
+ display,
+ category,
+ docs,
+ vis: item.vis.clone(),
+ ident: sig.ident.clone(),
+ params,
+ returns,
+ body: (*item.block).clone(),
};
- let mut s = Scanner::new(&section);
- let mut infos = vec![];
- let mut returns = vec![];
-
- while s.eat_if('-') {
- let mut named = false;
- let mut positional = false;
- let mut required = false;
- let mut variadic = false;
- let mut settable = false;
-
- s.eat_whitespace();
- let name = s.eat_until(':');
- s.expect(": ");
-
- if name == "returns" {
- returns = s
- .eat_until('\n')
- .split(" or ")
- .map(str::trim)
- .map(Into::into)
- .collect();
- s.eat_whitespace();
- continue;
- }
+ validate_attrs(&item.attrs)?;
+ Ok(func)
+}
- s.expect('`');
- let ty: syn::Type = syn::parse_str(s.eat_until('`'))?;
- s.expect('`');
- s.eat_whitespace();
- s.expect('(');
-
- for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) {
- match part {
- "positional" => positional = true,
- "named" => named = true,
- "required" => required = true,
- "variadic" => variadic = true,
- "settable" => settable = true,
- _ => bail!(callsite, "unknown parameter flag {:?}", part),
+fn create(func: &Func) -> TokenStream {
+ let Func {
+ name,
+ display,
+ category,
+ docs,
+ vis,
+ ident,
+ params,
+ returns,
+ body,
+ ..
+ } = func;
+ let handlers = params.iter().filter(|param| !param.external).map(create_param_parser);
+ let params = params.iter().map(create_param_info);
+ quote! {
+ #[doc = #docs]
+ #vis fn #ident() -> ::typst::eval::NativeFunc {
+ ::typst::eval::NativeFunc {
+ func: |vm, args| {
+ #(#handlers)*
+ #[allow(unreachable_code)]
+ Ok(#body)
+ },
+ info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo {
+ name: #name,
+ display: #display,
+ docs: #docs,
+ params: ::std::vec![#(#params),*],
+ returns: ::std::vec![#(#returns),*],
+ category: #category,
+ }),
}
}
+ }
+}
- if (!named && !positional) || (variadic && !positional) {
- bail!(callsite, "invalid combination of parameter flags");
+/// Create a parameter info for a field.
+fn create_param_info(param: &Param) -> TokenStream {
+ let Param { name, docs, named, variadic, ty, default, .. } = param;
+ let positional = !named;
+ let required = default.is_none();
+ let ty = if *variadic {
+ quote! { <#ty as ::typst::eval::Variadics>::Inner }
+ } else {
+ quote! { #ty }
+ };
+ quote! {
+ ::typst::eval::ParamInfo {
+ name: #name,
+ docs: #docs,
+ cast: <#ty as ::typst::eval::Cast<
+ ::typst::syntax::Spanned<::typst::eval::Value>
+ >>::describe(),
+ positional: #positional,
+ named: #named,
+ variadic: #variadic,
+ required: #required,
+ settable: false,
}
+ }
+}
- s.expect(')');
-
- let docs = dedent(s.eat_until("\n-").trim());
- let docs = docs.trim();
-
- infos.push(quote! {
- ::typst::eval::ParamInfo {
- name: #name,
- docs: #docs,
- cast: <#ty as ::typst::eval::Cast<
- ::typst::syntax::Spanned<::typst::eval::Value>
- >>::describe(),
- positional: #positional,
- named: #named,
- variadic: #variadic,
- required: #required,
- settable: #settable,
- }
- });
+/// Create argument parsing code for a parameter.
+fn create_param_parser(param: &Param) -> TokenStream {
+ let Param { name, ident, ty, .. } = param;
+
+ let mut value = if param.variadic {
+ quote! { args.all()? }
+ } else if param.named {
+ quote! { args.named(#name)? }
+ } else if param.default.is_some() {
+ quote! { args.eat()? }
+ } else {
+ quote! { args.expect(#name)? }
+ };
- s.eat_whitespace();
+ if let Some(default) = &param.default {
+ value = quote! { #value.unwrap_or_else(|| #default) }
}
- Ok((infos, returns))
+ quote! { let #ident: #ty = #value; }
}
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index c1a8b2ae..889eaa7b 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -16,14 +16,13 @@ use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Parser};
use syn::punctuated::Punctuated;
use syn::{parse_quote, Ident, Result, Token};
-use unscanny::Scanner;
use self::util::*;
-/// Implement `FuncType` for a type or function.
+/// Turns a function into a `NativeFunc`.
#[proc_macro_attribute]
pub fn func(_: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
- let item = syn::parse_macro_input!(item as syn::Item);
+ let item = syn::parse_macro_input!(item as syn::ItemFn);
func::func(item).unwrap_or_else(|err| err.to_compile_error()).into()
}
diff --git a/macros/src/node.rs b/macros/src/node.rs
index 739cc79d..92faf7dd 100644
--- a/macros/src/node.rs
+++ b/macros/src/node.rs
@@ -7,45 +7,35 @@ pub fn node(stream: TokenStream, body: syn::ItemStruct) -> Result<TokenStream> {
}
struct Node {
- attrs: Vec<syn::Attribute>,
+ name: String,
+ display: String,
+ category: String,
+ docs: String,
vis: syn::Visibility,
ident: Ident,
- name: String,
capable: Vec<Ident>,
fields: Vec<Field>,
}
-impl Node {
- fn inherent(&self) -> impl Iterator<Item = &Field> {
- self.fields.iter().filter(|field| field.inherent())
- }
-
- fn settable(&self) -> impl Iterator<Item = &Field> {
- self.fields.iter().filter(|field| field.settable())
- }
-}
-
struct Field {
- attrs: Vec<syn::Attribute>,
- vis: syn::Visibility,
-
name: String,
- ident: Ident,
- ident_in: Ident,
- with_ident: Ident,
- set_ident: Ident,
-
+ docs: String,
internal: bool,
+ external: bool,
positional: bool,
required: bool,
variadic: bool,
fold: bool,
resolve: bool,
parse: Option<FieldParser>,
-
+ default: syn::Expr,
+ vis: syn::Visibility,
+ ident: Ident,
+ ident_in: Ident,
+ with_ident: Ident,
+ set_ident: Ident,
ty: syn::Type,
output: syn::Type,
- default: syn::Expr,
}
impl Field {
@@ -87,34 +77,30 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
let mut attrs = field.attrs.clone();
let variadic = has_attr(&mut attrs, "variadic");
+ let required = has_attr(&mut attrs, "required") || variadic;
+ let positional = has_attr(&mut attrs, "positional") || required;
let mut field = Field {
- vis: field.vis.clone(),
-
name: kebab_case(&ident),
- ident: ident.clone(),
- ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
- with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
- set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
-
+ docs: documentation(&attrs),
internal: has_attr(&mut attrs, "internal"),
- positional: has_attr(&mut attrs, "positional") || variadic,
- required: has_attr(&mut attrs, "required") || variadic,
+ external: has_attr(&mut attrs, "external"),
+ positional,
+ required,
variadic,
fold: has_attr(&mut attrs, "fold"),
resolve: has_attr(&mut attrs, "resolve"),
parse: parse_attr(&mut attrs, "parse")?.flatten(),
-
- ty: field.ty.clone(),
- output: field.ty.clone(),
default: parse_attr(&mut attrs, "default")?
.flatten()
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }),
-
- attrs: {
- validate_attrs(&attrs)?;
- attrs
- },
+ vis: field.vis.clone(),
+ ident: ident.clone(),
+ ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
+ with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
+ set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
+ ty: field.ty.clone(),
+ output: field.ty.clone(),
};
if field.required && (field.fold || field.resolve) {
@@ -134,6 +120,7 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
field.output = parse_quote! { <#output as ::typst::model::Fold>::Output };
}
+ validate_attrs(&attrs)?;
fields.push(field);
}
@@ -142,32 +129,39 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
.into_iter()
.collect();
- let attrs = body.attrs.clone();
- Ok(Node {
+ let docs = documentation(&body.attrs);
+ let mut lines = docs.split("\n").collect();
+ let category = meta_line(&mut lines, "Category")?.into();
+ let display = meta_line(&mut lines, "Display")?.into();
+ let docs = lines.join("\n").trim().into();
+
+ let node = Node {
+ name: body.ident.to_string().trim_end_matches("Node").to_lowercase(),
+ display,
+ category,
+ docs,
vis: body.vis.clone(),
ident: body.ident.clone(),
- name: body.ident.to_string().trim_end_matches("Node").to_lowercase(),
capable,
fields,
- attrs: {
- validate_attrs(&attrs)?;
- attrs
- },
- })
+ };
+
+ validate_attrs(&body.attrs)?;
+ Ok(node)
}
/// Produce the node's definition.
fn create(node: &Node) -> TokenStream {
- let attrs = &node.attrs;
- let vis = &node.vis;
- let ident = &node.ident;
+ let Node { vis, ident, docs, .. } = node;
+ let all = node.fields.iter().filter(|field| !field.external);
+ let settable = all.clone().filter(|field| field.settable());
// Inherent methods and functions.
let new = create_new_func(node);
- let field_methods = node.fields.iter().map(create_field_method);
- let field_in_methods = node.settable().map(create_field_in_method);
- let with_fields_methods = node.fields.iter().map(create_with_field_method);
- let field_style_methods = node.settable().map(create_set_field_method);
+ let field_methods = all.clone().map(create_field_method);
+ let field_in_methods = settable.clone().map(create_field_in_method);
+ let with_fields_methods = all.map(create_with_field_method);
+ let field_style_methods = settable.map(create_set_field_method);
// Trait implementations.
let construct = node
@@ -179,8 +173,7 @@ fn create(node: &Node) -> TokenStream {
let node = create_node_impl(node);
quote! {
- #(#attrs)*
- #[::typst::eval::func]
+ #[doc = #docs]
#[derive(Debug, Clone, Hash)]
#[repr(transparent)]
#vis struct #ident(::typst::model::Content);
@@ -212,10 +205,11 @@ fn create(node: &Node) -> TokenStream {
/// Create the `new` function for the node.
fn create_new_func(node: &Node) -> TokenStream {
- let params = node.inherent().map(|Field { ident, ty, .. }| {
+ let relevant = node.fields.iter().filter(|field| !field.external && field.inherent());
+ let params = relevant.clone().map(|Field { ident, ty, .. }| {
quote! { #ident: #ty }
});
- let builder_calls = node.inherent().map(|Field { ident, with_ident, .. }| {
+ let builder_calls = relevant.map(|Field { ident, with_ident, .. }| {
quote! { .#with_ident(#ident) }
});
quote! {
@@ -229,10 +223,10 @@ fn create_new_func(node: &Node) -> TokenStream {
/// Create an accessor methods for a field.
fn create_field_method(field: &Field) -> TokenStream {
- let Field { attrs, vis, ident, name, output, .. } = field;
+ let Field { vis, docs, ident, name, output, .. } = field;
if field.inherent() {
quote! {
- #(#attrs)*
+ #[doc = #docs]
#vis fn #ident(&self) -> #output {
self.0.cast_required_field(#name)
}
@@ -241,7 +235,7 @@ fn create_field_method(field: &Field) -> TokenStream {
let access =
create_style_chain_access(field, quote! { self.0.field(#name).cloned() });
quote! {
- #(#attrs)*
+ #[doc = #docs]
#vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output {
#access
}
@@ -312,8 +306,7 @@ fn create_set_field_method(field: &Field) -> TokenStream {
/// Create the node's `Node` implementation.
fn create_node_impl(node: &Node) -> TokenStream {
- let ident = &node.ident;
- let name = &node.name;
+ let Node { ident, name, display, category, docs, .. } = node;
let vtable_func = create_vtable_func(node);
let infos = node
.fields
@@ -322,10 +315,6 @@ fn create_node_impl(node: &Node) -> TokenStream {
.map(create_param_info);
quote! {
impl ::typst::model::Node for #ident {
- fn pack(self) -> ::typst::model::Content {
- self.0
- }
-
fn id() -> ::typst::model::NodeId {
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
name: #name,
@@ -334,8 +323,24 @@ fn create_node_impl(node: &Node) -> TokenStream {
::typst::model::NodeId::from_meta(&META)
}
- fn params() -> ::std::vec::Vec<::typst::eval::ParamInfo> {
- ::std::vec![#(#infos),*]
+ fn pack(self) -> ::typst::model::Content {
+ self.0
+ }
+
+ fn func() -> ::typst::eval::NodeFunc {
+ ::typst::eval::NodeFunc {
+ id: Self::id(),
+ construct: <Self as ::typst::model::Construct>::construct,
+ set: <Self as ::typst::model::Set>::set,
+ info: ::typst::eval::Lazy::new(|| typst::eval::FuncInfo {
+ name: #name,
+ display: #display,
+ docs: #docs,
+ params: ::std::vec![#(#infos),*],
+ returns: ::std::vec!["content"],
+ category: #category,
+ }),
+ }
}
}
}
@@ -366,11 +371,14 @@ fn create_vtable_func(node: &Node) -> TokenStream {
/// Create a parameter info for a field.
fn create_param_info(field: &Field) -> TokenStream {
- let Field { name, positional, variadic, required, ty, .. } = field;
+ let Field { name, docs, positional, variadic, required, ty, .. } = field;
let named = !positional;
let settable = field.settable();
- let docs = documentation(&field.attrs);
- let docs = docs.trim();
+ let ty = if *variadic {
+ quote! { <#ty as ::typst::eval::Variadics>::Inner }
+ } else {
+ quote! { #ty }
+ };
quote! {
::typst::eval::ParamInfo {
name: #name,
@@ -393,7 +401,7 @@ fn create_construct_impl(node: &Node) -> TokenStream {
let handlers = node
.fields
.iter()
- .filter(|field| !field.internal || field.parse.is_some())
+ .filter(|field| !field.external && (!field.internal || field.parse.is_some()))
.map(|field| {
let with_ident = &field.with_ident;
let (prefix, value) = create_field_parser(field);
@@ -432,7 +440,11 @@ fn create_set_impl(node: &Node) -> TokenStream {
let handlers = node
.fields
.iter()
- .filter(|field| field.settable() && (!field.internal || field.parse.is_some()))
+ .filter(|field| {
+ !field.external
+ && field.settable()
+ && (!field.internal || field.parse.is_some())
+ })
.map(|field| {
let set_ident = &field.set_ident;
let (prefix, value) = create_field_parser(field);
@@ -459,11 +471,11 @@ fn create_set_impl(node: &Node) -> TokenStream {
/// Create argument parsing code for a field.
fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) {
- let name = &field.name;
if let Some(FieldParser { prefix, expr }) = &field.parse {
return (quote! { #(#prefix);* }, quote! { #expr });
}
+ let name = &field.name;
let value = if field.variadic {
quote! { args.all()? }
} else if field.required {
diff --git a/macros/src/util.rs b/macros/src/util.rs
index c8c56a05..d94ba932 100644
--- a/macros/src/util.rs
+++ b/macros/src/util.rs
@@ -58,14 +58,6 @@ pub fn kebab_case(name: &Ident) -> String {
name.to_string().to_lowercase().replace('_', "-")
}
-/// Dedent documentation text.
-pub fn dedent(text: &str) -> String {
- text.lines()
- .map(|s| s.strip_prefix(" ").unwrap_or(s))
- .collect::<Vec<_>>()
- .join("\n")
-}
-
/// Extract documentation comments from an attribute list.
pub fn documentation(attrs: &[syn::Attribute]) -> String {
let mut doc = String::new();
@@ -86,3 +78,11 @@ pub fn documentation(attrs: &[syn::Attribute]) -> String {
doc.trim().into()
}
+
+/// Extract a line of metadata from documentation.
+pub fn meta_line<'a>(lines: &mut Vec<&'a str>, key: &str) -> Result<&'a str> {
+ match lines.pop().and_then(|line| line.strip_prefix(&format!("{key}:"))) {
+ Some(value) => Ok(value.trim()),
+ None => bail!(callsite, "missing metadata key: {}", key),
+ }
+}
diff --git a/src/eval/cast.rs b/src/eval/cast.rs
index 840ceb05..806f7e92 100644
--- a/src/eval/cast.rs
+++ b/src/eval/cast.rs
@@ -1,6 +1,6 @@
pub use typst_macros::{cast_from_value, cast_to_value};
-use std::num::NonZeroUsize;
+use std::num::{NonZeroI64, NonZeroUsize};
use std::ops::Add;
use ecow::EcoString;
@@ -128,6 +128,20 @@ cast_to_value! {
}
cast_from_value! {
+ NonZeroI64,
+ int: i64 => int.try_into()
+ .map_err(|_| if int <= 0 {
+ "number must be positive"
+ } else {
+ "number too large"
+ })?,
+}
+
+cast_to_value! {
+ v: NonZeroI64 => Value::Int(v.get())
+}
+
+cast_from_value! {
char,
string: Str => {
let mut chars = string.chars();
@@ -211,6 +225,16 @@ impl<T: Into<Value>> From<Vec<T>> for Value {
}
}
+/// A container for a variadic argument.
+pub trait Variadics {
+ /// The contained type.
+ type Inner;
+}
+
+impl<T> Variadics for Vec<T> {
+ type Inner = T;
+}
+
/// Describes a possible value for a cast.
#[derive(Debug, Clone, Hash)]
pub enum CastInfo {
diff --git a/src/eval/func.rs b/src/eval/func.rs
index 6f98e316..79ae142c 100644
--- a/src/eval/func.rs
+++ b/src/eval/func.rs
@@ -6,10 +6,14 @@ use std::sync::Arc;
use comemo::{Prehashed, Track, Tracked, TrackedMut};
use ecow::EcoString;
+use once_cell::sync::Lazy;
-use super::{Args, CastInfo, Dict, Eval, Flow, Route, Scope, Scopes, Tracer, Value, Vm};
+use super::{
+ cast_to_value, Args, CastInfo, Dict, Eval, Flow, Route, Scope, Scopes, Tracer, Value,
+ Vm,
+};
use crate::diag::{bail, SourceResult, StrResult};
-use crate::model::{Node, NodeId, Selector, StyleMap};
+use crate::model::{Content, NodeId, Selector, StyleMap};
use crate::syntax::ast::{self, AstNode, Expr};
use crate::syntax::{SourceId, Span, SyntaxNode};
use crate::util::hash128;
@@ -22,8 +26,10 @@ pub struct Func(Arc<Prehashed<Repr>>, Span);
/// The different kinds of function representations.
#[derive(Hash)]
enum Repr {
- /// A native rust function.
- Native(Native),
+ /// A native Rust function.
+ Native(NativeFunc),
+ /// A function for a node.
+ Node(NodeFunc),
/// A user-defined closure.
Closure(Closure),
/// A nested function with pre-applied arguments.
@@ -31,50 +37,11 @@ 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(
- func: fn(&Vm, &mut Args) -> SourceResult<Value>,
- info: FuncInfo,
- ) -> Self {
- Self(
- Arc::new(Prehashed::new(Repr::Native(Native {
- func,
- set: None,
- node: None,
- info,
- }))),
- Span::detached(),
- )
- }
-
- /// Create a new function from a native rust node.
- pub fn from_node<T: Node>(mut info: FuncInfo) -> Self {
- info.params.extend(T::params());
- Self(
- Arc::new(Prehashed::new(Repr::Native(Native {
- func: |vm, args| T::construct(vm, args).map(Value::Content),
- set: Some(T::set),
- node: Some(NodeId::of::<T>()),
- info,
- }))),
- Span::detached(),
- )
- }
-
- /// Create a new function from a closure.
- pub(super) fn from_closure(closure: Closure, span: Span) -> Self {
- Self(Arc::new(Prehashed::new(Repr::Closure(closure))), span)
- }
-
/// The name of the function.
pub fn name(&self) -> Option<&str> {
match &**self.0 {
Repr::Native(native) => Some(native.info.name),
+ Repr::Node(node) => Some(node.info.name),
Repr::Closure(closure) => closure.name.as_deref(),
Repr::With(func, _) => func.name(),
}
@@ -84,6 +51,7 @@ impl Func {
pub fn info(&self) -> Option<&FuncInfo> {
match &**self.0 {
Repr::Native(native) => Some(&native.info),
+ Repr::Node(node) => Some(&node.info),
Repr::With(func, _) => func.info(),
_ => None,
}
@@ -119,6 +87,11 @@ impl Func {
args.finish()?;
Ok(value)
}
+ Repr::Node(node) => {
+ let value = (node.construct)(vm, &mut args)?;
+ args.finish()?;
+ Ok(Value::Content(value))
+ }
Repr::Closure(closure) => {
// Determine the route inside the closure.
let fresh = Route::new(closure.location);
@@ -172,8 +145,8 @@ impl Func {
/// Execute the function's set rule and return the resulting style map.
pub fn set(&self, mut args: Args) -> SourceResult<StyleMap> {
Ok(match &**self.0 {
- Repr::Native(Native { set: Some(set), .. }) => {
- let styles = set(&mut args)?;
+ Repr::Node(node) => {
+ let styles = (node.set)(&mut args)?;
args.finish()?;
styles
}
@@ -183,13 +156,13 @@ impl Func {
/// Create a selector for this function's node type.
pub fn select(&self, fields: Option<Dict>) -> StrResult<Selector> {
- match **self.0 {
- Repr::Native(Native { node: Some(id), .. }) => {
- if id == item!(text_id) {
+ match &**self.0 {
+ Repr::Node(node) => {
+ if node.id == item!(text_id) {
Err("to select text, please use a string or regex instead")?;
}
- Ok(Selector::Node(id, fields))
+ Ok(Selector::Node(node.id, fields))
}
_ => Err("this function is not selectable")?,
}
@@ -211,32 +184,75 @@ 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;
+impl From<Repr> for Func {
+ fn from(repr: Repr) -> Self {
+ Self(Arc::new(Prehashed::new(repr)), Span::detached())
+ }
}
-/// A function defined by a native rust function or node.
-struct Native {
- /// The function pointer.
- func: fn(&Vm, &mut Args) -> SourceResult<Value>,
- /// The set rule.
- set: Option<fn(&mut Args) -> SourceResult<StyleMap>>,
- /// The id of the node to customize with this function's show rule.
- node: Option<NodeId>,
- /// Documentation of the function.
- info: FuncInfo,
+/// A native Rust function.
+pub struct NativeFunc {
+ /// The function's implementation.
+ pub func: fn(&Vm, &mut Args) -> SourceResult<Value>,
+ /// Details about the function.
+ pub info: Lazy<FuncInfo>,
}
-impl Hash for Native {
+impl Hash for NativeFunc {
fn hash<H: Hasher>(&self, state: &mut H) {
(self.func as usize).hash(state);
- self.set.map(|set| set as usize).hash(state);
- self.node.hash(state);
}
}
+impl From<NativeFunc> for Func {
+ fn from(native: NativeFunc) -> Self {
+ Repr::Native(native).into()
+ }
+}
+
+cast_to_value! {
+ v: NativeFunc => Value::Func(v.into())
+}
+
+impl<F> From<F> for Value
+where
+ F: Fn() -> NativeFunc,
+{
+ fn from(f: F) -> Self {
+ f().into()
+ }
+}
+
+/// A function defined by a native Rust node.
+pub struct NodeFunc {
+ /// The node's id.
+ pub id: NodeId,
+ /// The node's constructor.
+ pub construct: fn(&Vm, &mut Args) -> SourceResult<Content>,
+ /// The node's set rule.
+ pub set: fn(&mut Args) -> SourceResult<StyleMap>,
+ /// Details about the function.
+ pub info: Lazy<FuncInfo>,
+}
+
+impl Hash for NodeFunc {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.id.hash(state);
+ (self.construct as usize).hash(state);
+ (self.set as usize).hash(state);
+ }
+}
+
+impl From<NodeFunc> for Func {
+ fn from(node: NodeFunc) -> Self {
+ Repr::Node(node).into()
+ }
+}
+
+cast_to_value! {
+ v: NodeFunc => Value::Func(v.into())
+}
+
/// Details about a function.
#[derive(Debug, Clone)]
pub struct FuncInfo {
@@ -375,6 +391,16 @@ impl Closure {
}
}
+impl From<Closure> for Func {
+ fn from(closure: Closure) -> Self {
+ Repr::Closure(closure).into()
+ }
+}
+
+cast_to_value! {
+ v: Closure => Value::Func(v.into())
+}
+
/// A visitor that determines which variables to capture for a closure.
pub(super) struct CapturesVisitor<'a> {
external: &'a Scopes<'a>,
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 0e0828e3..fcfda263 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -20,6 +20,9 @@ mod ops;
mod scope;
mod symbol;
+#[doc(hidden)]
+pub use once_cell::sync::Lazy;
+
pub use self::args::*;
pub use self::array::*;
pub use self::cast::*;
@@ -1152,7 +1155,7 @@ impl Eval for ast::Closure {
body: self.body(),
};
- Ok(Value::Func(Func::from_closure(closure, self.span())))
+ Ok(Value::Func(Func::from(closure).spanned(self.span())))
}
}
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index f6bd2164..d1590063 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -4,7 +4,7 @@ use std::hash::Hash;
use ecow::EcoString;
-use super::{Func, FuncType, Library, Value};
+use super::{Library, Value};
use crate::diag::StrResult;
/// A stack of scopes.
@@ -96,11 +96,6 @@ impl Scope {
self.0.insert(name, Slot::new(value.into(), Kind::Normal));
}
- /// Define a function through a native rust function.
- pub fn def_func<T: FuncType>(&mut self, name: &'static str) {
- self.define(name, Func::from_type::<T>(name));
- }
-
/// Define a captured, immutable binding.
pub fn define_captured(
&mut self,
diff --git a/src/ide/complete.rs b/src/ide/complete.rs
index f5eece93..2e810ee9 100644
--- a/src/ide/complete.rs
+++ b/src/ide/complete.rs
@@ -627,10 +627,6 @@ fn param_completions(
}
}
- if callee.as_str() == "text" {
- ctx.font_completions();
- }
-
if ctx.before.ends_with(',') {
ctx.enrich(" ", "");
}
@@ -653,7 +649,7 @@ fn named_param_value_completions(
ctx.cast_completions(&param.cast);
- if callee.as_str() == "text" && name == "family" {
+ if callee.as_str() == "text" && name == "font" {
ctx.font_completions();
}
diff --git a/src/model/content.rs b/src/model/content.rs
index 6b4f5e5d..d845ce1e 100644
--- a/src/model/content.rs
+++ b/src/model/content.rs
@@ -9,7 +9,7 @@ use ecow::{EcoString, EcoVec};
use super::{node, Guard, Recipe, Style, StyleMap};
use crate::diag::{SourceResult, StrResult};
-use crate::eval::{cast_from_value, Args, Cast, ParamInfo, Value, Vm};
+use crate::eval::{cast_from_value, Args, Cast, NodeFunc, Value, Vm};
use crate::syntax::Span;
use crate::World;
@@ -330,12 +330,10 @@ impl Sum for Content {
#[node]
pub struct StyledNode {
/// The styles.
- #[positional]
#[required]
pub map: StyleMap,
/// The styled content.
- #[positional]
#[required]
pub body: Content,
}
@@ -353,7 +351,6 @@ cast_from_value! {
/// Category: special
#[node]
pub struct SequenceNode {
- #[positional]
#[variadic]
pub children: Vec<Content>,
}
@@ -370,14 +367,14 @@ impl Debug for Label {
/// A constructable, stylable content node.
pub trait Node: Construct + Set + Sized + 'static {
- /// Pack a node into type-erased content.
- fn pack(self) -> Content;
-
/// The node's ID.
fn id() -> NodeId;
- /// List the fields of the node.
- fn params() -> Vec<ParamInfo>;
+ /// Pack a node into type-erased content.
+ fn pack(self) -> Content;
+
+ /// The node's function.
+ fn func() -> NodeFunc;
}
/// A unique identifier for a node.
@@ -425,6 +422,7 @@ pub struct NodeMeta {
pub vtable: fn(of: TypeId) -> Option<*const ()>,
}
+/// A node's constructor function.
pub trait Construct {
/// Construct a node from the arguments.
///
@@ -433,6 +431,7 @@ pub trait Construct {
fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content>;
}
+/// A node's set rule.
pub trait Set {
/// Parse relevant arguments into style properties for this node.
fn set(args: &mut Args) -> SourceResult<StyleMap>;
diff --git a/src/model/mod.rs b/src/model/mod.rs
index 07329e3f..6015c365 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -11,6 +11,4 @@ pub use self::realize::*;
pub use self::styles::*;
pub use self::typeset::*;
-#[doc(hidden)]
-pub use once_cell;
pub use typst_macros::node;
diff --git a/src/model/styles.rs b/src/model/styles.rs
index 0b74e162..3239bb17 100644
--- a/src/model/styles.rs
+++ b/src/model/styles.rs
@@ -86,7 +86,7 @@ impl Debug for StyleMap {
}
}
-/// A single style property, recipe or barrier.
+/// A single style property or recipe.
#[derive(Clone, Hash)]
pub enum Style {
/// A style property originating from a set rule or constructor.
diff --git a/tests/src/tests.rs b/tests/src/tests.rs
index b35ddcce..8a25af15 100644
--- a/tests/src/tests.rs
+++ b/tests/src/tests.rs
@@ -11,7 +11,7 @@ use comemo::{Prehashed, Track};
use elsa::FrozenVec;
use once_cell::unsync::OnceCell;
use tiny_skia as sk;
-use typst::diag::{bail, FileError, FileResult, SourceResult};
+use typst::diag::{bail, FileError, FileResult};
use typst::doc::{Document, Element, Frame, Meta};
use typst::eval::{func, Library, Value};
use typst::font::{Font, FontBook};
@@ -148,29 +148,29 @@ impl Args {
fn library() -> Library {
/// Display: Test
/// Category: test
+ /// Returns:
#[func]
- fn test(args: &mut typst::eval::Args) -> SourceResult<Value> {
- let lhs = args.expect::<Value>("left-hand side")?;
- let rhs = args.expect::<Value>("right-hand side")?;
+ fn test(lhs: Value, rhs: Value) -> Value {
if lhs != rhs {
bail!(args.span, "Assertion failed: {:?} != {:?}", lhs, rhs,);
}
- Ok(Value::None)
+ Value::None
}
/// Display: Print
/// Category: test
+ /// Returns:
#[func]
- fn print(args: &mut typst::eval::Args) -> SourceResult<Value> {
+ fn print(#[variadic] values: Vec<Value>) -> Value {
print!("> ");
- for (i, value) in args.all::<Value>()?.into_iter().enumerate() {
+ for (i, value) in values.into_iter().enumerate() {
if i > 0 {
print!(", ")
}
print!("{value:?}");
}
println!();
- Ok(Value::None)
+ Value::None
}
let mut lib = typst_library::build();
@@ -187,8 +187,8 @@ fn library() -> Library {
lib.styles.set(TextNode::set_size(TextSize(Abs::pt(10.0).into())));
// Hook up helpers into the global scope.
- lib.global.scope_mut().def_func::<TestFunc>("test");
- lib.global.scope_mut().def_func::<PrintFunc>("print");
+ lib.global.scope_mut().define("test", test);
+ lib.global.scope_mut().define("print", print);
lib.global
.scope_mut()
.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
diff --git a/tests/typ/compute/calc.typ b/tests/typ/compute/calc.typ
index 67579493..ee063707 100644
--- a/tests/typ/compute/calc.typ
+++ b/tests/typ/compute/calc.typ
@@ -77,7 +77,7 @@
#test(calc.min("hi"), "hi")
---
-// Error: 10-12 missing argument: value
+// Error: 10-12 expected at least one value
#calc.min()
---
@@ -109,5 +109,5 @@
#range(4, step: "one")
---
-// Error: 18-19 step must not be zero
+// Error: 18-19 number must be positive
#range(10, step: 0)
diff --git a/tests/typ/text/lorem.typ b/tests/typ/text/lorem.typ
index 944fd5be..92dfbba4 100644
--- a/tests/typ/text/lorem.typ
+++ b/tests/typ/text/lorem.typ
@@ -28,5 +28,5 @@
}
---
-// Error: 7-9 missing argument: number of words
+// Error: 7-9 missing argument: words
#lorem()