summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-12-15 20:27:41 +0100
committerLaurenz <laurmaedje@gmail.com>2021-12-15 20:27:41 +0100
commit2a3d0f4b390457174ed09347dd29e97ff9a783e4 (patch)
tree0e0634bff6b7f64131267f4cbe05651c1c91d900
parent244ad386ec271ff86a2101eb4cc38d37a55552b9 (diff)
Set Rules Episode VII: The Set Awakens
-rw-r--r--benches/bench.typ2
-rw-r--r--src/eval/class.rs101
-rw-r--r--src/eval/mod.rs28
-rw-r--r--src/eval/node.rs10
-rw-r--r--src/eval/scope.rs29
-rw-r--r--src/eval/value.rs7
-rw-r--r--src/library/mod.rs28
-rw-r--r--src/library/page.rs92
-rw-r--r--src/library/par.rs88
-rw-r--r--src/library/text.rs95
-rw-r--r--src/parse/mod.rs65
-rw-r--r--src/parse/parser.rs3
-rw-r--r--src/parse/tokens.rs1
-rw-r--r--src/source.rs5
-rw-r--r--src/syntax/ast.rs23
-rw-r--r--src/syntax/highlight.rs29
-rw-r--r--src/syntax/mod.rs101
-rw-r--r--src/syntax/pretty.rs12
-rw-r--r--tests/typ/code/call.typ11
-rw-r--r--tests/typ/code/include.typ2
-rw-r--r--tests/typ/code/spread.typ10
-rw-r--r--tests/typ/coma.typ2
-rw-r--r--tests/typ/elements/circle.typ4
-rw-r--r--tests/typ/elements/ellipse.typ2
-rw-r--r--tests/typ/elements/image.typ6
-rw-r--r--tests/typ/elements/rect.typ2
-rw-r--r--tests/typ/elements/square.typ6
-rw-r--r--tests/typ/layout/align.typ2
-rw-r--r--tests/typ/layout/aspect.typ10
-rw-r--r--tests/typ/layout/background.typ4
-rw-r--r--tests/typ/layout/containers.typ2
-rw-r--r--tests/typ/layout/grid-1.typ4
-rw-r--r--tests/typ/layout/grid-2.typ2
-rw-r--r--tests/typ/layout/grid-3.typ10
-rw-r--r--tests/typ/layout/grid-4.typ2
-rw-r--r--tests/typ/layout/grid-5.typ4
-rw-r--r--tests/typ/layout/pad.typ2
-rw-r--r--tests/typ/layout/page.typ28
-rw-r--r--tests/typ/layout/pagebreak.typ12
-rw-r--r--tests/typ/layout/placed.typ4
-rw-r--r--tests/typ/layout/spacing.typ4
-rw-r--r--tests/typ/layout/stack-1.typ12
-rw-r--r--tests/typ/layout/stack-2.typ6
-rw-r--r--tests/typ/layout/transform.typ6
-rw-r--r--tests/typ/text/baseline.typ2
-rw-r--r--tests/typ/text/basic.typ2
-rw-r--r--tests/typ/text/bidi.typ42
-rw-r--r--tests/typ/text/chinese.typ2
-rw-r--r--tests/typ/text/decorations.typ2
-rw-r--r--tests/typ/text/features.typ71
-rw-r--r--tests/typ/text/font.typ70
-rw-r--r--tests/typ/text/links.typ4
-rw-r--r--tests/typ/text/par.typ24
-rw-r--r--tests/typ/text/shaping.typ10
-rw-r--r--tests/typ/text/tracking.typ6
-rw-r--r--tests/typ/text/whitespace.typ4
56 files changed, 672 insertions, 445 deletions
diff --git a/benches/bench.typ b/benches/bench.typ
index f290844b..75c97d02 100644
--- a/benches/bench.typ
+++ b/benches/bench.typ
@@ -1,5 +1,5 @@
// Configuration with `page` and `font` functions.
-#page(width: 450pt, margins: 1cm)
+#set page(width: 450pt, margins: 1cm)
// There are variables and they can take normal values like strings, ...
#let city = "Berlin"
diff --git a/src/eval/class.rs b/src/eval/class.rs
new file mode 100644
index 00000000..45674933
--- /dev/null
+++ b/src/eval/class.rs
@@ -0,0 +1,101 @@
+use std::fmt::{self, Debug, Formatter, Write};
+use std::marker::PhantomData;
+use std::rc::Rc;
+
+use super::{Args, EvalContext, Node, Styles};
+use crate::diag::TypResult;
+use crate::util::EcoString;
+
+/// A class of nodes.
+#[derive(Clone)]
+pub struct Class(Rc<Inner<dyn Bounds>>);
+
+/// The unsized structure behind the [`Rc`].
+struct Inner<T: ?Sized> {
+ name: EcoString,
+ dispatch: T,
+}
+
+impl Class {
+ /// Create a new class.
+ pub fn new<T>(name: EcoString) -> Self
+ where
+ T: Construct + Set + 'static,
+ {
+ Self(Rc::new(Inner {
+ name,
+ dispatch: Dispatch::<T>(PhantomData),
+ }))
+ }
+
+ /// The name of the class.
+ pub fn name(&self) -> &EcoString {
+ &self.0.name
+ }
+
+ /// Construct an instance of the class.
+ pub fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ self.0.dispatch.construct(ctx, args)
+ }
+
+ /// Execute the class's set rule.
+ pub fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ self.0.dispatch.set(styles, args)
+ }
+}
+
+impl Debug for Class {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.write_str("<class ")?;
+ f.write_str(&self.0.name)?;
+ f.write_char('>')
+ }
+}
+
+impl PartialEq for Class {
+ fn eq(&self, other: &Self) -> bool {
+ // We cast to thin pointers for comparison.
+ std::ptr::eq(
+ Rc::as_ptr(&self.0) as *const (),
+ Rc::as_ptr(&other.0) as *const (),
+ )
+ }
+}
+
+/// Construct an instance of a class.
+pub trait Construct {
+ /// Construct an instance of this class from the arguments.
+ ///
+ /// This is passed only the arguments that remain after execution of the
+ /// class's set rule.
+ fn construct(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>;
+}
+
+/// Set style properties of a class.
+pub trait Set {
+ /// Parse the arguments and insert style properties of this class into the
+ /// given style map.
+ fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()>;
+}
+
+/// Zero-sized struct whose vtable contains the constructor and set rule of a
+/// class.
+struct Dispatch<T>(PhantomData<T>);
+
+trait Bounds {
+ fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node>;
+ fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()>;
+}
+
+impl<T> Bounds for Dispatch<T>
+where
+ T: Construct + Set,
+{
+ fn construct(&self, ctx: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ T::construct(ctx, args)
+ }
+
+ fn set(&self, styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ T::set(styles, args)
+ }
+}
diff --git a/src/eval/mod.rs b/src/eval/mod.rs
index 6dcff900..ae330134 100644
--- a/src/eval/mod.rs
+++ b/src/eval/mod.rs
@@ -9,6 +9,7 @@ mod value;
#[macro_use]
mod styles;
mod capture;
+mod class;
mod function;
mod node;
mod ops;
@@ -16,6 +17,7 @@ mod scope;
pub use array::*;
pub use capture::*;
+pub use class::*;
pub use dict::*;
pub use function::*;
pub use node::*;
@@ -54,7 +56,7 @@ pub fn eval(ctx: &mut Context, source: SourceId, markup: &Markup) -> TypResult<M
pub struct Module {
/// The top-level definitions that were bound in this module.
pub scope: Scope,
- /// The node defined by this module.
+ /// The module's layoutable contents.
pub node: Node,
}
@@ -288,6 +290,7 @@ impl Eval for Expr {
Self::Unary(v) => v.eval(ctx),
Self::Binary(v) => v.eval(ctx),
Self::Let(v) => v.eval(ctx),
+ Self::Set(v) => v.eval(ctx),
Self::If(v) => v.eval(ctx),
Self::While(v) => v.eval(ctx),
Self::For(v) => v.eval(ctx),
@@ -474,9 +477,17 @@ impl Eval for CallExpr {
Ok(value)
}
+ Value::Class(class) => {
+ let mut styles = Styles::new();
+ class.set(&mut styles, &mut args)?;
+ let node = class.construct(ctx, &mut args)?;
+ args.finish()?;
+ Ok(Value::Node(node.styled(styles)))
+ }
+
v => bail!(
self.callee().span(),
- "expected function or collection, found {}",
+ "expected callable or collection, found {}",
v.type_name(),
),
}
@@ -643,6 +654,19 @@ impl Eval for LetExpr {
}
}
+impl Eval for SetExpr {
+ type Output = Value;
+
+ fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
+ let class = self.class();
+ let class = class.eval(ctx)?.cast::<Class>().at(class.span())?;
+ let mut args = self.args().eval(ctx)?;
+ class.set(&mut ctx.styles, &mut args)?;
+ args.finish()?;
+ Ok(Value::None)
+ }
+}
+
impl Eval for IfExpr {
type Output = Value;
diff --git a/src/eval/node.rs b/src/eval/node.rs
index 5653beff..a04fe84b 100644
--- a/src/eval/node.rs
+++ b/src/eval/node.rs
@@ -36,6 +36,8 @@ pub enum Node {
Inline(PackedNode),
/// A block node.
Block(PackedNode),
+ /// A page node.
+ Page(PackedNode),
/// A sequence of nodes (which may themselves contain sequences).
Sequence(Vec<(Self, Styles)>),
}
@@ -214,6 +216,14 @@ impl Packer {
Node::Block(block) => {
self.push_block(block.styled(styles));
}
+ Node::Page(flow) => {
+ if self.top {
+ self.pagebreak();
+ self.pages.push(PageNode { child: flow, styles });
+ } else {
+ self.push_block(flow.styled(styles));
+ }
+ }
Node::Sequence(list) => {
// For a list of nodes, we apply the list's styles to each node
// individually.
diff --git a/src/eval/scope.rs b/src/eval/scope.rs
index ffe2d63e..5178c819 100644
--- a/src/eval/scope.rs
+++ b/src/eval/scope.rs
@@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::iter;
use std::rc::Rc;
-use super::{Args, EvalContext, Function, Value};
+use super::{Args, Class, Construct, EvalContext, Function, Set, Value};
use crate::diag::TypResult;
use crate::util::EcoString;
@@ -88,15 +88,6 @@ impl Scope {
self.values.insert(var.into(), Rc::new(cell));
}
- /// Define a constant function.
- pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F)
- where
- F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static,
- {
- let name = name.into();
- self.def_const(name.clone(), Function::new(Some(name), f));
- }
-
/// Define a mutable variable with a value.
pub fn def_mut(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
self.values.insert(var.into(), Rc::new(RefCell::new(value.into())));
@@ -107,6 +98,24 @@ impl Scope {
self.values.insert(var.into(), slot);
}
+ /// Define a constant function.
+ pub fn def_func<F>(&mut self, name: &str, f: F)
+ where
+ F: Fn(&mut EvalContext, &mut Args) -> TypResult<Value> + 'static,
+ {
+ let name = EcoString::from(name);
+ self.def_const(name.clone(), Function::new(Some(name), f));
+ }
+
+ /// Define a constant class.
+ pub fn def_class<T>(&mut self, name: &str)
+ where
+ T: Construct + Set + 'static,
+ {
+ let name = EcoString::from(name);
+ self.def_const(name.clone(), Class::new::<T>(name));
+ }
+
/// Look up the value of a variable.
pub fn get(&self, var: &str) -> Option<&Slot> {
self.values.get(var)
diff --git a/src/eval/value.rs b/src/eval/value.rs
index 2cf82a26..0995ab75 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::rc::Rc;
-use super::{ops, Array, Dict, Function, Node};
+use super::{ops, Array, Class, Dict, Function, Node};
use crate::diag::StrResult;
use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor};
use crate::layout::Layout;
@@ -46,6 +46,8 @@ pub enum Value {
Node(Node),
/// An executable function.
Func(Function),
+ /// A class of nodes.
+ Class(Class),
/// A dynamic value.
Dyn(Dynamic),
}
@@ -86,6 +88,7 @@ impl Value {
Self::Dict(_) => Dict::TYPE_NAME,
Self::Node(_) => Node::TYPE_NAME,
Self::Func(_) => Function::TYPE_NAME,
+ Self::Class(_) => Class::TYPE_NAME,
Self::Dyn(v) => v.type_name(),
}
}
@@ -148,6 +151,7 @@ impl Debug for Value {
Self::Dict(v) => Debug::fmt(v, f),
Self::Node(_) => f.pad("<template>"),
Self::Func(v) => Debug::fmt(v, f),
+ Self::Class(v) => Debug::fmt(v, f),
Self::Dyn(v) => Debug::fmt(v, f),
}
}
@@ -394,6 +398,7 @@ primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict }
primitive! { Node: "template", Node }
primitive! { Function: "function", Func }
+primitive! { Class: "class", Class }
impl Cast<Value> for Value {
fn is(_: &Value) -> bool {
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 9b6da6a9..1e2ee224 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -27,7 +27,9 @@ mod prelude {
pub use std::rc::Rc;
pub use crate::diag::{At, TypResult};
- pub use crate::eval::{Args, EvalContext, Node, Property, Smart, Styles, Value};
+ pub use crate::eval::{
+ Args, Construct, EvalContext, Node, Property, Set, Smart, Styles, Value,
+ };
pub use crate::frame::*;
pub use crate::geom::*;
pub use crate::layout::*;
@@ -60,22 +62,24 @@ use crate::geom::*;
pub fn new() -> Scope {
let mut std = Scope::new();
- // Text.
- std.def_func("font", font);
- std.def_func("par", par);
- std.def_func("parbreak", parbreak);
+ // Classes.
+ std.def_class::<PageNode>("page");
+ std.def_class::<ParNode>("par");
+ std.def_class::<TextNode>("text");
+
+ // Text functions.
std.def_func("strike", strike);
std.def_func("underline", underline);
std.def_func("overline", overline);
std.def_func("link", link);
- // Layout.
- std.def_func("page", page);
- std.def_func("pagebreak", pagebreak);
+ // Layout functions.
std.def_func("h", h);
std.def_func("v", v);
std.def_func("box", box_);
std.def_func("block", block);
+ std.def_func("pagebreak", pagebreak);
+ std.def_func("parbreak", parbreak);
std.def_func("stack", stack);
std.def_func("grid", grid);
std.def_func("pad", pad);
@@ -85,14 +89,14 @@ pub fn new() -> Scope {
std.def_func("scale", scale);
std.def_func("rotate", rotate);
- // Elements.
+ // Element functions.
std.def_func("image", image);
std.def_func("rect", rect);
std.def_func("square", square);
std.def_func("ellipse", ellipse);
std.def_func("circle", circle);
- // Utility.
+ // Utility functions.
std.def_func("assert", assert);
std.def_func("type", type_);
std.def_func("repr", repr);
@@ -110,14 +114,14 @@ pub fn new() -> Scope {
std.def_func("len", len);
std.def_func("sorted", sorted);
- // Colors.
+ // Predefined colors.
std.def_const("white", RgbaColor::WHITE);
std.def_const("black", RgbaColor::BLACK);
std.def_const("eastern", RgbaColor::new(0x23, 0x9D, 0xAD, 0xFF));
std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
- // Arbitrary constants.
+ // Other constants.
std.def_const("ltr", Dir::LTR);
std.def_const("rtl", Dir::RTL);
std.def_const("ttb", Dir::TTB);
diff --git a/src/library/page.rs b/src/library/page.rs
index 4a4fab03..0b0cc2d9 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -6,53 +6,6 @@ use std::str::FromStr;
use super::prelude::*;
use super::PadNode;
-/// `page`: Configure pages.
-pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- castable! {
- Paper,
- Expected: "string",
- Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?,
- }
-
- let body: Option<Node> = args.find();
-
- let mut map = Styles::new();
- let styles = match body {
- Some(_) => &mut map,
- None => &mut ctx.styles,
- };
-
- if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
- styles.set(PageNode::CLASS, paper.class());
- styles.set(PageNode::WIDTH, Smart::Custom(paper.width()));
- styles.set(PageNode::HEIGHT, Smart::Custom(paper.height()));
- }
-
- if let Some(width) = args.named("width")? {
- styles.set(PageNode::CLASS, PaperClass::Custom);
- styles.set(PageNode::WIDTH, width);
- }
-
- if let Some(height) = args.named("height")? {
- styles.set(PageNode::CLASS, PaperClass::Custom);
- styles.set(PageNode::HEIGHT, height);
- }
-
- let margins = args.named("margins")?;
-
- set!(styles, PageNode::FLIPPED => args.named("flipped")?);
- set!(styles, PageNode::LEFT => args.named("left")?.or(margins));
- set!(styles, PageNode::TOP => args.named("top")?.or(margins));
- set!(styles, PageNode::RIGHT => args.named("right")?.or(margins));
- set!(styles, PageNode::BOTTOM => args.named("bottom")?.or(margins));
- set!(styles, PageNode::FILL => args.named("fill")?);
-
- Ok(match body {
- Some(body) => Value::block(body.into_block().styled(map)),
- None => Value::None,
- })
-}
-
/// `pagebreak`: Start a new page.
pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Pagebreak))
@@ -90,6 +43,45 @@ properties! {
FILL: Option<Paint> = None,
}
+impl Construct for PageNode {
+ fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ // TODO(set): Make sure it's really a page so that it doesn't merge
+ // with adjacent pages.
+ Ok(Node::Page(args.expect::<Node>("body")?.into_block()))
+ }
+}
+
+impl Set for PageNode {
+ fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ if let Some(paper) = args.named::<Paper>("paper")?.or_else(|| args.find()) {
+ styles.set(PageNode::CLASS, paper.class());
+ styles.set(PageNode::WIDTH, Smart::Custom(paper.width()));
+ styles.set(PageNode::HEIGHT, Smart::Custom(paper.height()));
+ }
+
+ if let Some(width) = args.named("width")? {
+ styles.set(PageNode::CLASS, PaperClass::Custom);
+ styles.set(PageNode::WIDTH, width);
+ }
+
+ if let Some(height) = args.named("height")? {
+ styles.set(PageNode::CLASS, PaperClass::Custom);
+ styles.set(PageNode::HEIGHT, height);
+ }
+
+ let margins = args.named("margins")?;
+
+ set!(styles, PageNode::FLIPPED => args.named("flipped")?);
+ set!(styles, PageNode::LEFT => args.named("left")?.or(margins));
+ set!(styles, PageNode::TOP => args.named("top")?.or(margins));
+ set!(styles, PageNode::RIGHT => args.named("right")?.or(margins));
+ set!(styles, PageNode::BOTTOM => args.named("bottom")?.or(margins));
+ set!(styles, PageNode::FILL => args.named("fill")?);
+
+ Ok(())
+ }
+}
+
impl PageNode {
/// Layout the page run into a sequence of frames, one per page.
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
@@ -182,6 +174,12 @@ impl Default for Paper {
}
}
+castable! {
+ Paper,
+ Expected: "string",
+ Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?,
+}
+
/// Defines default margins for a class of related papers.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PaperClass {
diff --git a/src/library/par.rs b/src/library/par.rs
index 6ea960bf..d63c2315 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -9,46 +9,6 @@ use super::prelude::*;
use super::{shape, ShapedText, SpacingKind, SpacingNode, TextNode};
use crate::util::{EcoString, RangeExt, RcExt, SliceExt};
-/// `par`: Configure paragraphs.
-pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- let spacing = args.named("spacing")?;
- let leading = args.named("leading")?;
-
- let mut dir =
- args.named("lang")?
- .map(|iso: EcoString| match iso.to_lowercase().as_str() {
- "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
- "en" | "fr" | "de" => Dir::LTR,
- _ => Dir::LTR,
- });
-
- if let Some(Spanned { v, span }) = args.named::<Spanned<Dir>>("dir")? {
- if v.axis() != SpecAxis::Horizontal {
- bail!(span, "must be horizontal");
- }
- dir = Some(v);
- }
-
- let mut align = None;
- if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? {
- if v.axis() != SpecAxis::Horizontal {
- bail!(span, "must be horizontal");
- }
- align = Some(v);
- }
-
- if let (Some(dir), None) = (dir, align) {
- align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right });
- }
-
- set!(ctx.styles, ParNode::DIR => dir);
- set!(ctx.styles, ParNode::ALIGN => align);
- set!(ctx.styles, ParNode::LEADING => leading);
- set!(ctx.styles, ParNode::SPACING => spacing);
-
- Ok(Value::None)
-}
-
/// `parbreak`: Start a new paragraph.
pub fn parbreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Parbreak))
@@ -71,6 +31,54 @@ properties! {
SPACING: Linear = Relative::new(1.2).into(),
}
+impl Construct for ParNode {
+ fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ // Lift to a block so that it doesn't merge with adjacent stuff.
+ Ok(Node::Block(args.expect::<Node>("body")?.into_block()))
+ }
+}
+
+impl Set for ParNode {
+ fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ let spacing = args.named("spacing")?;
+ let leading = args.named("leading")?;
+
+ let mut dir =
+ args.named("lang")?
+ .map(|iso: EcoString| match iso.to_lowercase().as_str() {
+ "ar" | "he" | "fa" | "ur" | "ps" | "yi" => Dir::RTL,
+ "en" | "fr" | "de" => Dir::LTR,
+ _ => Dir::LTR,
+ });
+
+ if let Some(Spanned { v, span }) = args.named::<Spanned<Dir>>("dir")? {
+ if v.axis() != SpecAxis::Horizontal {
+ bail!(span, "must be horizontal");
+ }
+ dir = Some(v);
+ }
+
+ let mut align = None;
+ if let Some(Spanned { v, span }) = args.named::<Spanned<Align>>("align")? {
+ if v.axis() != SpecAxis::Horizontal {
+ bail!(span, "must be horizontal");
+ }
+ align = Some(v);
+ }
+
+ if let (Some(dir), None) = (dir, align) {
+ align = Some(if dir == Dir::LTR { Align::Left } else { Align::Right });
+ }
+
+ set!(styles, ParNode::DIR => dir);
+ set!(styles, ParNode::ALIGN => align);
+ set!(styles, ParNode::LEADING => leading);
+ set!(styles, ParNode::SPACING => spacing);
+
+ Ok(())
+ }
+}
+
impl Layout for ParNode {
fn layout(
&self,
diff --git a/src/library/text.rs b/src/library/text.rs
index 4ab378c2..4baf4bc5 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -15,54 +15,6 @@ use crate::font::{
use crate::geom::{Dir, Em, Length, Point, Size};
use crate::util::{EcoString, SliceExt};
-/// `font`: Configure the font.
-pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
- let body = args.find::<Node>();
-
- let mut map = Styles::new();
- let styles = match body {
- Some(_) => &mut map,
- None => &mut ctx.styles,
- };
-
- let list = args.named("family")?.or_else(|| {
- let families: Vec<_> = args.all().collect();
- (!families.is_empty()).then(|| families)
- });
-
- set!(styles, TextNode::FAMILY_LIST => list);
- set!(styles, TextNode::SERIF_LIST => args.named("serif")?);
- set!(styles, TextNode::SANS_SERIF_LIST => args.named("sans-serif")?);
- set!(styles, TextNode::MONOSPACE_LIST => args.named("monospace")?);
- set!(styles, TextNode::FALLBACK => args.named("fallback")?);
- set!(styles, TextNode::STYLE => args.named("style")?);
- set!(styles, TextNode::WEIGHT => args.named("weight")?);
- set!(styles, TextNode::STRETCH => args.named("stretch")?);
- set!(styles, TextNode::FILL => args.named("fill")?.or_else(|| args.find()));
- set!(styles, TextNode::SIZE => args.named("size")?.or_else(|| args.find()));
- set!(styles, TextNode::TRACKING => args.named("tracking")?.map(Em::new));
- set!(styles, TextNode::TOP_EDGE => args.named("top-edge")?);
- set!(styles, TextNode::BOTTOM_EDGE => args.named("bottom-edge")?);
- set!(styles, TextNode::KERNING => args.named("kerning")?);
- set!(styles, TextNode::SMALLCAPS => args.named("smallcaps")?);
- set!(styles, TextNode::ALTERNATES => args.named("alternates")?);
- set!(styles, TextNode::STYLISTIC_SET => args.named("stylistic-set")?);
- set!(styles, TextNode::LIGATURES => args.named("ligatures")?);
- set!(styles, TextNode::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?);
- set!(styles, TextNode::HISTORICAL_LIGATURES => args.named("historical-ligatures")?);
- set!(styles, TextNode::NUMBER_TYPE => args.named("number-type")?);
- set!(styles, TextNode::NUMBER_WIDTH => args.named("number-width")?);
- set!(styles, TextNode::NUMBER_POSITION => args.named("number-position")?);
- set!(styles, TextNode::SLASHED_ZERO => args.named("slashed-zero")?);
- set!(styles, TextNode::FRACTIONS => args.named("fractions")?);
- set!(styles, TextNode::FEATURES => args.named("features")?);
-
- Ok(match body {
- Some(body) => Value::Node(body.styled(map)),
- None => Value::None,
- })
-}
-
/// `strike`: Typeset striken-through text.
pub fn strike(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
line_impl(args, LineKind::Strikethrough)
@@ -172,6 +124,53 @@ properties! {
FEATURES: Vec<(Tag, u32)> = vec![],
}
+impl Construct for TextNode {
+ fn construct(_: &mut EvalContext, args: &mut Args) -> TypResult<Node> {
+ // We don't need to do anything more here because the whole point of the
+ // text constructor is to apply the styles and that happens
+ // automatically during class construction.
+ args.expect::<Node>("body")
+ }
+}
+
+impl Set for TextNode {
+ fn set(styles: &mut Styles, args: &mut Args) -> TypResult<()> {
+ let list = args.named("family")?.or_else(|| {
+ let families: Vec<_> = args.all().collect();
+ (!families.is_empty()).then(|| families)
+ });
+
+ set!(styles, TextNode::FAMILY_LIST => list);
+ set!(styles, TextNode::SERIF_LIST => args.named("serif")?);
+ set!(styles, TextNode::SANS_SERIF_LIST => args.named("sans-serif")?);
+ set!(styles, TextNode::MONOSPACE_LIST => args.named("monospace")?);
+ set!(styles, TextNode::FALLBACK => args.named("fallback")?);
+ set!(styles, TextNode::STYLE => args.named("style")?);
+ set!(styles, TextNode::WEIGHT => args.named("weight")?);
+ set!(styles, TextNode::STRETCH => args.named("stretch")?);
+ set!(styles, TextNode::FILL => args.named("fill")?.or_else(|| args.find()));
+ set!(styles, TextNode::SIZE => args.named("size")?.or_else(|| args.find()));
+ set!(styles, TextNode::TRACKING => args.named("tracking")?.map(Em::new));
+ set!(styles, TextNode::TOP_EDGE => args.named("top-edge")?);
+ set!(styles, TextNode::BOTTOM_EDGE => args.named("bottom-edge")?);
+ set!(styles, TextNode::KERNING => args.named("kerning")?);
+ set!(styles, TextNode::SMALLCAPS => args.named("smallcaps")?);
+ set!(styles, TextNode::ALTERNATES => args.named("alternates")?);
+ set!(styles, TextNode::STYLISTIC_SET => args.named("stylistic-set")?);
+ set!(styles, TextNode::LIGATURES => args.named("ligatures")?);
+ set!(styles, TextNode::DISCRETIONARY_LIGATURES => args.named("discretionary-ligatures")?);
+ set!(styles, TextNode::HISTORICAL_LIGATURES => args.named("historical-ligatures")?);
+ set!(styles, TextNode::NUMBER_TYPE => args.named("number-type")?);
+ set!(styles, TextNode::NUMBER_WIDTH => args.named("number-width")?);
+ set!(styles, TextNode::NUMBER_POSITION => args.named("number-position")?);
+ set!(styles, TextNode::SLASHED_ZERO => args.named("slashed-zero")?);
+ set!(styles, TextNode::FRACTIONS => args.named("fractions")?);
+ set!(styles, TextNode::FEATURES => args.named("features")?);
+
+ Ok(())
+ }
+}
+
impl Debug for TextNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if f.alternate() {
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index dbec0a5e..0a2f73f5 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -110,12 +110,13 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
// Hashtag + keyword / identifier.
NodeKind::Ident(_)
| NodeKind::Let
+ | NodeKind::Set
| NodeKind::If
| NodeKind::While
| NodeKind::For
| NodeKind::Import
| NodeKind::Include => {
- let stmt = matches!(token, NodeKind::Let | NodeKind::Import);
+ let stmt = matches!(token, NodeKind::Let | NodeKind::Set | NodeKind::Import);
let group = if stmt { Group::Stmt } else { Group::Expr };
p.start_group(group);
@@ -265,6 +266,7 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
// Keywords.
Some(NodeKind::Let) => let_expr(p),
+ Some(NodeKind::Set) => set_expr(p),
Some(NodeKind::If) => if_expr(p),
Some(NodeKind::While) => while_expr(p),
Some(NodeKind::For) => for_expr(p),
@@ -507,45 +509,40 @@ fn block(p: &mut Parser) {
/// Parse a function call.
fn call(p: &mut Parser, callee: Marker) -> ParseResult {
- callee.perform(p, NodeKind::Call, |p| match p.peek_direct() {
- Some(NodeKind::LeftParen | NodeKind::LeftBracket) => {
- args(p, true);
- Ok(())
- }
- _ => {
- p.expected_at("argument list");
- Err(())
- }
- })
+ callee.perform(p, NodeKind::Call, |p| args(p, true, true))
}
/// Parse the arguments to a function call.
-fn args(p: &mut Parser, allow_template: bool) {
+fn args(p: &mut Parser, direct: bool, brackets: bool) -> ParseResult {
+ match if direct { p.peek_direct() } else { p.peek() } {
+ Some(NodeKind::LeftParen) => {}
+ Some(NodeKind::LeftBracket) if brackets => {}
+ _ => {
+ p.expected("argument list");
+ return Err(());
+ }
+ }
+
p.perform(NodeKind::CallArgs, |p| {
- if !allow_template || p.peek_direct() == Some(&NodeKind::LeftParen) {
+ if p.at(&NodeKind::LeftParen) {
p.start_group(Group::Paren);
collection(p);
p.end_group();
}
- while allow_template && p.peek_direct() == Some(&NodeKind::LeftBracket) {
+ while brackets && p.peek_direct() == Some(&NodeKind::LeftBracket) {
template(p);
}
- })
+ });
+
+ Ok(())
}
/// Parse a with expression.
fn with_expr(p: &mut Parser, marker: Marker) -> ParseResult {
marker.perform(p, NodeKind::WithExpr, |p| {
p.eat_assert(&NodeKind::With);
-
- if p.at(&NodeKind::LeftParen) {
- args(p, false);
- Ok(())
- } else {
- p.expected("argument list");
- Err(())
- }
+ args(p, false, false)
})
}
@@ -587,6 +584,15 @@ fn let_expr(p: &mut Parser) -> ParseResult {
})
}
+/// Parse a set expression.
+fn set_expr(p: &mut Parser) -> ParseResult {
+ p.perform(NodeKind::SetExpr, |p| {
+ p.eat_assert(&NodeKind::Set);
+ ident(p)?;
+ args(p, true, false)
+ })
+}
+
/// Parse an if expresion.
fn if_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::IfExpr, |p| {
@@ -612,8 +618,7 @@ fn while_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::WhileExpr, |p| {
p.eat_assert(&NodeKind::While);
expr(p)?;
- body(p)?;
- Ok(())
+ body(p)
})
}
@@ -624,8 +629,7 @@ fn for_expr(p: &mut Parser) -> ParseResult {
for_pattern(p)?;
p.eat_expect(&NodeKind::In)?;
expr(p)?;
- body(p)?;
- Ok(())
+ body(p)
})
}
@@ -664,9 +668,7 @@ fn import_expr(p: &mut Parser) -> ParseResult {
};
p.eat_expect(&NodeKind::From)?;
- expr(p)?;
-
- Ok(())
+ expr(p)
})
}
@@ -674,8 +676,7 @@ fn import_expr(p: &mut Parser) -> ParseResult {
fn include_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::IncludeExpr, |p| {
p.eat_assert(&NodeKind::Include);
- expr(p)?;
- Ok(())
+ expr(p)
})
}
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
index 6598b1f2..503158a9 100644
--- a/src/parse/parser.rs
+++ b/src/parse/parser.rs
@@ -125,6 +125,7 @@ impl<'s> Parser<'s> {
}
/// Eat, debug-asserting that the token is the given one.
+ #[track_caller]
pub fn eat_assert(&mut self, t: &NodeKind) {
debug_assert_eq!(self.peek(), Some(t));
self.eat();
@@ -199,6 +200,7 @@ impl<'s> Parser<'s> {
/// to `end_group`.
///
/// This panics if the current token does not start the given group.
+ #[track_caller]
pub fn start_group(&mut self, kind: Group) {
self.groups.push(GroupEntry { kind, prev_mode: self.tokens.mode() });
self.tokens.set_mode(match kind {
@@ -220,6 +222,7 @@ impl<'s> Parser<'s> {
/// End the parsing of a group.
///
/// This panics if no group was started.
+ #[track_caller]
pub fn end_group(&mut self) {
let group_mode = self.tokens.mode();
let group = self.groups.pop().expect("no started group");
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
index 07a6fe12..27ec046d 100644
--- a/src/parse/tokens.rs
+++ b/src/parse/tokens.rs
@@ -527,6 +527,7 @@ fn keyword(ident: &str) -> Option<NodeKind> {
"or" => NodeKind::Or,
"with" => NodeKind::With,
"let" => NodeKind::Let,
+ "set" => NodeKind::Set,
"if" => NodeKind::If,
"else" => NodeKind::Else,
"for" => NodeKind::For,
diff --git a/src/source.rs b/src/source.rs
index 509b0a76..5fd85ed9 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -149,6 +149,11 @@ impl SourceFile {
Self::new(SourceId(0), Path::new(""), src.into())
}
+ /// The root node of the untyped green tree.
+ pub fn root(&self) -> &Rc<GreenNode> {
+ &self.root
+ }
+
/// The file's abstract syntax tree.
pub fn ast(&self) -> TypResult<Markup> {
let red = RedNode::from_root(self.root.clone(), self.id);
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 8df25f59..9190953f 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -211,6 +211,8 @@ pub enum Expr {
With(WithExpr),
/// A let expression: `let x = 1`.
Let(LetExpr),
+ /// A set expression: `set text(...)`.
+ Set(SetExpr),
/// An if-else expression: `if x { y } else { z }`.
If(IfExpr),
/// A while loop expression: `while x { y }`.
@@ -238,6 +240,7 @@ impl TypedNode for Expr {
NodeKind::Closure => node.cast().map(Self::Closure),
NodeKind::WithExpr => node.cast().map(Self::With),
NodeKind::LetExpr => node.cast().map(Self::Let),
+ NodeKind::SetExpr => node.cast().map(Self::Set),
NodeKind::IfExpr => node.cast().map(Self::If),
NodeKind::WhileExpr => node.cast().map(Self::While),
NodeKind::ForExpr => node.cast().map(Self::For),
@@ -262,6 +265,7 @@ impl TypedNode for Expr {
Self::Closure(v) => v.as_red(),
Self::With(v) => v.as_red(),
Self::Let(v) => v.as_red(),
+ Self::Set(v) => v.as_red(),
Self::If(v) => v.as_red(),
Self::While(v) => v.as_red(),
Self::For(v) => v.as_red(),
@@ -838,6 +842,25 @@ impl LetExpr {
}
node! {
+ /// A set expression: `set text(...)`.
+ SetExpr
+}
+
+impl SetExpr {
+ /// The class to set style properties for.
+ pub fn class(&self) -> Ident {
+ self.0.cast_first_child().expect("set expression is missing class")
+ }
+
+ /// The style properties to set.
+ pub fn args(&self) -> CallArgs {
+ self.0
+ .cast_first_child()
+ .expect("set expression is missing argument list")
+ }
+}
+
+node! {
/// An import expression: `import a, b, c from "utils.typ"`.
ImportExpr
}
diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs
index 22e6cf50..85fbef12 100644
--- a/src/syntax/highlight.rs
+++ b/src/syntax/highlight.rs
@@ -96,22 +96,23 @@ impl Category {
NodeKind::EnDash => Some(Category::Shortcut),
NodeKind::EmDash => Some(Category::Shortcut),
NodeKind::Escape(_) => Some(Category::Escape),
+ NodeKind::Not => Some(Category::Keyword),
+ NodeKind::And => Some(Category::Keyword),
+ NodeKind::Or => Some(Category::Keyword),
+ NodeKind::With => Some(Category::Keyword),
NodeKind::Let => Some(Category::Keyword),
+ NodeKind::Set => Some(Category::Keyword),
NodeKind::If => Some(Category::Keyword),
NodeKind::Else => Some(Category::Keyword),
+ NodeKind::While => Some(Category::Keyword),
NodeKind::For => Some(Category::Keyword),
NodeKind::In => Some(Category::Keyword),
- NodeKind::While => Some(Category::Keyword),
NodeKind::Break => Some(Category::Keyword),
NodeKind::Continue => Some(Category::Keyword),
NodeKind::Return => Some(Category::Keyword),
NodeKind::Import => Some(Category::Keyword),
- NodeKind::Include => Some(Category::Keyword),
NodeKind::From => Some(Category::Keyword),
- NodeKind::Not => Some(Category::Keyword),
- NodeKind::And => Some(Category::Keyword),
- NodeKind::Or => Some(Category::Keyword),
- NodeKind::With => Some(Category::Keyword),
+ NodeKind::Include => Some(Category::Keyword),
NodeKind::Plus => Some(Category::Operator),
NodeKind::Star => Some(Category::Operator),
NodeKind::Slash => Some(Category::Operator),
@@ -139,6 +140,7 @@ impl Category {
Some(Category::Function)
}
NodeKind::WithExpr => Some(Category::Function),
+ NodeKind::SetExpr => Some(Category::Function),
NodeKind::Call => Some(Category::Function),
_ => Some(Category::Variable),
},
@@ -161,21 +163,22 @@ impl Category {
NodeKind::Array => None,
NodeKind::Dict => None,
NodeKind::Named => None,
+ NodeKind::Template => None,
NodeKind::Group => None,
+ NodeKind::Block => None,
NodeKind::Unary => None,
NodeKind::Binary => None,
NodeKind::Call => None,
NodeKind::CallArgs => None,
+ NodeKind::Spread => None,
NodeKind::Closure => None,
NodeKind::ClosureParams => None,
- NodeKind::Spread => None,
- NodeKind::Template => None,
- NodeKind::Block => None,
- NodeKind::ForExpr => None,
- NodeKind::WhileExpr => None,
- NodeKind::IfExpr => None,
- NodeKind::LetExpr => None,
NodeKind::WithExpr => None,
+ NodeKind::LetExpr => None,
+ NodeKind::SetExpr => None,
+ NodeKind::IfExpr => None,
+ NodeKind::WhileExpr => None,
+ NodeKind::ForExpr => None,
NodeKind::ForPattern => None,
NodeKind::ImportExpr => None,
NodeKind::ImportItems => None,
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index e9011a4d..b9b00487 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -83,19 +83,15 @@ impl Default for Green {
impl Debug for Green {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "{:?}: {}", self.kind(), self.len())?;
- if let Self::Node(n) = self {
- if !n.children.is_empty() {
- f.write_str(" ")?;
- f.debug_list().entries(&n.children).finish()?;
- }
+ match self {
+ Self::Node(node) => node.fmt(f),
+ Self::Token(token) => token.fmt(f),
}
- Ok(())
}
}
/// An inner node in the untyped green tree.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Clone, PartialEq)]
pub struct GreenNode {
/// Node metadata.
data: GreenData,
@@ -145,8 +141,19 @@ impl From<Rc<GreenNode>> for Green {
}
}
+impl Debug for GreenNode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.data.fmt(f)?;
+ if !self.children.is_empty() {
+ f.write_str(" ")?;
+ f.debug_list().entries(&self.children).finish()?;
+ }
+ Ok(())
+ }
+}
+
/// Data shared between inner and leaf nodes.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Clone, PartialEq)]
pub struct GreenData {
/// What kind of node this is (each kind would have its own struct in a
/// strongly typed AST).
@@ -178,6 +185,12 @@ impl From<GreenData> for Green {
}
}
+impl Debug for GreenData {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{:?}: {}", self.kind, self.len())
+ }
+}
+
/// A owned wrapper for a green node with span information.
///
/// Owned variant of [`RedRef`]. Can be [cast](Self::cast) to an AST node.
@@ -465,6 +478,8 @@ pub enum NodeKind {
Auto,
/// The `let` keyword.
Let,
+ /// The `set` keyword.
+ Set,
/// The `if` keyword.
If,
/// The `else` keyword.
@@ -552,8 +567,12 @@ pub enum NodeKind {
Dict,
/// A named pair: `thickness: 3pt`.
Named,
+ /// A template expression: `[*Hi* there!]`.
+ Template,
/// A grouped expression: `(1 + 2)`.
Group,
+ /// A block expression: `{ let x = 1; x + 2 }`.
+ Block,
/// A unary operation: `-x`.
Unary,
/// A binary operation: `a + b`.
@@ -562,39 +581,37 @@ pub enum NodeKind {
Call,
/// A function call's argument list: `(x, y)`.
CallArgs,
+ /// Spreaded arguments or a parameter sink: `..x`.
+ Spread,
/// A closure expression: `(x, y) => z`.
Closure,
/// A closure's parameters: `(x, y)`.
ClosureParams,
- /// A parameter sink: `..x`.
- Spread,
- /// A template expression: `[*Hi* there!]`.
- Template,
- /// A block expression: `{ let x = 1; x + 2 }`.
- Block,
- /// A for loop expression: `for x in y { ... }`.
- ForExpr,
- /// A while loop expression: `while x { ... }`.
- WhileExpr,
- /// An if expression: `if x { ... }`.
- IfExpr,
+ /// A with expression: `f with (x, y: 1)`.
+ WithExpr,
/// A let expression: `let x = 1`.
LetExpr,
- /// The `with` expression: `with (1)`.
- WithExpr,
+ /// A set expression: `set text(...)`.
+ SetExpr,
+ /// An if-else expression: `if x { y } else { z }`.
+ IfExpr,
+ /// A while loop expression: `while x { ... }`.
+ WhileExpr,
+ /// A for loop expression: `for x in y { ... }`.
+ ForExpr,
/// A for loop's destructuring pattern: `x` or `x, y`.
ForPattern,
- /// The import expression: `import x from "foo.typ"`.
+ /// An import expression: `import a, b, c from "utils.typ"`.
ImportExpr,
/// Items to import: `a, b, c`.
ImportItems,
- /// The include expression: `include "foo.typ"`.
+ /// An include expression: `include "chapter1.typ"`.
IncludeExpr,
- /// Two slashes followed by inner contents, terminated with a newline:
- /// `//<str>\n`.
+ /// A line comment, two slashes followed by inner contents, terminated with
+ /// a newline: `//<str>\n`.
LineComment,
- /// A slash and a star followed by inner contents, terminated with a star
- /// and a slash: `/*<str>*/`.
+ /// A block comment, a slash and a star followed by inner contents,
+ /// terminated with a star and a slash: `/*<str>*/`.
///
/// The comment can contain nested block comments.
BlockComment,
@@ -616,11 +633,6 @@ pub enum ErrorPos {
}
impl NodeKind {
- /// Whether this is some kind of parenthesis.
- pub fn is_paren(&self) -> bool {
- matches!(self, Self::LeftParen | Self::RightParen)
- }
-
/// Whether this is some kind of bracket.
pub fn is_bracket(&self) -> bool {
matches!(self, Self::LeftBracket | Self::RightBracket)
@@ -631,6 +643,11 @@ impl NodeKind {
matches!(self, Self::LeftBrace | Self::RightBrace)
}
+ /// Whether this is some kind of parenthesis.
+ pub fn is_paren(&self) -> bool {
+ matches!(self, Self::LeftParen | Self::RightParen)
+ }
+
/// Whether this is some kind of error.
pub fn is_error(&self) -> bool {
matches!(self, NodeKind::Error(_, _) | NodeKind::Unknown(_))
@@ -672,6 +689,7 @@ impl NodeKind {
Self::None => "`none`",
Self::Auto => "`auto`",
Self::Let => "keyword `let`",
+ Self::Set => "keyword `set`",
Self::If => "keyword `if`",
Self::Else => "keyword `else`",
Self::For => "keyword `for`",
@@ -712,21 +730,22 @@ impl NodeKind {
Self::Array => "array",
Self::Dict => "dictionary",
Self::Named => "named argument",
+ Self::Template => "template",
Self::Group => "group",
+ Self::Block => "block",
Self::Unary => "unary expression",
Self::Binary => "binary expression",
Self::Call => "call",
Self::CallArgs => "call arguments",
+ Self::Spread => "parameter sink",
Self::Closure => "closure",
Self::ClosureParams => "closure parameters",
- Self::Spread => "parameter sink",
- Self::Template => "template",
- Self::Block => "block",
- Self::ForExpr => "for-loop expression",
- Self::WhileExpr => "while-loop expression",
- Self::IfExpr => "`if` expression",
- Self::LetExpr => "`let` expression",
Self::WithExpr => "`with` expression",
+ Self::LetExpr => "`let` expression",
+ Self::SetExpr => "`set` expression",
+ Self::IfExpr => "`if` expression",
+ Self::WhileExpr => "while-loop expression",
+ Self::ForExpr => "for-loop expression",
Self::ForPattern => "for-loop destructuring pattern",
Self::ImportExpr => "`import` expression",
Self::ImportItems => "import items",
diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs
index c453fb56..62ecb8cd 100644
--- a/src/syntax/pretty.rs
+++ b/src/syntax/pretty.rs
@@ -225,6 +225,7 @@ impl Pretty for Expr {
Self::Closure(v) => v.pretty(p),
Self::With(v) => v.pretty(p),
Self::Let(v) => v.pretty(p),
+ Self::Set(v) => v.pretty(p),
Self::If(v) => v.pretty(p),
Self::While(v) => v.pretty(p),
Self::For(v) => v.pretty(p),
@@ -444,6 +445,16 @@ impl Pretty for LetExpr {
}
}
+impl Pretty for SetExpr {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str("set ");
+ self.class().pretty(p);
+ p.push_str("(");
+ self.args().pretty(p);
+ p.push(')');
+ }
+}
+
impl Pretty for IfExpr {
fn pretty(&self, p: &mut Printer) {
p.push_str("if ");
@@ -639,6 +650,7 @@ mod tests {
// Control flow.
roundtrip("#let x = 1 + 2");
roundtrip("#let f(x) = y");
+ roundtrip("#set text(size: 12pt)");
roundtrip("#if x [y] else [z]");
roundtrip("#if x {} else if y {} else {}");
roundtrip("#while x {y}");
diff --git a/tests/typ/code/call.typ b/tests/typ/code/call.typ
index 2c16af1c..5736c63b 100644
--- a/tests/typ/code/call.typ
+++ b/tests/typ/code/call.typ
@@ -5,7 +5,8 @@
// Ref: true
// Ommitted space.
-[#font(weight:"bold")Bold]
+#let f() = {}
+[#f()*Bold*]
// Call return value of function with body.
#let f(x, body) = (y) => [#x] + body + [#y]
@@ -44,25 +45,25 @@
}
---
-// Error: 2-6 expected function or collection, found boolean
+// Error: 2-6 expected callable or collection, found boolean
{true()}
---
#let x = "x"
-// Error: 1-3 expected function or collection, found string
+// Error: 1-3 expected callable or collection, found string
#x()
---
#let f(x) = x
-// Error: 1-6 expected function or collection, found integer
+// Error: 1-6 expected callable or collection, found integer
#f(1)(2)
---
#let f(x) = x
-// Error: 1-6 expected function or collection, found template
+// Error: 1-6 expected callable or collection, found template
#f[1](2)
---
diff --git a/tests/typ/code/include.typ b/tests/typ/code/include.typ
index 83e00384..1e5d5827 100644
--- a/tests/typ/code/include.typ
+++ b/tests/typ/code/include.typ
@@ -1,7 +1,7 @@
// Test include statements.
---
-#page(width: 200pt)
+#set page(width: 200pt)
= Document
diff --git a/tests/typ/code/spread.typ b/tests/typ/code/spread.typ
index 41e790a4..5f7d2061 100644
--- a/tests/typ/code/spread.typ
+++ b/tests/typ/code/spread.typ
@@ -4,14 +4,14 @@
---
// Test standard argument overriding.
{
- let font(style: "normal", weight: "regular") = {
+ let f(style: "normal", weight: "regular") = {
"(style: " + style + ", weight: " + weight + ")"
}
- let myfont(..args) = font(weight: "bold", ..args)
- test(myfont(), "(style: normal, weight: bold)")
- test(myfont(weight: "black"), "(style: normal, weight: black)")
- test(myfont(style: "italic"), "(style: italic, weight: bold)")
+ let myf(..args) = f(weight: "bold", ..args)
+ test(myf(), "(style: normal, weight: bold)")
+ test(myf(weight: "black"), "(style: normal, weight: black)")
+ test(myf(style: "italic"), "(style: italic, weight: bold)")
}
---
diff --git a/tests/typ/coma.typ b/tests/typ/coma.typ
index 77baec45..ef9e9f86 100644
--- a/tests/typ/coma.typ
+++ b/tests/typ/coma.typ
@@ -1,4 +1,4 @@
-#page(width: 450pt, margins: 1cm)
+#set page(width: 450pt, margins: 1cm)
*Technische Universität Berlin* #h(1fr) *WiSe 2019/2020* \
*Fakultät II, Institut for Mathematik* #h(1fr) Woche 3 \
diff --git a/tests/typ/elements/circle.typ b/tests/typ/elements/circle.typ
index ad9d3a4e..8b795830 100644
--- a/tests/typ/elements/circle.typ
+++ b/tests/typ/elements/circle.typ
@@ -23,7 +23,7 @@ Center-aligned rect in auto-sized circle.
Rect in auto-sized circle. \
#circle(fill: forest,
rect(fill: conifer, stroke: white, padding: 4pt)[
- #font(8pt)
+ #set text(8pt)
But, soft! what light through yonder window breaks?
]
)
@@ -38,7 +38,7 @@ Expanded by height.
---
// Test relative sizing.
#let centered(body) = align(center + horizon, body)
-#font(fill: white)
+#set text(fill: white)
#rect(width: 100pt, height: 50pt, fill: rgb("aaa"), centered[
#circle(radius: 10pt, fill: eastern, centered[A]) // D=20pt
#circle(height: 60%, fill: eastern, centered[B]) // D=30pt
diff --git a/tests/typ/elements/ellipse.typ b/tests/typ/elements/ellipse.typ
index 39c73a6f..154144c4 100644
--- a/tests/typ/elements/ellipse.typ
+++ b/tests/typ/elements/ellipse.typ
@@ -18,6 +18,6 @@ Rect in ellipse in fixed rect. \
Auto-sized ellipse. \
#ellipse(fill: conifer, stroke: forest, thickness: 3pt, padding: 3pt)[
- #font(8pt)
+ #set text(8pt)
But, soft! what light through yonder window breaks?
]
diff --git a/tests/typ/elements/image.typ b/tests/typ/elements/image.typ
index 7fddb12d..8817713f 100644
--- a/tests/typ/elements/image.typ
+++ b/tests/typ/elements/image.typ
@@ -7,7 +7,7 @@
#image("../../res/rhino.png")
// Load an RGB JPEG image.
-#page(height: 60pt)
+#set page(height: 60pt)
#image("../../res/tiger.jpg")
---
@@ -25,7 +25,7 @@
---
// Test all three fit modes.
-#page(height: 50pt, margins: 0pt)
+#set page(height: 50pt, margins: 0pt)
#grid(
columns: (1fr, 1fr, 1fr),
rows: 100%,
@@ -37,7 +37,7 @@
---
// Does not fit to remaining height of page.
-#page(height: 60pt)
+#set page(height: 60pt)
Stuff \
Stuff
#image("../../res/rhino.png")
diff --git a/tests/typ/elements/rect.typ b/tests/typ/elements/rect.typ
index b3d4d286..add39b80 100644
--- a/tests/typ/elements/rect.typ
+++ b/tests/typ/elements/rect.typ
@@ -5,7 +5,7 @@
#rect()
---
-#page(width: 150pt)
+#set page(width: 150pt)
// Fit to text.
#rect(fill: conifer, padding: 3pt)[Textbox]
diff --git a/tests/typ/elements/square.typ b/tests/typ/elements/square.typ
index f09fc3e0..c4ece778 100644
--- a/tests/typ/elements/square.typ
+++ b/tests/typ/elements/square.typ
@@ -8,7 +8,7 @@
---
// Test auto-sized square.
#square(fill: eastern, padding: 5pt)[
- #font(fill: white, weight: "bold")
+ #set text(fill: white, weight: "bold")
Typst
]
@@ -21,14 +21,14 @@
---
// Test text overflowing height.
-#page(width: 75pt, height: 100pt)
+#set page(width: 75pt, height: 100pt)
#square(fill: conifer)[
But, soft! what light through yonder window breaks?
]
---
// Test that square does not overflow page.
-#page(width: 100pt, height: 75pt)
+#set page(width: 100pt, height: 75pt)
#square(fill: conifer)[
But, soft! what light through yonder window breaks?
]
diff --git a/tests/typ/layout/align.typ b/tests/typ/layout/align.typ
index 13b88ac1..09c4dee5 100644
--- a/tests/typ/layout/align.typ
+++ b/tests/typ/layout/align.typ
@@ -1,7 +1,7 @@
// Test alignment.
---
-#page(height: 100pt)
+#set page(height: 100pt)
#stack(dir: ltr,
align(left, square(size: 15pt, fill: eastern)),
align(center, square(size: 20pt, fill: eastern)),
diff --git a/tests/typ/layout/aspect.typ b/tests/typ/layout/aspect.typ
index c9b8ee41..2c3e9b0c 100644
--- a/tests/typ/layout/aspect.typ
+++ b/tests/typ/layout/aspect.typ
@@ -3,14 +3,14 @@
---
// Test relative width and height and size that is smaller
// than default size.
-#page(width: 120pt, height: 70pt)
+#set page(width: 120pt, height: 70pt)
#square(width: 50%, align(bottom)[A])
#square(height: 50%)
#box(stack(square(size: 10pt), 5pt, square(size: 10pt, [B])))
---
// Test alignment in automatically sized square and circle.
-#font(8pt)
+#set text(8pt)
#square(padding: 4pt)[
Hey there, #align(center + bottom, rotate(180deg, [you!]))
]
@@ -23,19 +23,19 @@
---
// Test square that is limited by region size.
-#page(width: 20pt, height: 10pt, margins: 0pt)
+#set page(width: 20pt, height: 10pt, margins: 0pt)
#stack(dir: ltr, square(fill: forest), square(fill: conifer))
---
// Test different ways of sizing.
-#page(width: 120pt, height: 40pt)
+#set page(width: 120pt, height: 40pt)
#circle(radius: 5pt)
#circle(width: 10%)
#circle(height: 50%)
---
// Test square that is overflowing due to its aspect ratio.
-#page(width: 40pt, height: 20pt, margins: 5pt)
+#set page(width: 40pt, height: 20pt, margins: 5pt)
#square(width: 100%)
#square(width: 100%)[Hello]
diff --git a/tests/typ/layout/background.typ b/tests/typ/layout/background.typ
index f55262b2..f64bf0ee 100644
--- a/tests/typ/layout/background.typ
+++ b/tests/typ/layout/background.typ
@@ -1,8 +1,8 @@
// Test placing a background image on a page.
---
-#page(paper: "a10", flipped: true)
-#font(fill: white)
+#set page(paper: "a10", flipped: true)
+#set text(fill: white)
#place(
dx: -10pt,
dy: -10pt,
diff --git a/tests/typ/layout/containers.typ b/tests/typ/layout/containers.typ
index d4556a0f..14258c1e 100644
--- a/tests/typ/layout/containers.typ
+++ b/tests/typ/layout/containers.typ
@@ -12,7 +12,7 @@ Apart
---
// Test block over multiple pages.
-#page(height: 60pt)
+#set page(height: 60pt)
First!
#block[
diff --git a/tests/typ/layout/grid-1.typ b/tests/typ/layout/grid-1.typ
index 647e366f..a6596c98 100644
--- a/tests/typ/layout/grid-1.typ
+++ b/tests/typ/layout/grid-1.typ
@@ -3,7 +3,7 @@
---
#let cell(width, color) = rect(width: width, height: 2cm, fill: color)
-#page(width: 100pt, height: 140pt)
+#set page(width: 100pt, height: 140pt)
#grid(
columns: (auto, 1fr, 3fr, 0.25cm, 3%, 2mm + 10%),
cell(0.5cm, rgb("2a631a")),
@@ -31,7 +31,7 @@
)
---
-#page(height: 3cm, margins: 0pt)
+#set page(height: 3cm, margins: 0pt)
#grid(
columns: (1fr,),
rows: (1fr, auto, 2fr),
diff --git a/tests/typ/layout/grid-2.typ b/tests/typ/layout/grid-2.typ
index c522a402..7df64a56 100644
--- a/tests/typ/layout/grid-2.typ
+++ b/tests/typ/layout/grid-2.typ
@@ -1,7 +1,7 @@
// Test using the `grid` function to create a finance table.
---
-#page(width: 12cm, height: 2.5cm)
+#set page(width: 12cm, height: 2.5cm)
#grid(
columns: 5,
column-gutter: (2fr, 1fr, 1fr),
diff --git a/tests/typ/layout/grid-3.typ b/tests/typ/layout/grid-3.typ
index 6f7f6977..8d04722e 100644
--- a/tests/typ/layout/grid-3.typ
+++ b/tests/typ/layout/grid-3.typ
@@ -1,7 +1,7 @@
// Test grid cells that overflow to the next region.
---
-#page(width: 5cm, height: 3cm)
+#set page(width: 5cm, height: 3cm)
#grid(
columns: 2,
row-gutter: 8pt,
@@ -18,7 +18,7 @@
---
// Test a column that starts overflowing right after another row/column did
// that.
-#page(width: 5cm, height: 2cm)
+#set page(width: 5cm, height: 2cm)
#grid(
columns: 4 * (1fr,),
row-gutter: 10pt,
@@ -32,7 +32,7 @@
---
// Test two columns in the same row overflowing by a different amount.
-#page(width: 5cm, height: 2cm)
+#set page(width: 5cm, height: 2cm)
#grid(
columns: 3 * (1fr,),
row-gutter: 8pt,
@@ -48,7 +48,7 @@
---
// Test grid within a grid, overflowing.
-#page(width: 5cm, height: 2.25cm)
+#set page(width: 5cm, height: 2.25cm)
#grid(
columns: 4 * (1fr,),
row-gutter: 10pt,
@@ -62,7 +62,7 @@
---
// Test partition of `fr` units before and after multi-region layout.
-#page(width: 5cm, height: 4cm)
+#set page(width: 5cm, height: 4cm)
#grid(
columns: 2 * (1fr,),
rows: (1fr, 2fr, auto, 1fr, 1cm),
diff --git a/tests/typ/layout/grid-4.typ b/tests/typ/layout/grid-4.typ
index d6aa6358..e02d98c5 100644
--- a/tests/typ/layout/grid-4.typ
+++ b/tests/typ/layout/grid-4.typ
@@ -23,7 +23,7 @@
---
// Test that all three kinds of rows use the correct bases.
-#page(height: 4cm, margins: 0cm)
+#set page(height: 4cm, margins: 0cm)
#grid(
rows: (1cm, 1fr, 1fr, auto),
rect(height: 50%, width: 100%, fill: conifer),
diff --git a/tests/typ/layout/grid-5.typ b/tests/typ/layout/grid-5.typ
index 585fc6ce..db7c525a 100644
--- a/tests/typ/layout/grid-5.typ
+++ b/tests/typ/layout/grid-5.typ
@@ -1,7 +1,7 @@
---
// Test that trailing linebreak doesn't overflow the region.
-#page(height: 2cm)
+#set page(height: 2cm)
#grid[
Hello \
Hello \
@@ -12,7 +12,7 @@
---
// Test that broken cell expands vertically.
-#page(height: 2.25cm)
+#set page(height: 2.25cm)
#grid(
columns: 2,
gutter: 10pt,
diff --git a/tests/typ/layout/pad.typ b/tests/typ/layout/pad.typ
index 1fa0f79d..502137ec 100644
--- a/tests/typ/layout/pad.typ
+++ b/tests/typ/layout/pad.typ
@@ -19,7 +19,7 @@ Hi #box(pad(left: 10pt)[A]) there
---
// Test that the pad node doesn't consume the whole region.
-#page(height: 6cm)
+#set page(height: 6cm)
#align(left)[Before]
#pad(10pt, image("../../res/tiger.jpg"))
#align(right)[After]
diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ
index 3dad26c8..35f338f4 100644
--- a/tests/typ/layout/page.typ
+++ b/tests/typ/layout/page.typ
@@ -2,33 +2,33 @@
---
// Set width and height.
-#page(width: 80pt, height: 80pt)
-[#page(width: 40pt) High]
-[#page(height: 40pt) Wide]
+#set page(width: 80pt, height: 80pt)
+[#set page(width: 40pt);High]
+[#set page(height: 40pt);Wide]
// Set all margins at once.
[
- #page(margins: 5pt)
+ #set page(margins: 5pt)
#place(top + left)[TL]
#place(bottom + right)[BR]
]
// Set individual margins.
-#page(height: 40pt)
-[#page(left: 0pt) #align(left)[Left]]
-[#page(right: 0pt) #align(right)[Right]]
-[#page(top: 0pt) #align(top)[Top]]
-[#page(bottom: 0pt) #align(bottom)[Bottom]]
+#set page(height: 40pt)
+[#set page(left: 0pt); #align(left)[Left]]
+[#set page(right: 0pt); #align(right)[Right]]
+[#set page(top: 0pt); #align(top)[Top]]
+[#set page(bottom: 0pt); #align(bottom)[Bottom]]
// Ensure that specific margins override general margins.
-[#page(margins: 0pt, left: 20pt) Overriden]
+[#set page(margins: 0pt, left: 20pt); Overriden]
// Flipped predefined paper.
-[#page(paper: "a11", flipped: true) Flipped A11]
+[#set page(paper: "a11", flipped: true);Flipped A11]
---
-#page(width: 80pt, height: 40pt, fill: eastern)
-#font(15pt, "Roboto", fill: white, smallcaps: true)[Typst]
+#set page(width: 80pt, height: 40pt, fill: eastern)
+#text(15pt, "Roboto", fill: white, smallcaps: true)[Typst]
-#page(width: 40pt, fill: none, margins: auto, top: 10pt)
+#set page(width: 40pt, fill: none, margins: auto, top: 10pt)
Hi
diff --git a/tests/typ/layout/pagebreak.typ b/tests/typ/layout/pagebreak.typ
index e0ffc92e..9a74d2bf 100644
--- a/tests/typ/layout/pagebreak.typ
+++ b/tests/typ/layout/pagebreak.typ
@@ -3,7 +3,7 @@
---
First of two
#pagebreak()
-#page(height: 40pt)
+#set page(height: 40pt)
Second of two
---
@@ -12,7 +12,7 @@ A
#box[
B
#pagebreak()
- #page("a4")
+ #set page("a4")
]
C
@@ -23,13 +23,13 @@ D
---
// Test a combination of pages with bodies and normal content.
-#page(width: 80pt, height: 30pt)
+#set page(width: 80pt, height: 30pt)
-Fi[#page(width: 80pt)rst]
-[#page(width: 70pt) Second]
+Fi[#set page(width: 80pt);rst]
+[#set page(width: 70pt); Second]
#pagebreak()
#pagebreak()
Fourth
#page(height: 20pt)[]
Sixth
-[#page() Seventh]
+[#set page(); Seventh]
diff --git a/tests/typ/layout/placed.typ b/tests/typ/layout/placed.typ
index 017cdc20..527e0559 100644
--- a/tests/typ/layout/placed.typ
+++ b/tests/typ/layout/placed.typ
@@ -1,7 +1,7 @@
// Test the `place` function.
---
-#page("a8")
+#set page("a8")
#place(bottom + center)[© Typst]
= Placement
@@ -26,7 +26,7 @@ the line breaks still had to be inserted manually.
---
// Test how the placed node interacts with paragraph spacing around it.
-#page("a8", height: 60pt)
+#set page("a8", height: 60pt)
First
diff --git a/tests/typ/layout/spacing.typ b/tests/typ/layout/spacing.typ
index 78b778c5..98a6100c 100644
--- a/tests/typ/layout/spacing.typ
+++ b/tests/typ/layout/spacing.typ
@@ -20,8 +20,8 @@ Add #h(10pt) #h(10pt) up
---
// Test that spacing has style properties.
-A[#par(align: right)#h(1cm)]B
-[#page(height: 20pt)#v(1cm)]
+A[#set par(align: right);#h(1cm)]B
+[#set page(height: 20pt);#v(1cm)]
B
---
diff --git a/tests/typ/layout/stack-1.typ b/tests/typ/layout/stack-1.typ
index 02083d2b..a4a0d6b8 100644
--- a/tests/typ/layout/stack-1.typ
+++ b/tests/typ/layout/stack-1.typ
@@ -15,13 +15,13 @@
#let items = for w in widths { (align(right, shaded(w)),) }
-#page(width: 50pt, margins: 0pt)
+#set page(width: 50pt, margins: 0pt)
#stack(dir: btt, ..items)
---
// Test RTL alignment.
-#page(width: 50pt, margins: 5pt)
-#font(8pt)
+#set page(width: 50pt, margins: 5pt)
+#set text(8pt)
#stack(dir: rtl,
align(center, [A]),
align(left, [B]),
@@ -30,8 +30,8 @@
---
// Test spacing.
-#page(width: 50pt, margins: 0pt)
-#par(spacing: 5pt)
+#set page(width: 50pt, margins: 0pt)
+#set par(spacing: 5pt)
#let x = square(size: 10pt, fill: eastern)
#stack(dir: rtl, spacing: 5pt, x, x, x)
@@ -40,7 +40,7 @@
---
// Test overflow.
-#page(width: 50pt, height: 30pt, margins: 0pt)
+#set page(width: 50pt, height: 30pt, margins: 0pt)
#box(stack(
rect(width: 40pt, height: 20pt, fill: conifer),
rect(width: 30pt, height: 13pt, fill: forest),
diff --git a/tests/typ/layout/stack-2.typ b/tests/typ/layout/stack-2.typ
index 2167f48f..f88f7a58 100644
--- a/tests/typ/layout/stack-2.typ
+++ b/tests/typ/layout/stack-2.typ
@@ -1,7 +1,7 @@
// Test fr units in stacks.
---
-#page(height: 3.5cm)
+#set page(height: 3.5cm)
#stack(
dir: ltr,
spacing: 1fr,
@@ -15,8 +15,8 @@ from #h(1fr) the #h(1fr) wonderful
World! 🌍
---
-#page(height: 2cm)
-#font(white)
+#set page(height: 2cm)
+#set text(white)
#rect(fill: forest)[
#v(1fr)
#h(1fr) Hi you! #h(5pt)
diff --git a/tests/typ/layout/transform.typ b/tests/typ/layout/transform.typ
index 5b1fa2a1..82ee1390 100644
--- a/tests/typ/layout/transform.typ
+++ b/tests/typ/layout/transform.typ
@@ -23,13 +23,13 @@
[X]
}
-#font("Latin Modern Math", size)
+#set text("Latin Modern Math", size)
Neither #tex, \
nor #xetex!
---
// Test combination of scaling and rotation.
-#page(height: 80pt)
+#set page(height: 80pt)
#align(center + horizon,
rotate(20deg, scale(70%, image("../../res/tiger.jpg")))
)
@@ -43,7 +43,7 @@ nor #xetex!
---
// Test setting scaling origin.
#let r = rect(width: 100pt, height: 10pt, fill: forest)
-#page(height: 65pt)
+#set page(height: 65pt)
#scale(r, x: 50%, y: 200%, origin: left + top)
#scale(r, x: 50%, origin: center)
#scale(r, x: 50%, y: 200%, origin: right + bottom)
diff --git a/tests/typ/text/baseline.typ b/tests/typ/text/baseline.typ
index f22fce08..7100ab52 100644
--- a/tests/typ/text/baseline.typ
+++ b/tests/typ/text/baseline.typ
@@ -1,4 +1,4 @@
// Test text baseline.
---
-Hi #font(150%)[You], #font(75%)[how are you?]
+Hi #text(150%)[You], #text(75%)[how are you?]
diff --git a/tests/typ/text/basic.typ b/tests/typ/text/basic.typ
index 0c6c8896..a074a046 100644
--- a/tests/typ/text/basic.typ
+++ b/tests/typ/text/basic.typ
@@ -1,7 +1,7 @@
// Test simple text.
---
-#page(width: 250pt, height: 120pt)
+#set page(width: 250pt, height: 120pt)
But, soft! what light through yonder window breaks? It is the east, and Juliet
is the sun. Arise, fair sun, and kill the envious moon, Who is already sick and
diff --git a/tests/typ/text/bidi.typ b/tests/typ/text/bidi.typ
index 7d33aeea..658c7fa6 100644
--- a/tests/typ/text/bidi.typ
+++ b/tests/typ/text/bidi.typ
@@ -2,55 +2,55 @@
---
// Test reordering with different top-level paragraph directions.
-#let text = [Text טֶקסט]
-#font(serif, "Noto Serif Hebrew")
-#par(lang: "he") {text}
-#par(lang: "de") {text}
+#let content = [Text טֶקסט]
+#set text(serif, "Noto Serif Hebrew")
+#par(lang: "he", content)
+#par(lang: "de", content)
---
// Test that consecutive, embedded LTR runs stay LTR.
// Here, we have two runs: "A" and italic "B".
-#let text = [أنت A_B_مطرC]
-#font(serif, "Noto Sans Arabic")
-#par(lang: "ar") {text}
-#par(lang: "de") {text}
+#let content = [أنت A_B_مطرC]
+#set text(serif, "Noto Sans Arabic")
+#par(lang: "ar", content)
+#par(lang: "de", content)
---
// Test that consecutive, embedded RTL runs stay RTL.
// Here, we have three runs: "גֶ", bold "שֶׁ", and "ם".
-#let text = [Aגֶ*שֶׁ*םB]
-#font(serif, "Noto Serif Hebrew")
-#par(lang: "he") {text}
-#par(lang: "de") {text}
+#let content = [Aגֶ*שֶׁ*םB]
+#set text(serif, "Noto Serif Hebrew")
+#par(lang: "he", content)
+#par(lang: "de", content)
---
// Test embedding up to level 4 with isolates.
-#font(serif, "Noto Serif Hebrew", "Twitter Color Emoji")
-#par(dir: rtl)
+#set text(serif, "Noto Serif Hebrew", "Twitter Color Emoji")
+#set par(dir: rtl)
א\u{2066}A\u{2067}Bב\u{2069}?
---
// Test hard line break (leads to two paragraphs in unicode-bidi).
-#font("Noto Sans Arabic", serif)
-#par(lang: "ar")
+#set text("Noto Sans Arabic", serif)
+#set par(lang: "ar")
Life المطر هو الحياة \
الحياة تمطر is rain.
---
// Test spacing.
-#font(serif, "Noto Serif Hebrew")
+#set text(serif, "Noto Serif Hebrew")
L #h(1cm) ריווחR \
Lריווח #h(1cm) R
---
// Test inline object.
-#font("Noto Serif Hebrew", serif)
-#par(lang: "he")
+#set text("Noto Serif Hebrew", serif)
+#set par(lang: "he")
קרנפיםRh#image("../../res/rhino.png", height: 11pt)inoחיים
---
// Test setting a vertical direction.
// Ref: false
-// Error: 11-14 must be horizontal
-#par(dir: ttb)
+// Error: 15-18 must be horizontal
+#set par(dir: ttb)
diff --git a/tests/typ/text/chinese.typ b/tests/typ/text/chinese.typ
index 0800a220..04fa7027 100644
--- a/tests/typ/text/chinese.typ
+++ b/tests/typ/text/chinese.typ
@@ -1,7 +1,7 @@
// Test chinese text from Wikipedia.
---
-#font("Noto Serif CJK SC")
+#set text("Noto Serif CJK SC")
是美国广播公司电视剧《迷失》第3季的第22和23集,也是全剧的第71集和72集
由执行制作人戴蒙·林道夫和卡尔顿·库斯编剧,导演则是另一名执行制作人杰克·本德
diff --git a/tests/typ/text/decorations.typ b/tests/typ/text/decorations.typ
index 9cd7b096..14dfe821 100644
--- a/tests/typ/text/decorations.typ
+++ b/tests/typ/text/decorations.typ
@@ -13,7 +13,7 @@
#underline(red)[Critical information is conveyed here.]
// Inherits font color.
-#font(fill: red, underline[Change with the wind.])
+#text(fill: red, underline[Change with the wind.])
// Both over- and underline.
#overline(underline[Running amongst the wolves.])
diff --git a/tests/typ/text/features.typ b/tests/typ/text/features.typ
index d60583d8..3f61873e 100644
--- a/tests/typ/text/features.typ
+++ b/tests/typ/text/features.typ
@@ -2,72 +2,73 @@
---
// Test turning kerning off.
-#font(kerning: true)[Tq] \
-#font(kerning: false)[Tq]
+#text(kerning: true)[Tq] \
+#text(kerning: false)[Tq]
---
// Test smallcaps.
-#font("Roboto")
-#font(smallcaps: true)[Smallcaps]
+#set text("Roboto")
+#text(smallcaps: true)[Smallcaps]
---
// Test alternates and stylistic sets.
-#font("IBM Plex Serif")
-a vs #font(alternates: true)[a] \
-ß vs #font(stylistic-set: 5)[ß]
+#set text("IBM Plex Serif")
+a vs #text(alternates: true)[a] \
+ß vs #text(stylistic-set: 5)[ß]
---
// Test ligatures.
-fi vs. #font(ligatures: false)[No fi] \
+fi vs. #text(ligatures: false)[No fi] \
---
// Test number type.
-#font("Roboto")
-#font(number-type: "old-style") 0123456789 \
-#font(number-type: auto)[0123456789]
+#set text("Roboto")
+#set text(number-type: "old-style")
+0123456789 \
+#text(number-type: auto)[0123456789]
---
// Test number width.
-#font("Roboto")
-#font(number-width: "proportional")[0123456789] \
-#font(number-width: "tabular")[3456789123] \
-#font(number-width: "tabular")[0123456789]
+#set text("Roboto")
+#text(number-width: "proportional")[0123456789] \
+#text(number-width: "tabular")[3456789123] \
+#text(number-width: "tabular")[0123456789]
---
// Test number position.
-#font("IBM Plex Sans")
-#font(number-position: "normal")[C2H4] \
-#font(number-position: "subscript")[C2H4] \
-#font(number-position: "superscript")[C2H4]
+#set text("IBM Plex Sans")
+#text(number-position: "normal")[C2H4] \
+#text(number-position: "subscript")[C2H4] \
+#text(number-position: "superscript")[C2H4]
---
// Test extra number stuff.
-#font("IBM Plex Sans")
-0 vs. #font(slashed-zero: true)[0] \
-1/2 vs. #font(fractions: true)[1/2]
+#set text("IBM Plex Sans")
+0 vs. #text(slashed-zero: true)[0] \
+1/2 vs. #text(fractions: true)[1/2]
---
// Test raw features.
-#font("Roboto")
-#font(features: ("smcp",))[Smcp] \
-fi vs. #font(features: (liga: 0))[No fi]
+#set text("Roboto")
+#text(features: ("smcp",))[Smcp] \
+fi vs. #text(features: (liga: 0))[No fi]
---
-// Error: 22-27 expected integer or none, found boolean
-#font(stylistic-set: false)
+// Error: 26-31 expected integer or none, found boolean
+#set text(stylistic-set: false)
---
-// Error: 22-24 must be between 1 and 20
-#font(stylistic-set: 25)
+// Error: 26-28 must be between 1 and 20
+#set text(stylistic-set: 25)
---
-// Error: 20-21 expected string or auto, found integer
-#font(number-type: 2)
+// Error: 24-25 expected string or auto, found integer
+#set text(number-type: 2)
---
-// Error: 20-31 expected "lining" or "old-style"
-#font(number-type: "different")
+// Error: 24-35 expected "lining" or "old-style"
+#set text(number-type: "different")
---
-// Error: 17-22 expected array of strings or dictionary mapping tags to integers, found boolean
-#font(features: false)
+// Error: 21-26 expected array of strings or dictionary mapping tags to integers, found boolean
+#set text(features: false)
diff --git a/tests/typ/text/font.typ b/tests/typ/text/font.typ
index 5c97d367..71ac8d3d 100644
--- a/tests/typ/text/font.typ
+++ b/tests/typ/text/font.typ
@@ -2,57 +2,57 @@
---
// Set same font size in three different ways.
-#font(20pt)[A]
-#font(200%)[A]
-#font(size: 15pt + 50%)[A]
+#text(20pt)[A]
+#text(200%)[A]
+#text(size: 15pt + 50%)[A]
// Do nothing.
-#font()[Normal]
+#text()[Normal]
// Set style (is available).
-#font(style: "italic")[Italic]
+#text(style: "italic")[Italic]
// Set weight (is available).
-#font(weight: "bold")[Bold]
+#text(weight: "bold")[Bold]
// Set stretch (not available, matching closest).
-#font(stretch: 50%)[Condensed]
+#text(stretch: 50%)[Condensed]
// Set family.
-#font(family: serif)[Serif]
+#text(family: serif)[Serif]
// Emoji.
Emoji: 🐪, 🌋, 🏞
// Math.
-#font("Latin Modern Math")[∫ 𝛼 + 3𝛽 d𝑡]
+#text("Latin Modern Math")[∫ 𝛼 + 3𝛽 d𝑡]
// Colors.
[
- #font(fill: eastern)
- This is #font(rgb("FA644B"))[way more] colorful.
+ #set text(fill: eastern)
+ This is #text(rgb("FA644B"))[way more] colorful.
]
// Disable font fallback beyond the user-specified list.
// Without disabling, Latin Modern Math would come to the rescue.
-#font("PT Sans", "Twitter Color Emoji", fallback: false)
+#set text("PT Sans", "Twitter Color Emoji", fallback: false)
2π = 𝛼 + 𝛽. ✅
---
// Test class definitions.
-#font(sans-serif: "PT Sans")
-#font(family: sans-serif)[Sans-serif.] \
-#font(monospace)[Monospace.] \
-#font(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.]
+#set text(sans-serif: "PT Sans")
+#text(family: sans-serif)[Sans-serif.] \
+#text(monospace)[Monospace.] \
+#text(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.]
---
// Test top and bottom edge.
-#page(width: 150pt)
-#font(size: 8pt)
+#set page(width: 150pt)
+#set text(size: 8pt)
#let try(top, bottom) = rect(fill: conifer)[
- #font(monospace, top-edge: top, bottom-edge: bottom)
+ #set text(monospace, top-edge: top, bottom-edge: bottom)
From #top to #bottom
]
@@ -64,33 +64,33 @@ Emoji: 🐪, 🌋, 🏞
#try(1pt + 27%, -18%)
---
-// Error: 7-12 unexpected argument
-#font(false)
+// Error: 11-16 unexpected argument
+#set text(false)
---
-// Error: 14-20 expected "normal", "italic" or "oblique"
-#font(style: "bold", weight: "thin")
+// Error: 18-24 expected "normal", "italic" or "oblique"
+#set text(style: "bold", weight: "thin")
---
-// Error: 17-19 expected linear or string, found array
-#font(top-edge: ())
+// Error: 21-23 expected linear or string, found array
+#set text(top-edge: ())
---
-// Error: 17-19 unknown font metric
-#font(top-edge: "")
+// Error: 21-23 unknown font metric
+#set text(top-edge: "")
---
-// Error: 14-15 expected string or array of strings, found integer
-#font(serif: 0)
+// Error: 18-19 expected string or array of strings, found integer
+#set text(serif: 0)
---
-// Error: 19-23 unexpected argument
-#font(size: 10pt, 12pt)
+// Error: 23-27 unexpected argument
+#set text(size: 10pt, 12pt)
---
-// Error: 28-35 unexpected argument
-#font(family: "Helvetica", "Arial")
+// Error: 32-39 unexpected argument
+#set text(family: "Helvetica", "Arial")
---
-// Error: 7-27 unexpected argument
-#font(something: "invalid")
+// Error: 11-31 unexpected argument
+#set text(something: "invalid")
diff --git a/tests/typ/text/links.typ b/tests/typ/text/links.typ
index 1a77e34b..f2da8b9c 100644
--- a/tests/typ/text/links.typ
+++ b/tests/typ/text/links.typ
@@ -12,12 +12,12 @@ Contact #link("mailto:hi@typst.app") or call #link("tel:123") for more informati
---
// Styled with underline and color.
-#let link(url, body) = link(url, font(fill: rgb("283663"), underline(body)))
+#let link(url, body) = link(url, text(fill: rgb("283663"), underline(body)))
You could also make the #link("https://html5zombo.com/")[link look way more typical.]
---
// Transformed link.
-#page(height: 60pt)
+#set page(height: 60pt)
#let link = link("https://typst.app/")[LINK]
My cool #move(x: 0.7cm, y: 0.7cm, rotate(10deg, scale(200%, link)))
diff --git a/tests/typ/text/par.typ b/tests/typ/text/par.typ
index 9c14279c..771a6218 100644
--- a/tests/typ/text/par.typ
+++ b/tests/typ/text/par.typ
@@ -2,37 +2,37 @@
---
// Test ragged-left.
-#par(align: right)
+#set par(align: right)
To the right! Where the sunlight peeks behind the mountain.
---
// Test that explicit paragraph break respects active styles.
-#par(spacing: 7pt)
-[#par(spacing: 100pt) First]
+#set par(spacing: 7pt)
+[#set par(spacing: 100pt);First]
-[#par(spacing: 100pt) Second]
-#par(spacing: 20pt)
+[#set par(spacing: 100pt);Second]
+#set par(spacing: 20pt)
Third
---
// Test that paragraph break due to incompatibility respects
// spacing defined by the two adjacent paragraphs.
-#let a = [#par(spacing: 40pt) Hello]
-#let b = [#par(spacing: 60pt) World]
+#let a = [#set par(spacing: 40pt);Hello]
+#let b = [#set par(spacing: 60pt);World]
{a}{b}
---
// Test weird metrics.
-#par(spacing: 100%, leading: 0pt)
+#set par(spacing: 100%, leading: 0pt)
But, soft! what light through yonder window breaks?
It is the east, and Juliet is the sun.
---
-// Error: 13-16 must be horizontal
-#par(align: top)
+// Error: 17-20 must be horizontal
+#set par(align: top)
---
-// Error: 13-29 expected alignment, found 2d alignment
-#par(align: horizon + center)
+// Error: 17-33 expected alignment, found 2d alignment
+#set par(align: horizon + center)
diff --git a/tests/typ/text/shaping.typ b/tests/typ/text/shaping.typ
index 1a8a7933..bb8f4ce9 100644
--- a/tests/typ/text/shaping.typ
+++ b/tests/typ/text/shaping.typ
@@ -7,11 +7,11 @@
Le fira
// This should just shape nicely.
-#font("Noto Sans Arabic")
+#set text("Noto Sans Arabic")
دع النص يمطر عليك
// This should form a three-member family.
-#font("Twitter Color Emoji")
+#set text("Twitter Color Emoji")
👩‍👩‍👦 🤚🏿
// These two shouldn't be affected by a zero-width joiner.
@@ -20,7 +20,7 @@ Le fira
---
// Test font fallback.
-#font(sans-serif, "Noto Sans Arabic", "Twitter Color Emoji")
+#set text(sans-serif, "Noto Sans Arabic", "Twitter Color Emoji")
// Font fallback for emoji.
A😀B
@@ -40,6 +40,6 @@ A🐈中文B
---
// Test reshaping.
-#font("Noto Serif Hebrew")
-#par(lang: "he")
+#set text("Noto Serif Hebrew")
+#set par(lang: "he")
ס \ טֶ
diff --git a/tests/typ/text/tracking.typ b/tests/typ/text/tracking.typ
index 695e6734..e3ff70ff 100644
--- a/tests/typ/text/tracking.typ
+++ b/tests/typ/text/tracking.typ
@@ -1,12 +1,12 @@
// Test tracking characters apart or together.
---
-#font(tracking: -0.01)
+#set text(tracking: -0.01)
I saw Zoe yӛsterday, on the tram.
---
-I'm in#font(tracking: 0.3)[ spaace]!
+I'm in#text(tracking: 0.3)[ spaace]!
---
-#font("Noto Serif Hebrew", tracking: 0.3)
+#set text("Noto Serif Hebrew", tracking: 0.3)
טֶקסט
diff --git a/tests/typ/text/whitespace.typ b/tests/typ/text/whitespace.typ
index e01b047c..4c3d4db5 100644
--- a/tests/typ/text/whitespace.typ
+++ b/tests/typ/text/whitespace.typ
@@ -30,11 +30,11 @@ A #for _ in (none,) {"B"}C
---
// Test that a run consisting only of whitespace isn't trimmed.
-A[#font(serif) ]B
+A[#set text(serif); ]B
---
// Test font change after space.
-Left [#font(serif)Right].
+Left [#set text(serif);Right].
---
// Test that space at start of line is not trimmed.