- New show rule syntax
- Set if syntax
- Removed wrap syntax
This commit is contained in:
Laurenz 2022-11-07 12:21:12 +01:00
parent eb951c008b
commit efd1853d06
46 changed files with 537 additions and 584 deletions

View File

@ -200,7 +200,7 @@ impl Cast<Spanned<Value>> for Marginal {
fn cast(value: Spanned<Value>) -> StrResult<Self> { fn cast(value: Spanned<Value>) -> StrResult<Self> {
match value.v { match value.v {
Value::None => Ok(Self::None), Value::None => Ok(Self::None),
Value::Str(v) => Ok(Self::Content(TextNode(v.into()).pack())), Value::Str(v) => Ok(Self::Content(TextNode::packed(v))),
Value::Content(v) => Ok(Self::Content(v)), Value::Content(v) => Ok(Self::Content(v)),
Value::Func(v) => Ok(Self::Func(v, value.span)), Value::Func(v) => Ok(Self::Func(v, value.span)),
v => Err(format!( v => Err(format!(

View File

@ -18,7 +18,7 @@ pub struct MathNode {
pub display: bool, pub display: bool,
} }
#[node(Show, LayoutInline, Texify)] #[node(Show, Finalize, LayoutInline, Texify)]
impl MathNode { impl MathNode {
/// The math font family. /// The math font family.
#[property(referenced)] #[property(referenced)]
@ -29,6 +29,13 @@ impl MathNode {
/// The spacing below display math. /// The spacing below display math.
#[property(resolve, shorthand(around))] #[property(resolve, shorthand(around))]
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into()); pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
fn field(&self, name: &str) -> Option<Value> {
match name {
"display" => Some(Value::Bool(self.display)),
_ => None,
}
}
} }
impl Show for MathNode { impl Show for MathNode {
@ -36,18 +43,16 @@ impl Show for MathNode {
self.clone().pack() self.clone().pack()
} }
fn field(&self, _: &str) -> Option<Value> { fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
None
}
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
Ok(if self.display { Ok(if self.display {
self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into()))) self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into())))
} else { } else {
self.clone().pack() self.clone().pack()
}) })
} }
}
impl Finalize for MathNode {
fn finalize( fn finalize(
&self, &self,
_: Tracked<dyn World>, _: Tracked<dyn World>,

View File

@ -9,8 +9,8 @@ pub use typst::frame::*;
pub use typst::geom::*; pub use typst::geom::*;
pub use typst::model::{ pub use typst::model::{
array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast,
Content, Dict, Fold, Func, Key, Node, Resolve, Scope, Selector, Show, Smart, Str, Content, Dict, Finalize, Fold, Func, Key, Node, Resolve, Scope, Selector, Show,
StyleChain, StyleMap, StyleVec, Value, Vm, Smart, Str, StyleChain, StyleMap, StyleVec, Value, Vm,
}; };
pub use typst::syntax::{Span, Spanned}; pub use typst::syntax::{Span, Spanned};
pub use typst::util::{format_eco, EcoString}; pub use typst::util::{format_eco, EcoString};

View File

@ -12,7 +12,7 @@ pub struct HeadingNode {
pub body: Content, pub body: Content,
} }
#[node(Show)] #[node(Show, Finalize)]
impl HeadingNode { impl HeadingNode {
/// The heading's font family. Just the normal text family if `auto`. /// The heading's font family. Just the normal text family if `auto`.
#[property(referenced)] #[property(referenced)]
@ -67,12 +67,6 @@ impl HeadingNode {
} }
.pack()) .pack())
} }
}
impl Show for HeadingNode {
fn unguard_parts(&self, sel: Selector) -> Content {
Self { body: self.body.unguard(sel), ..*self }.pack()
}
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
@ -81,11 +75,19 @@ impl Show for HeadingNode {
_ => None, _ => None,
} }
} }
}
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { impl Show for HeadingNode {
Ok(BlockNode(self.body.clone()).pack()) fn unguard_parts(&self, sel: Selector) -> Content {
Self { body: self.body.unguard(sel), ..*self }.pack()
} }
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
Ok(BlockNode(self.body.clone()).pack())
}
}
impl Finalize for HeadingNode {
fn finalize( fn finalize(
&self, &self,
world: Tracked<dyn World>, world: Tracked<dyn World>,

View File

@ -22,7 +22,7 @@ pub type EnumNode = ListNode<ENUM>;
/// A description list. /// A description list.
pub type DescNode = ListNode<DESC>; pub type DescNode = ListNode<DESC>;
#[node(Show)] #[node(Show, Finalize)]
impl<const L: ListKind> ListNode<L> { impl<const L: ListKind> ListNode<L> {
/// How the list is labelled. /// How the list is labelled.
#[property(referenced)] #[property(referenced)]
@ -80,16 +80,6 @@ impl<const L: ListKind> ListNode<L> {
} }
.pack()) .pack())
} }
}
impl<const L: ListKind> Show for ListNode<L> {
fn unguard_parts(&self, sel: Selector) -> Content {
Self {
items: self.items.map(|item| item.unguard(sel)),
..*self
}
.pack()
}
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
@ -101,8 +91,18 @@ impl<const L: ListKind> Show for ListNode<L> {
_ => None, _ => None,
} }
} }
}
fn realize( impl<const L: ListKind> Show for ListNode<L> {
fn unguard_parts(&self, sel: Selector) -> Content {
Self {
items: self.items.map(|item| item.unguard(sel)),
..*self
}
.pack()
}
fn show(
&self, &self,
world: Tracked<dyn World>, world: Tracked<dyn World>,
styles: StyleChain, styles: StyleChain,
@ -140,7 +140,7 @@ impl<const L: ListKind> Show for ListNode<L> {
ListItem::Enum(_, body) => body.as_ref().clone(), ListItem::Enum(_, body) => body.as_ref().clone(),
ListItem::Desc(item) => Content::sequence(vec![ ListItem::Desc(item) => Content::sequence(vec![
HNode { amount: (-body_indent).into(), weak: false }.pack(), HNode { amount: (-body_indent).into(), weak: false }.pack(),
(item.term.clone() + TextNode(':'.into()).pack()).strong(), (item.term.clone() + TextNode::packed(':')).strong(),
SpaceNode.pack(), SpaceNode.pack(),
item.body.clone(), item.body.clone(),
]), ]),
@ -162,7 +162,9 @@ impl<const L: ListKind> Show for ListNode<L> {
} }
.pack()) .pack())
} }
}
impl<const L: ListKind> Finalize for ListNode<L> {
fn finalize( fn finalize(
&self, &self,
_: Tracked<dyn World>, _: Tracked<dyn World>,
@ -312,14 +314,14 @@ impl Label {
) -> SourceResult<Content> { ) -> SourceResult<Content> {
Ok(match self { Ok(match self {
Self::Default => match kind { Self::Default => match kind {
LIST => TextNode('•'.into()).pack(), LIST => TextNode::packed('•'),
ENUM => TextNode(format_eco!("{}.", number)).pack(), ENUM => TextNode::packed(format_eco!("{}.", number)),
DESC | _ => panic!("description lists don't have a label"), DESC | _ => panic!("description lists don't have a label"),
}, },
Self::Pattern(prefix, numbering, upper, suffix) => { Self::Pattern(prefix, numbering, upper, suffix) => {
let fmt = numbering.apply(number); let fmt = numbering.apply(number);
let mid = if *upper { fmt.to_uppercase() } else { fmt.to_lowercase() }; let mid = if *upper { fmt.to_uppercase() } else { fmt.to_lowercase() };
TextNode(format_eco!("{}{}{}", prefix, mid, suffix)).pack() TextNode::packed(format_eco!("{}{}{}", prefix, mid, suffix))
} }
Self::Content(content) => content.clone(), Self::Content(content) => content.clone(),
Self::Func(func, span) => { Self::Func(func, span) => {

View File

@ -8,7 +8,14 @@ pub struct RefNode(pub EcoString);
#[node(Show)] #[node(Show)]
impl RefNode { impl RefNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("label")?).pack()) Ok(Self(args.expect("target")?).pack())
}
fn field(&self, name: &str) -> Option<Value> {
match name {
"target" => Some(Value::Str(self.0.clone().into())),
_ => None,
}
} }
} }
@ -17,14 +24,7 @@ impl Show for RefNode {
Self(self.0.clone()).pack() Self(self.0.clone()).pack()
} }
fn field(&self, name: &str) -> Option<Value> { fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
match name { Ok(TextNode::packed(format_eco!("@{}", self.0)))
"label" => Some(Value::Str(self.0.clone().into())),
_ => None,
}
}
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
Ok(TextNode(format_eco!("@{}", self.0)).pack())
} }
} }

View File

@ -12,7 +12,7 @@ pub struct TableNode {
pub cells: Vec<Content>, pub cells: Vec<Content>,
} }
#[node(Show)] #[node(Show, Finalize)]
impl TableNode { impl TableNode {
/// How to fill the cells. /// How to fill the cells.
#[property(referenced)] #[property(referenced)]
@ -46,6 +46,15 @@ impl TableNode {
} }
.pack()) .pack())
} }
fn field(&self, name: &str) -> Option<Value> {
match name {
"cells" => Some(Value::Array(
self.cells.iter().cloned().map(Value::Content).collect(),
)),
_ => None,
}
}
} }
impl Show for TableNode { impl Show for TableNode {
@ -58,16 +67,7 @@ impl Show for TableNode {
.pack() .pack()
} }
fn field(&self, name: &str) -> Option<Value> { fn show(
match name {
"cells" => Some(Value::Array(
self.cells.iter().cloned().map(Value::Content).collect(),
)),
_ => None,
}
}
fn realize(
&self, &self,
world: Tracked<dyn World>, world: Tracked<dyn World>,
styles: StyleChain, styles: StyleChain,
@ -106,7 +106,9 @@ impl Show for TableNode {
} }
.pack()) .pack())
} }
}
impl Finalize for TableNode {
fn finalize( fn finalize(
&self, &self,
_: Tracked<dyn World>, _: Tracked<dyn World>,

View File

@ -37,12 +37,6 @@ impl<const L: DecoLine> DecoNode<L> {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack()) Ok(Self(args.expect("body")?).pack())
} }
}
impl<const L: DecoLine> Show for DecoNode<L> {
fn unguard_parts(&self, sel: Selector) -> Content {
Self(self.0.unguard(sel)).pack()
}
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
@ -50,12 +44,14 @@ impl<const L: DecoLine> Show for DecoNode<L> {
_ => None, _ => None,
} }
} }
}
fn realize( impl<const L: DecoLine> Show for DecoNode<L> {
&self, fn unguard_parts(&self, sel: Selector) -> Content {
_: Tracked<dyn World>, Self(self.0.unguard(sel)).pack()
styles: StyleChain, }
) -> SourceResult<Content> {
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> SourceResult<Content> {
Ok(self.0.clone().styled( Ok(self.0.clone().styled(
TextNode::DECO, TextNode::DECO,
Decoration { Decoration {
@ -81,6 +77,15 @@ pub(super) struct Decoration {
pub evade: bool, pub evade: bool,
} }
impl Fold for Decoration {
type Output = Vec<Self>;
fn fold(self, mut outer: Self::Output) -> Self::Output {
outer.insert(0, self);
outer
}
}
/// A kind of decorative line. /// A kind of decorative line.
pub type DecoLine = usize; pub type DecoLine = usize;

View File

@ -17,7 +17,7 @@ impl LinkNode {
} }
} }
#[node(Show)] #[node(Show, Finalize)]
impl LinkNode { impl LinkNode {
/// The fill color of text in the link. Just the surrounding text color /// The fill color of text in the link. Just the surrounding text color
/// if `auto`. /// if `auto`.
@ -33,16 +33,6 @@ impl LinkNode {
}; };
Ok(Self { dest, body }.pack()) Ok(Self { dest, body }.pack())
} }
}
impl Show for LinkNode {
fn unguard_parts(&self, sel: Selector) -> Content {
Self {
dest: self.dest.clone(),
body: self.body.as_ref().map(|body| body.unguard(sel)),
}
.pack()
}
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
@ -57,25 +47,33 @@ impl Show for LinkNode {
_ => None, _ => None,
} }
} }
}
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { impl Show for LinkNode {
Ok(self fn unguard_parts(&self, sel: Selector) -> Content {
.body Self {
.clone() dest: self.dest.clone(),
.unwrap_or_else(|| match &self.dest { body: self.body.as_ref().map(|body| body.unguard(sel)),
Destination::Url(url) => { }
let mut text = url.as_str(); .pack()
for prefix in ["mailto:", "tel:"] {
text = text.trim_start_matches(prefix);
}
let shorter = text.len() < url.len();
TextNode(if shorter { text.into() } else { url.clone() }).pack()
}
Destination::Internal(_) => Content::empty(),
})
.styled(TextNode::LINK, Some(self.dest.clone())))
} }
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
Ok(self.body.clone().unwrap_or_else(|| match &self.dest {
Destination::Url(url) => {
let mut text = url.as_str();
for prefix in ["mailto:", "tel:"] {
text = text.trim_start_matches(prefix);
}
let shorter = text.len() < url.len();
TextNode::packed(if shorter { text.into() } else { url.clone() })
}
Destination::Internal(_) => Content::empty(),
}))
}
}
impl Finalize for LinkNode {
fn finalize( fn finalize(
&self, &self,
_: Tracked<dyn World>, _: Tracked<dyn World>,
@ -83,6 +81,8 @@ impl Show for LinkNode {
mut realized: Content, mut realized: Content,
) -> SourceResult<Content> { ) -> SourceResult<Content> {
let mut map = StyleMap::new(); let mut map = StyleMap::new();
map.set(TextNode::LINK, Some(self.dest.clone()));
if let Smart::Custom(fill) = styles.get(Self::FILL) { if let Smart::Custom(fill) = styles.get(Self::FILL) {
map.set(TextNode::FILL, fill); map.set(TextNode::FILL, fill);
} }

View File

@ -28,6 +28,13 @@ use crate::prelude::*;
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone, Hash)]
pub struct TextNode(pub EcoString); pub struct TextNode(pub EcoString);
impl TextNode {
/// Create a new packed text node.
pub fn packed(text: impl Into<EcoString>) -> Content {
Self(text.into()).pack()
}
}
#[node] #[node]
impl TextNode { impl TextNode {
/// A prioritized sequence of font families. /// A prioritized sequence of font families.
@ -486,12 +493,6 @@ impl StrongNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack()) Ok(Self(args.expect("body")?).pack())
} }
}
impl Show for StrongNode {
fn unguard_parts(&self, sel: Selector) -> Content {
Self(self.0.unguard(sel)).pack()
}
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
@ -499,8 +500,14 @@ impl Show for StrongNode {
_ => None, _ => None,
} }
} }
}
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { impl Show for StrongNode {
fn unguard_parts(&self, sel: Selector) -> Content {
Self(self.0.unguard(sel)).pack()
}
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
Ok(self.0.clone().styled(TextNode::BOLD, Toggle)) Ok(self.0.clone().styled(TextNode::BOLD, Toggle))
} }
} }
@ -514,12 +521,6 @@ impl EmphNode {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack()) Ok(Self(args.expect("body")?).pack())
} }
}
impl Show for EmphNode {
fn unguard_parts(&self, sel: Selector) -> Content {
Self(self.0.unguard(sel)).pack()
}
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
@ -527,8 +528,14 @@ impl Show for EmphNode {
_ => None, _ => None,
} }
} }
}
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> { impl Show for EmphNode {
fn unguard_parts(&self, sel: Selector) -> Content {
Self(self.0.unguard(sel)).pack()
}
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
Ok(self.0.clone().styled(TextNode::ITALIC, Toggle)) Ok(self.0.clone().styled(TextNode::ITALIC, Toggle))
} }
} }
@ -544,12 +551,3 @@ impl Fold for Toggle {
!outer !outer
} }
} }
impl Fold for Decoration {
type Output = Vec<Self>;
fn fold(self, mut outer: Self::Output) -> Self::Output {
outer.insert(0, self);
outer
}
}

View File

@ -19,7 +19,7 @@ pub struct RawNode {
pub block: bool, pub block: bool,
} }
#[node(Show)] #[node(Show, Finalize)]
impl RawNode { impl RawNode {
/// The language to syntax-highlight in. /// The language to syntax-highlight in.
#[property(referenced)] #[property(referenced)]
@ -41,12 +41,6 @@ impl RawNode {
} }
.pack()) .pack())
} }
}
impl Show for RawNode {
fn unguard_parts(&self, _: Selector) -> Content {
Self { text: self.text.clone(), ..*self }.pack()
}
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
@ -55,12 +49,14 @@ impl Show for RawNode {
_ => None, _ => None,
} }
} }
}
fn realize( impl Show for RawNode {
&self, fn unguard_parts(&self, _: Selector) -> Content {
_: Tracked<dyn World>, Self { text: self.text.clone(), ..*self }.pack()
styles: StyleChain, }
) -> SourceResult<Content> {
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> SourceResult<Content> {
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
let foreground = THEME let foreground = THEME
.settings .settings
@ -100,7 +96,7 @@ impl Show for RawNode {
Content::sequence(seq) Content::sequence(seq)
} else { } else {
TextNode(self.text.clone()).pack() TextNode::packed(self.text.clone())
}; };
if self.block { if self.block {
@ -114,7 +110,9 @@ impl Show for RawNode {
Ok(realized.styled_with_map(map)) Ok(realized.styled_with_map(map))
} }
}
impl Finalize for RawNode {
fn finalize( fn finalize(
&self, &self,
_: Tracked<dyn World>, _: Tracked<dyn World>,
@ -134,7 +132,7 @@ impl Show for RawNode {
/// Style a piece of text with a syntect style. /// Style a piece of text with a syntect style.
fn styled(piece: &str, foreground: Paint, style: Style) -> Content { fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
let mut body = TextNode(piece.into()).pack(); let mut body = TextNode::packed(piece);
let paint = style.foreground.into(); let paint = style.foreground.into();
if paint != foreground { if paint != foreground {

View File

@ -33,12 +33,6 @@ impl<const S: ShiftKind> ShiftNode<S> {
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
Ok(Self(args.expect("body")?).pack()) Ok(Self(args.expect("body")?).pack())
} }
}
impl<const S: ShiftKind> Show for ShiftNode<S> {
fn unguard_parts(&self, _: Selector) -> Content {
Self(self.0.clone()).pack()
}
fn field(&self, name: &str) -> Option<Value> { fn field(&self, name: &str) -> Option<Value> {
match name { match name {
@ -46,8 +40,14 @@ impl<const S: ShiftKind> Show for ShiftNode<S> {
_ => None, _ => None,
} }
} }
}
fn realize( impl<const S: ShiftKind> Show for ShiftNode<S> {
fn unguard_parts(&self, _: Selector) -> Content {
Self(self.0.clone()).pack()
}
fn show(
&self, &self,
world: Tracked<dyn World>, world: Tracked<dyn World>,
styles: StyleChain, styles: StyleChain,
@ -56,7 +56,7 @@ impl<const S: ShiftKind> Show for ShiftNode<S> {
if styles.get(Self::TYPOGRAPHIC) { if styles.get(Self::TYPOGRAPHIC) {
if let Some(text) = search_text(&self.0, S) { if let Some(text) = search_text(&self.0, S) {
if is_shapable(world, &text, styles) { if is_shapable(world, &text, styles) {
transformed = Some(TextNode(text).pack()); transformed = Some(TextNode::packed(text));
} }
} }
}; };

View File

@ -47,6 +47,7 @@ fn expand_node(
let mut properties = vec![]; let mut properties = vec![];
let mut construct = None; let mut construct = None;
let mut set = None; let mut set = None;
let mut field = None;
for item in std::mem::take(&mut impl_block.items) { for item in std::mem::take(&mut impl_block.items) {
match item { match item {
@ -61,6 +62,7 @@ fn expand_node(
match method.sig.ident.to_string().as_str() { match method.sig.ident.to_string().as_str() {
"construct" => construct = Some(method), "construct" => construct = Some(method),
"set" => set = Some(method), "set" => set = Some(method),
"field" => field = Some(method),
_ => return Err(Error::new(method.span(), "unexpected method")), _ => return Err(Error::new(method.span(), "unexpected method")),
} }
} }
@ -81,6 +83,14 @@ fn expand_node(
let set = generate_set(&properties, set); let set = generate_set(&properties, set);
let field = field.unwrap_or_else(|| {
parse_quote! {
fn field(&self, name: &str) -> Option<Value> {
None
}
}
});
let items: syn::punctuated::Punctuated<Ident, syn::Token![,]> = let items: syn::punctuated::Punctuated<Ident, syn::Token![,]> =
parse_quote! { #stream }; parse_quote! { #stream };
@ -115,11 +125,13 @@ fn expand_node(
impl<#params> model::Node for #self_ty { impl<#params> model::Node for #self_ty {
#construct #construct
#set #set
#vtable #field
fn id(&self) -> model::NodeId { fn id(&self) -> model::NodeId {
model::NodeId::of::<Self>() model::NodeId::of::<Self>()
} }
#vtable
} }
#(#key_modules)* #(#key_modules)*

View File

@ -1,7 +1,7 @@
use std::num::NonZeroUsize; use std::num::NonZeroUsize;
use std::str::FromStr; use std::str::FromStr;
use super::{Pattern, Regex, Value}; use super::{Content, Pattern, Regex, Transform, Value};
use crate::diag::{with_alternative, StrResult}; use crate::diag::{with_alternative, StrResult};
use crate::font::{FontStretch, FontStyle, FontWeight}; use crate::font::{FontStretch, FontStyle, FontWeight};
use crate::frame::{Destination, Lang, Location, Region}; use crate::frame::{Destination, Lang, Location, Region};
@ -189,6 +189,15 @@ castable! {
@regex: Regex => Self::Regex(regex.clone()), @regex: Regex => Self::Regex(regex.clone()),
} }
castable! {
Transform,
Expected: "content or function",
Value::None => Self::Content(Content::empty()),
Value::Str(text) => Self::Content(item!(text)(text.into())),
Value::Content(content) => Self::Content(content),
Value::Func(func) => Self::Func(func),
}
dynamic! { dynamic! {
Dir: "direction", Dir: "direction",
} }

View File

@ -5,13 +5,14 @@ use std::iter::{self, Sum};
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
use std::sync::Arc; use std::sync::Arc;
use comemo::Tracked;
use siphasher::sip128::{Hasher128, SipHasher}; use siphasher::sip128::{Hasher128, SipHasher};
use typst_macros::node; use typst_macros::node;
use super::{Args, Key, Property, Selector, StyleEntry, StyleMap, Vm}; use super::{Args, Key, Property, Recipe, Selector, StyleEntry, StyleMap, Value, Vm};
use crate as typst;
use crate::diag::{SourceResult, StrResult}; use crate::diag::{SourceResult, StrResult};
use crate::util::{EcoString, ReadableTypeId}; use crate::util::ReadableTypeId;
use crate::World;
/// Composable representation of styled content. /// Composable representation of styled content.
/// ///
@ -27,11 +28,6 @@ impl Content {
SequenceNode(vec![]).pack() SequenceNode(vec![]).pack()
} }
/// Create content from a string of text.
pub fn text(text: impl Into<EcoString>) -> Self {
item!(text)(text.into())
}
/// Create a new sequence node from multiples nodes. /// Create a new sequence node from multiples nodes.
pub fn sequence(seq: Vec<Self>) -> Self { pub fn sequence(seq: Vec<Self>) -> Self {
match seq.as_slice() { match seq.as_slice() {
@ -65,6 +61,11 @@ impl Content {
Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::<T>() Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::<T>()
} }
/// Access a field on this content.
pub fn field(&self, name: &str) -> Option<Value> {
self.0.field(name)
}
/// Whether this content has the given capability. /// Whether this content has the given capability.
pub fn has<C>(&self) -> bool pub fn has<C>(&self) -> bool
where where
@ -97,6 +98,19 @@ impl Content {
self.styled_with_entry(StyleEntry::Property(Property::new(key, value))) self.styled_with_entry(StyleEntry::Property(Property::new(key, value)))
} }
/// Style this content with a recipe, eagerly applying it if possible.
pub fn styled_with_recipe(
self,
world: Tracked<dyn World>,
recipe: Recipe,
) -> SourceResult<Self> {
if recipe.pattern.is_none() {
recipe.transform.apply(world, recipe.span, || Value::Content(self))
} else {
Ok(self.styled_with_entry(StyleEntry::Recipe(recipe)))
}
}
/// Style this content with a style entry. /// Style this content with a style entry.
pub fn styled_with_entry(mut self, entry: StyleEntry) -> Self { pub fn styled_with_entry(mut self, entry: StyleEntry) -> Self {
if let Some(styled) = self.try_downcast_mut::<StyledNode>() { if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
@ -242,6 +256,9 @@ pub trait Node: 'static {
where where
Self: Sized; Self: Sized;
/// Access a field on this node.
fn field(&self, name: &str) -> Option<Value>;
/// A unique identifier of the node type. /// A unique identifier of the node type.
fn id(&self) -> NodeId; fn id(&self) -> NodeId;

View File

@ -7,7 +7,7 @@ use unicode_segmentation::UnicodeSegmentation;
use super::{ use super::{
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func, methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func,
Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm, Pattern, Recipe, Scope, Scopes, StyleMap, Transform, Value, Vm,
}; };
use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint}; use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint};
use crate::geom::{Abs, Angle, Em, Fr, Ratio}; use crate::geom::{Abs, Angle, Em, Fr, Ratio};
@ -133,12 +133,8 @@ fn eval_markup(
break; break;
} }
eval_markup(vm, nodes)?.styled_with_entry(StyleEntry::Recipe(recipe))
}
ast::MarkupNode::Expr(ast::Expr::Wrap(wrap)) => {
let tail = eval_markup(vm, nodes)?; let tail = eval_markup(vm, nodes)?;
vm.scopes.top.define(wrap.binding().take(), tail); tail.styled_with_recipe(vm.world, recipe)?
wrap.body().eval(vm)?.display(vm.world)
} }
_ => node.eval(vm)?, _ => node.eval(vm)?,
}); });
@ -408,7 +404,6 @@ impl Eval for ast::Expr {
Self::Let(v) => v.eval(vm), Self::Let(v) => v.eval(vm),
Self::Set(_) => bail!(forbidden("set")), Self::Set(_) => bail!(forbidden("set")),
Self::Show(_) => bail!(forbidden("show")), Self::Show(_) => bail!(forbidden("show")),
Self::Wrap(_) => bail!(forbidden("wrap")),
Self::Conditional(v) => v.eval(vm), Self::Conditional(v) => v.eval(vm),
Self::While(v) => v.eval(vm), Self::While(v) => v.eval(vm),
Self::For(v) => v.eval(vm), Self::For(v) => v.eval(vm),
@ -484,18 +479,12 @@ fn eval_code(
} }
ast::Expr::Show(show) => { ast::Expr::Show(show) => {
let recipe = show.eval(vm)?; let recipe = show.eval(vm)?;
let entry = StyleEntry::Recipe(recipe);
if vm.flow.is_some() { if vm.flow.is_some() {
break; break;
} }
let tail = eval_code(vm, exprs)?.display(vm.world); let tail = eval_code(vm, exprs)?.display(vm.world);
Value::Content(tail.styled_with_entry(entry)) Value::Content(tail.styled_with_recipe(vm.world, recipe)?)
}
ast::Expr::Wrap(wrap) => {
let tail = eval_code(vm, exprs)?;
vm.scopes.top.define(wrap.binding().take(), tail);
wrap.body().eval(vm)?
} }
_ => expr.eval(vm)?, _ => expr.eval(vm)?,
}; };
@ -671,9 +660,8 @@ impl Eval for ast::FieldAccess {
Ok(match object { Ok(match object {
Value::Dict(dict) => dict.get(&field).at(span)?.clone(), Value::Dict(dict) => dict.get(&field).at(span)?.clone(),
Value::Content(node) => node Value::Content(content) => content
.to::<dyn Show>() .field(&field)
.and_then(|node| node.field(&field))
.ok_or_else(|| format!("unknown field {field:?}")) .ok_or_else(|| format!("unknown field {field:?}"))
.at(span)?, .at(span)?,
v => bail!(self.target().span(), "cannot access field on {}", v.type_name()), v => bail!(self.target().span(), "cannot access field on {}", v.type_name()),
@ -838,6 +826,11 @@ impl Eval for ast::SetRule {
type Output = StyleMap; type Output = StyleMap;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
if let Some(condition) = self.condition() {
if !condition.eval(vm)?.cast::<bool>().at(condition.span())? {
return Ok(StyleMap::new());
}
}
let target = self.target(); let target = self.target();
let target = target.eval(vm)?.cast::<Func>().at(target.span())?; let target = target.eval(vm)?.cast::<Func>().at(target.span())?;
let args = self.args().eval(vm)?; let args = self.args().eval(vm)?;
@ -849,36 +842,16 @@ impl Eval for ast::ShowRule {
type Output = Recipe; type Output = Recipe;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
// Evaluate the target function. let pattern = self
let pattern = self.pattern(); .pattern()
let pattern = pattern.eval(vm)?.cast::<Pattern>().at(pattern.span())?; .map(|pattern| pattern.eval(vm)?.cast::<Pattern>().at(pattern.span()))
.transpose()?;
// Collect captured variables. let transform = self.transform();
let captured = { let span = transform.span();
let mut visitor = CapturesVisitor::new(&vm.scopes); let transform = transform.eval(vm)?.cast::<Transform>().at(span)?;
visitor.visit(self.as_untyped());
visitor.finish()
};
// Define parameters. Ok(Recipe { span, pattern, transform })
let mut params = vec![];
if let Some(binding) = self.binding() {
params.push((binding.take(), None));
}
// Define the recipe function.
let body = self.body();
let span = body.span();
let func = Func::from_closure(Closure {
location: vm.location,
name: None,
captured,
params,
sink: None,
body,
});
Ok(Recipe { pattern, func: Spanned::new(func, span) })
} }
} }
@ -1066,7 +1039,7 @@ fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult<Module> {
Ok(module) Ok(module)
} }
impl Eval for ast::BreakStmt { impl Eval for ast::LoopBreak {
type Output = Value; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
@ -1077,7 +1050,7 @@ impl Eval for ast::BreakStmt {
} }
} }
impl Eval for ast::ContinueStmt { impl Eval for ast::LoopContinue {
type Output = Value; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
@ -1088,7 +1061,7 @@ impl Eval for ast::ContinueStmt {
} }
} }
impl Eval for ast::ReturnStmt { impl Eval for ast::FuncReturn {
type Output = Value; type Output = Value;
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {

View File

@ -310,16 +310,6 @@ impl<'a> CapturesVisitor<'a> {
self.bind(expr.binding()); self.bind(expr.binding());
} }
// A show rule contains a binding, but that binding is only active
// after the target has been evaluated.
Some(ast::Expr::Show(show)) => {
self.visit(show.pattern().as_untyped());
if let Some(binding) = show.binding() {
self.bind(binding);
}
self.visit(show.body().as_untyped());
}
// A for loop contains one or two bindings in its pattern. These are // A for loop contains one or two bindings in its pattern. These are
// active after the iterable is evaluated but before the body is // active after the iterable is evaluated but before the body is
// evaluated. // evaluated.
@ -391,9 +381,9 @@ mod tests {
test("{(x, y: x + z) => x + y}", &["x", "z"]); test("{(x, y: x + z) => x + y}", &["x", "z"]);
// Show rule. // Show rule.
test("#show x: y as x", &["y"]); test("#show y: x => x", &["y"]);
test("#show x: y as x + z", &["y", "z"]); test("#show y: x => x + z", &["y", "z"]);
test("#show x: x as x", &["x"]); test("#show x: x => x", &["x"]);
// For loop. // For loop.
test("#for x in y { x + z }", &["y", "z"]); test("#for x in y { x + z }", &["y", "z"]);

View File

@ -48,11 +48,11 @@ pub struct LangItems {
pub em: fn(StyleChain) -> Abs, pub em: fn(StyleChain) -> Abs,
/// Access the text direction. /// Access the text direction.
pub dir: fn(StyleChain) -> Dir, pub dir: fn(StyleChain) -> Dir,
/// A space. /// Whitespace.
pub space: fn() -> Content, pub space: fn() -> Content,
/// A forced line break. /// A forced line break: `\`.
pub linebreak: fn(justify: bool) -> Content, pub linebreak: fn(justify: bool) -> Content,
/// Plain text. /// Plain text without markup.
pub text: fn(text: EcoString) -> Content, pub text: fn(text: EcoString) -> Content,
/// A smart quote: `'` or `"`. /// A smart quote: `'` or `"`.
pub smart_quote: fn(double: bool) -> Content, pub smart_quote: fn(double: bool) -> Content,
@ -72,18 +72,18 @@ pub struct LangItems {
pub heading: fn(level: NonZeroUsize, body: Content) -> Content, pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
/// An item in an unordered list: `- ...`. /// An item in an unordered list: `- ...`.
pub list_item: fn(body: Content) -> Content, pub list_item: fn(body: Content) -> Content,
/// An item in an enumeration (ordered list): `1. ...`. /// An item in an enumeration (ordered list): `+ ...` or `1. ...`.
pub enum_item: fn(number: Option<usize>, body: Content) -> Content, pub enum_item: fn(number: Option<usize>, body: Content) -> Content,
/// An item in a description list: `/ Term: Details`. /// An item in a description list: `/ Term: Details`.
pub desc_item: fn(term: Content, body: Content) -> Content, pub desc_item: fn(term: Content, body: Content) -> Content,
/// A math formula: `$x$`, `$ x^2 $`. /// A mathematical formula: `$x$`, `$ x^2 $`.
pub math: fn(children: Vec<Content>, display: bool) -> Content, pub math: fn(children: Vec<Content>, display: bool) -> Content,
/// A atom in a formula: `x`, `+`, `12`. /// An atom in a formula: `x`, `+`, `12`.
pub math_atom: fn(atom: EcoString) -> Content, pub math_atom: fn(atom: EcoString) -> Content,
/// A base with an optional sub- and superscript in a formula: `a_1^2`. /// A base with optional sub- and superscripts in a formula: `a_1^2`.
pub math_script: pub math_script:
fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content, fn(base: Content, sub: Option<Content>, sup: Option<Content>) -> Content,
/// A fraction in a formula: `x/2` /// A fraction in a formula: `x/2`.
pub math_frac: fn(num: Content, denom: Content) -> Content, pub math_frac: fn(num: Content, denom: Content) -> Content,
/// An alignment indicator in a formula: `&`, `&&`. /// An alignment indicator in a formula: `&`, `&&`.
pub math_align: fn(count: usize) -> Content, pub math_align: fn(count: usize) -> Content,

View File

@ -19,8 +19,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
(a, None) => a, (a, None) => a,
(None, b) => b, (None, b) => b,
(Str(a), Str(b)) => Str(a + b), (Str(a), Str(b)) => Str(a + b),
(Str(a), Content(b)) => Content(super::Content::text(a) + b), (Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
(Content(a), Str(b)) => Content(a + super::Content::text(b)), (Content(a), Str(b)) => Content(a + item!(text)(b.into())),
(Content(a), Content(b)) => Content(a + b), (Content(a), Content(b)) => Content(a + b),
(Array(a), Array(b)) => Array(a + b), (Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b), (Dict(a), Dict(b)) => Dict(a + b),
@ -85,8 +85,8 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
(Str(a), Str(b)) => Str(a + b), (Str(a), Str(b)) => Str(a + b),
(Content(a), Content(b)) => Content(a + b), (Content(a), Content(b)) => Content(a + b),
(Content(a), Str(b)) => Content(a + super::Content::text(b)), (Content(a), Str(b)) => Content(a + item!(text)(b.into())),
(Str(a), Content(b)) => Content(super::Content::text(a) + b), (Str(a), Content(b)) => Content(item!(text)(a.into()) + b),
(Array(a), Array(b)) => Array(a + b), (Array(a), Array(b)) => Array(a + b),
(Dict(a), Dict(b)) => Dict(a + b), (Dict(a), Dict(b)) => Dict(a + b),

View File

@ -12,7 +12,7 @@ use crate::diag::SourceResult;
use crate::geom::{ use crate::geom::{
Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides, Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
}; };
use crate::syntax::Spanned; use crate::syntax::Span;
use crate::util::ReadableTypeId; use crate::util::ReadableTypeId;
use crate::World; use crate::World;
@ -283,17 +283,17 @@ impl<'a> StyleChain<'a> {
.unguard_parts(sel) .unguard_parts(sel)
.to::<dyn Show>() .to::<dyn Show>()
.unwrap() .unwrap()
.realize(world, self)?; .show(world, self)?;
realized = Some(content.styled_with_entry(StyleEntry::Guard(sel))); realized = Some(content.styled_with_entry(StyleEntry::Guard(sel)));
} }
} }
// Finalize only if guarding didn't stop any recipe. // Finalize only if guarding didn't stop any recipe.
if !guarded { if !guarded {
if let Some(content) = realized { if let Some(node) = node.to::<dyn Finalize>() {
realized = Some( if let Some(content) = realized {
node.to::<dyn Show>().unwrap().finalize(world, self, content)?, realized = Some(node.finalize(world, self, content)?);
); }
} }
} }
} }
@ -974,18 +974,20 @@ impl Fold for PartialStroke<Abs> {
/// A show rule recipe. /// A show rule recipe.
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq, Hash)]
pub struct Recipe { pub struct Recipe {
/// The patterns to customize. /// The span errors are reported with.
pub pattern: Pattern, pub span: Span,
/// The function that defines the recipe. /// The pattern that the rule applies to.
pub func: Spanned<Func>, pub pattern: Option<Pattern>,
/// The transformation to perform on the match.
pub transform: Transform,
} }
impl Recipe { impl Recipe {
/// Whether the recipe is applicable to the target. /// Whether the recipe is applicable to the target.
pub fn applicable(&self, target: Target) -> bool { pub fn applicable(&self, target: Target) -> bool {
match (&self.pattern, target) { match (&self.pattern, target) {
(Pattern::Node(id), Target::Node(node)) => *id == node.id(), (Some(Pattern::Node(id)), Target::Node(node)) => *id == node.id(),
(Pattern::Regex(_), Target::Text(_)) => true, (Some(Pattern::Regex(_)), Target::Text(_)) => true,
_ => false, _ => false,
} }
} }
@ -998,12 +1000,13 @@ impl Recipe {
target: Target, target: Target,
) -> SourceResult<Option<Content>> { ) -> SourceResult<Option<Content>> {
let content = match (target, &self.pattern) { let content = match (target, &self.pattern) {
(Target::Node(node), &Pattern::Node(id)) if node.id() == id => { (Target::Node(node), Some(Pattern::Node(id))) if node.id() == *id => {
let node = node.to::<dyn Show>().unwrap().unguard_parts(sel); self.transform.apply(world, self.span, || {
self.call(world, || Value::Content(node))? Value::Content(node.to::<dyn Show>().unwrap().unguard_parts(sel))
})?
} }
(Target::Text(text), Pattern::Regex(regex)) => { (Target::Text(text), Some(Pattern::Regex(regex))) => {
let make = world.config().items.text; let make = world.config().items.text;
let mut result = vec![]; let mut result = vec![];
let mut cursor = 0; let mut cursor = 0;
@ -1014,7 +1017,10 @@ impl Recipe {
result.push(make(text[cursor..start].into())); result.push(make(text[cursor..start].into()));
} }
result.push(self.call(world, || Value::Str(mat.as_str().into()))?); let transformed = self
.transform
.apply(world, self.span, || Value::Str(mat.as_str().into()))?;
result.push(transformed);
cursor = mat.end(); cursor = mat.end();
} }
@ -1035,24 +1041,10 @@ impl Recipe {
Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel)))) Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel))))
} }
/// Call the recipe function, with the argument if desired.
fn call<F>(&self, world: Tracked<dyn World>, arg: F) -> SourceResult<Content>
where
F: FnOnce() -> Value,
{
let args = if self.func.v.argc() == Some(0) {
Args::new(self.func.span, [])
} else {
Args::new(self.func.span, [arg()])
};
Ok(self.func.v.call_detached(world, args)?.display(world))
}
/// Whether this recipe is for the given node. /// Whether this recipe is for the given node.
pub fn is_of(&self, node: NodeId) -> bool { pub fn is_of(&self, node: NodeId) -> bool {
match self.pattern { match self.pattern {
Pattern::Node(id) => id == node, Some(Pattern::Node(id)) => id == node,
_ => false, _ => false,
} }
} }
@ -1060,7 +1052,7 @@ impl Recipe {
impl Debug for Recipe { impl Debug for Recipe {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Recipe matching {:?} from {:?}", self.pattern, self.func.span) write!(f, "Recipe matching {:?}", self.pattern)
} }
} }
@ -1080,6 +1072,36 @@ impl Pattern {
} }
} }
/// A show rule transformation that can be applied to a match.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Transform {
/// Replacement content.
Content(Content),
/// A function to apply to the match.
Func(Func),
}
impl Transform {
/// Apply the transform.
pub fn apply<F>(
&self,
world: Tracked<dyn World>,
span: Span,
arg: F,
) -> SourceResult<Content>
where
F: FnOnce() -> Value,
{
match self {
Transform::Content(content) => Ok(content.clone()),
Transform::Func(func) => {
let args = Args::new(span, [arg()]);
Ok(func.call_detached(world, args)?.display(world))
}
}
}
}
/// A target for a show rule recipe. /// A target for a show rule recipe.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum Target<'a> { pub enum Target<'a> {
@ -1104,30 +1126,25 @@ pub trait Show: 'static + Sync + Send {
/// Unguard nested content against recursive show rules. /// Unguard nested content against recursive show rules.
fn unguard_parts(&self, sel: Selector) -> Content; fn unguard_parts(&self, sel: Selector) -> Content;
/// Access a field on this node.
fn field(&self, name: &str) -> Option<Value>;
/// The base recipe for this node that is executed if there is no /// The base recipe for this node that is executed if there is no
/// user-defined show rule. /// user-defined show rule.
fn realize( fn show(
&self, &self,
world: Tracked<dyn World>, world: Tracked<dyn World>,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Content>; ) -> SourceResult<Content>;
}
/// Post-process a node after it was realized.
#[capability]
pub trait Finalize: 'static + Sync + Send {
/// Finalize this node given the realization of a base or user recipe. Use /// Finalize this node given the realization of a base or user recipe. Use
/// this for effects that should work even in the face of a user-defined /// this for effects that should work even in the face of a user-defined
/// show rule, for example: /// show rule, for example the linking behaviour of a link node.
/// - Application of general settable properties
///
/// Defaults to just the realized content.
#[allow(unused_variables)]
fn finalize( fn finalize(
&self, &self,
world: Tracked<dyn World>, world: Tracked<dyn World>,
styles: StyleChain, styles: StyleChain,
realized: Content, realized: Content,
) -> SourceResult<Content> { ) -> SourceResult<Content>;
Ok(realized)
}
} }

View File

@ -381,7 +381,7 @@ primitive! { Str: "string", Str }
primitive! { Content: "content", primitive! { Content: "content",
Content, Content,
None => Content::empty(), None => Content::empty(),
Str(text) => Content::text(text) Str(text) => item!(text)(text.into())
} }
primitive! { Array: "array", Array } primitive! { Array: "array", Array }
primitive! { Dict: "dictionary", Dict } primitive! { Dict: "dictionary", Dict }

View File

@ -63,11 +63,11 @@ impl Markup {
/// A single piece of markup. /// A single piece of markup.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum MarkupNode { pub enum MarkupNode {
/// Whitespace containing less than two newlines. /// Whitespace.
Space(Space), Space(Space),
/// A forced line break. /// A forced line break: `\`.
Linebreak(Linebreak), Linebreak(Linebreak),
/// Plain text. /// Plain text without markup.
Text(Text), Text(Text),
/// An escape sequence: `\#`, `\u{1F5FA}`. /// An escape sequence: `\#`, `\u{1F5FA}`.
Escape(Escape), Escape(Escape),
@ -76,9 +76,9 @@ pub enum MarkupNode {
Shorthand(Shorthand), Shorthand(Shorthand),
/// A smart quote: `'` or `"`. /// A smart quote: `'` or `"`.
SmartQuote(SmartQuote), SmartQuote(SmartQuote),
/// Strong markup: `*Strong*`. /// Strong content: `*Strong*`.
Strong(Strong), Strong(Strong),
/// Emphasized markup: `_Emphasized_`. /// Emphasized content: `_Emphasized_`.
Emph(Emph), Emph(Emph),
/// A raw block with optional syntax highlighting: `` `...` ``. /// A raw block with optional syntax highlighting: `` `...` ``.
Raw(Raw), Raw(Raw),
@ -171,7 +171,7 @@ node! {
} }
node! { node! {
/// Plain text. /// Plain text without markup.
Text Text
} }
@ -367,12 +367,12 @@ impl ListItem {
} }
node! { node! {
/// An item in an enumeration (ordered list): `1. ...`. /// An item in an enumeration (ordered list): `+ ...` or `1. ...`.
EnumItem EnumItem
} }
impl EnumItem { impl EnumItem {
/// The number, if any. /// The explicit numbering, if any: `23.`.
pub fn number(&self) -> Option<usize> { pub fn number(&self) -> Option<usize> {
self.0.children().find_map(|node| match node.kind() { self.0.children().find_map(|node| match node.kind() {
NodeKind::EnumNumbering(num) => Some(*num), NodeKind::EnumNumbering(num) => Some(*num),
@ -434,9 +434,9 @@ pub enum MathNode {
Linebreak(Linebreak), Linebreak(Linebreak),
/// An escape sequence: `\#`, `\u{1F5FA}`. /// An escape sequence: `\#`, `\u{1F5FA}`.
Escape(Escape), Escape(Escape),
/// A atom: `x`, `+`, `12`. /// An atom: `x`, `+`, `12`.
Atom(Atom), Atom(Atom),
/// A base with an optional sub- and superscript: `a_1^2`. /// A base with optional sub- and superscripts: `a_1^2`.
Script(Script), Script(Script),
/// A fraction: `x/2`. /// A fraction: `x/2`.
Frac(Frac), Frac(Frac),
@ -565,9 +565,9 @@ pub enum Expr {
Content(ContentBlock), Content(ContentBlock),
/// A grouped expression: `(1 + 2)`. /// A grouped expression: `(1 + 2)`.
Parenthesized(Parenthesized), Parenthesized(Parenthesized),
/// An array expression: `(1, "hi", 12cm)`. /// An array: `(1, "hi", 12cm)`.
Array(Array), Array(Array),
/// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. /// A dictionary: `(thickness: 3pt, pattern: dashed)`.
Dict(Dict), Dict(Dict),
/// A unary operation: `-x`. /// A unary operation: `-x`.
Unary(Unary), Unary(Unary),
@ -579,16 +579,14 @@ pub enum Expr {
FuncCall(FuncCall), FuncCall(FuncCall),
/// An invocation of a method: `array.push(v)`. /// An invocation of a method: `array.push(v)`.
MethodCall(MethodCall), MethodCall(MethodCall),
/// A closure expression: `(x, y) => z`. /// A closure: `(x, y) => z`.
Closure(Closure), Closure(Closure),
/// A let binding: `let x = 1`. /// A let binding: `let x = 1`.
Let(LetBinding), Let(LetBinding),
/// A set rule: `set text(...)`. /// A set rule: `set text(...)`.
Set(SetRule), Set(SetRule),
/// A show rule: `show node: heading as [*{nody.body}*]`. /// A show rule: `show heading: it => [*{it.body}*]`.
Show(ShowRule), Show(ShowRule),
/// A wrap rule: `wrap body in columns(2, body)`.
Wrap(WrapRule),
/// An if-else conditional: `if x { y } else { z }`. /// An if-else conditional: `if x { y } else { z }`.
Conditional(Conditional), Conditional(Conditional),
/// A while loop: `while x { y }`. /// A while loop: `while x { y }`.
@ -599,12 +597,12 @@ pub enum Expr {
Import(ModuleImport), Import(ModuleImport),
/// A module include: `include "chapter1.typ"`. /// A module include: `include "chapter1.typ"`.
Include(ModuleInclude), Include(ModuleInclude),
/// A break statement: `break`. /// A break from a loop: `break`.
Break(BreakStmt), Break(LoopBreak),
/// A continue statement: `continue`. /// A continue in a loop: `continue`.
Continue(ContinueStmt), Continue(LoopContinue),
/// A return statement: `return`, `return x + 1`. /// A return from a function: `return`, `return x + 1`.
Return(ReturnStmt), Return(FuncReturn),
} }
impl TypedNode for Expr { impl TypedNode for Expr {
@ -625,15 +623,14 @@ impl TypedNode for Expr {
NodeKind::LetBinding => node.cast().map(Self::Let), NodeKind::LetBinding => node.cast().map(Self::Let),
NodeKind::SetRule => node.cast().map(Self::Set), NodeKind::SetRule => node.cast().map(Self::Set),
NodeKind::ShowRule => node.cast().map(Self::Show), NodeKind::ShowRule => node.cast().map(Self::Show),
NodeKind::WrapRule => node.cast().map(Self::Wrap),
NodeKind::Conditional => node.cast().map(Self::Conditional), NodeKind::Conditional => node.cast().map(Self::Conditional),
NodeKind::WhileLoop => node.cast().map(Self::While), NodeKind::WhileLoop => node.cast().map(Self::While),
NodeKind::ForLoop => node.cast().map(Self::For), NodeKind::ForLoop => node.cast().map(Self::For),
NodeKind::ModuleImport => node.cast().map(Self::Import), NodeKind::ModuleImport => node.cast().map(Self::Import),
NodeKind::ModuleInclude => node.cast().map(Self::Include), NodeKind::ModuleInclude => node.cast().map(Self::Include),
NodeKind::BreakStmt => node.cast().map(Self::Break), NodeKind::LoopBreak => node.cast().map(Self::Break),
NodeKind::ContinueStmt => node.cast().map(Self::Continue), NodeKind::LoopContinue => node.cast().map(Self::Continue),
NodeKind::ReturnStmt => node.cast().map(Self::Return), NodeKind::FuncReturn => node.cast().map(Self::Return),
_ => node.cast().map(Self::Lit), _ => node.cast().map(Self::Lit),
} }
} }
@ -656,7 +653,6 @@ impl TypedNode for Expr {
Self::Let(v) => v.as_untyped(), Self::Let(v) => v.as_untyped(),
Self::Set(v) => v.as_untyped(), Self::Set(v) => v.as_untyped(),
Self::Show(v) => v.as_untyped(), Self::Show(v) => v.as_untyped(),
Self::Wrap(v) => v.as_untyped(),
Self::Conditional(v) => v.as_untyped(), Self::Conditional(v) => v.as_untyped(),
Self::While(v) => v.as_untyped(), Self::While(v) => v.as_untyped(),
Self::For(v) => v.as_untyped(), Self::For(v) => v.as_untyped(),
@ -679,7 +675,6 @@ impl Expr {
| Self::Let(_) | Self::Let(_)
| Self::Set(_) | Self::Set(_)
| Self::Show(_) | Self::Show(_)
| Self::Wrap(_)
| Self::Conditional(_) | Self::Conditional(_)
| Self::While(_) | Self::While(_)
| Self::For(_) | Self::For(_)
@ -723,15 +718,15 @@ pub enum LitKind {
None, None,
/// The auto literal: `auto`. /// The auto literal: `auto`.
Auto, Auto,
/// A boolean literal: `true`, `false`. /// A boolean: `true`, `false`.
Bool(bool), Bool(bool),
/// An integer literal: `120`. /// An integer: `120`.
Int(i64), Int(i64),
/// A floating-point literal: `1.2`, `10e-4`. /// A floating-point number: `1.2`, `10e-4`.
Float(f64), Float(f64),
/// A numeric literal with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`. /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`.
Numeric(f64, Unit), Numeric(f64, Unit),
/// A string literal: `"hello!"`. /// A quoted string: `"..."`.
Str(EcoString), Str(EcoString),
} }
@ -760,7 +755,7 @@ impl ContentBlock {
} }
node! { node! {
/// A parenthesized expression: `(1 + 2)`. /// A grouped expression: `(1 + 2)`.
Parenthesized Parenthesized
} }
@ -853,7 +848,7 @@ impl TypedNode for DictItem {
} }
node! { node! {
/// A pair of a name and an expression: `thickness: 3pt`. /// A named pair: `thickness: 3pt`.
Named Named
} }
@ -870,7 +865,7 @@ impl Named {
} }
node! { node! {
/// A pair of a string key and an expression: `"spacy key": true`. /// A keyed pair: `"spacy key": true`.
Keyed Keyed
} }
@ -1204,7 +1199,7 @@ impl MethodCall {
} }
node! { node! {
/// The arguments to a function: `12, draw: false`. /// A function call's argument list: `(12pt, y)`.
Args Args
} }
@ -1245,7 +1240,7 @@ impl TypedNode for Arg {
} }
node! { node! {
/// A closure expression: `(x, y) => z`. /// A closure: `(x, y) => z`.
Closure Closure
} }
@ -1347,52 +1342,34 @@ impl SetRule {
pub fn args(&self) -> Args { pub fn args(&self) -> Args {
self.0.cast_last_child().expect("set rule is missing argument list") self.0.cast_last_child().expect("set rule is missing argument list")
} }
/// A condition under which the set rule applies.
pub fn condition(&self) -> Option<Expr> {
self.0
.children()
.skip_while(|child| child.kind() != &NodeKind::If)
.find_map(SyntaxNode::cast)
}
} }
node! { node! {
/// A show rule: `show node: heading as [*{nody.body}*]`. /// A show rule: `show heading: it => [*{it.body}*]`.
ShowRule ShowRule
} }
impl ShowRule { impl ShowRule {
/// The binding to assign to.
pub fn binding(&self) -> Option<Ident> {
let mut children = self.0.children();
children
.find_map(SyntaxNode::cast)
.filter(|_| children.any(|child| child.kind() == &NodeKind::Colon))
}
/// The pattern that this rule matches. /// The pattern that this rule matches.
pub fn pattern(&self) -> Expr { pub fn pattern(&self) -> Option<Expr> {
self.0 self.0
.children() .children()
.rev() .rev()
.skip_while(|child| child.kind() != &NodeKind::As) .skip_while(|child| child.kind() != &NodeKind::Colon)
.find_map(SyntaxNode::cast) .find_map(SyntaxNode::cast)
.expect("show rule is missing pattern")
} }
/// The expression that realizes the node. /// The transformation recipe.
pub fn body(&self) -> Expr { pub fn transform(&self) -> Expr {
self.0.cast_last_child().expect("show rule is missing body") self.0.cast_last_child().expect("show rule is missing transform")
}
}
node! {
/// A wrap rule: `wrap body in columns(2, body)`.
WrapRule
}
impl WrapRule {
/// The binding to assign the remaining markup to.
pub fn binding(&self) -> Ident {
self.0.cast_first_child().expect("wrap rule is missing binding")
}
/// The expression to evaluate.
pub fn body(&self) -> Expr {
self.0.cast_last_child().expect("wrap rule is missing body")
} }
} }
@ -1462,7 +1439,7 @@ impl ForLoop {
} }
node! { node! {
/// A for-in loop: `for x in y { z }`. /// A for loop's destructuring pattern: `x` or `x, y`.
ForPattern ForPattern
} }
@ -1533,21 +1510,21 @@ impl ModuleInclude {
} }
node! { node! {
/// A break expression: `break`. /// A break from a loop: `break`.
BreakStmt LoopBreak
} }
node! { node! {
/// A continue expression: `continue`. /// A continue in a loop: `continue`.
ContinueStmt LoopContinue
} }
node! { node! {
/// A return expression: `return`, `return x + 1`. /// A return from a function: `return`, `return x + 1`.
ReturnStmt FuncReturn
} }
impl ReturnStmt { impl FuncReturn {
/// The expression to return. /// The expression to return.
pub fn body(&self) -> Option<Expr> { pub fn body(&self) -> Option<Expr> {
self.0.cast_last_child() self.0.cast_last_child()
@ -1555,7 +1532,7 @@ impl ReturnStmt {
} }
node! { node! {
/// An identifier. /// An identifier: `it`.
Ident Ident
} }

View File

@ -257,7 +257,6 @@ impl Category {
NodeKind::Let => Some(Category::Keyword), NodeKind::Let => Some(Category::Keyword),
NodeKind::Set => Some(Category::Keyword), NodeKind::Set => Some(Category::Keyword),
NodeKind::Show => Some(Category::Keyword), NodeKind::Show => Some(Category::Keyword),
NodeKind::Wrap => Some(Category::Keyword),
NodeKind::If => Some(Category::Keyword), NodeKind::If => Some(Category::Keyword),
NodeKind::Else => Some(Category::Keyword), NodeKind::Else => Some(Category::Keyword),
NodeKind::For => Some(Category::Keyword), NodeKind::For => Some(Category::Keyword),
@ -269,7 +268,6 @@ impl Category {
NodeKind::Import => Some(Category::Keyword), NodeKind::Import => Some(Category::Keyword),
NodeKind::Include => Some(Category::Keyword), NodeKind::Include => Some(Category::Keyword),
NodeKind::From => Some(Category::Keyword), NodeKind::From => Some(Category::Keyword),
NodeKind::As => Some(Category::Keyword),
NodeKind::Markup { .. } => match parent.kind() { NodeKind::Markup { .. } => match parent.kind() {
NodeKind::DescItem NodeKind::DescItem
@ -316,8 +314,7 @@ impl Category {
if parent if parent
.children() .children()
.rev() .rev()
.skip_while(|child| child.kind() != &NodeKind::As) .skip_while(|child| child.kind() != &NodeKind::Colon)
.take_while(|child| child.kind() != &NodeKind::Colon)
.find(|c| matches!(c.kind(), NodeKind::Ident(_))) .find(|c| matches!(c.kind(), NodeKind::Ident(_)))
.map_or(false, |ident| std::ptr::eq(ident, child)) => .map_or(false, |ident| std::ptr::eq(ident, child)) =>
{ {
@ -349,7 +346,6 @@ impl Category {
NodeKind::LetBinding => None, NodeKind::LetBinding => None,
NodeKind::SetRule => None, NodeKind::SetRule => None,
NodeKind::ShowRule => None, NodeKind::ShowRule => None,
NodeKind::WrapRule => None,
NodeKind::Conditional => None, NodeKind::Conditional => None,
NodeKind::WhileLoop => None, NodeKind::WhileLoop => None,
NodeKind::ForLoop => None, NodeKind::ForLoop => None,
@ -357,9 +353,9 @@ impl Category {
NodeKind::ModuleImport => None, NodeKind::ModuleImport => None,
NodeKind::ImportItems => None, NodeKind::ImportItems => None,
NodeKind::ModuleInclude => None, NodeKind::ModuleInclude => None,
NodeKind::BreakStmt => None, NodeKind::LoopBreak => None,
NodeKind::ContinueStmt => None, NodeKind::LoopContinue => None,
NodeKind::ReturnStmt => None, NodeKind::FuncReturn => None,
NodeKind::Error(_, _) => Some(Category::Error), NodeKind::Error(_, _) => Some(Category::Error),
} }

View File

@ -106,8 +106,6 @@ pub enum NodeKind {
Set, Set,
/// The `show` keyword. /// The `show` keyword.
Show, Show,
/// The `wrap` keyword.
Wrap,
/// The `if` keyword. /// The `if` keyword.
If, If,
/// The `else` keyword. /// The `else` keyword.
@ -130,8 +128,6 @@ pub enum NodeKind {
Include, Include,
/// The `from` keyword. /// The `from` keyword.
From, From,
/// The `as` keyword.
As,
/// Markup of which all lines must have a minimal indentation. /// Markup of which all lines must have a minimal indentation.
/// ///
@ -139,7 +135,7 @@ pub enum NodeKind {
/// started, but to the right of which column all markup elements must be, /// started, but to the right of which column all markup elements must be,
/// so it is zero except inside indent-aware constructs like lists. /// so it is zero except inside indent-aware constructs like lists.
Markup { min_indent: usize }, Markup { min_indent: usize },
/// Consecutive text without markup. /// Plain text without markup.
Text(EcoString), Text(EcoString),
/// A forced line break: `\`. /// A forced line break: `\`.
Linebreak, Linebreak,
@ -150,9 +146,9 @@ pub enum NodeKind {
Shorthand(char), Shorthand(char),
/// A smart quote: `'` or `"`. /// A smart quote: `'` or `"`.
SmartQuote { double: bool }, SmartQuote { double: bool },
/// Strong markup: `*Strong*`. /// Strong content: `*Strong*`.
Strong, Strong,
/// Emphasized markup: `_Emphasized_`. /// Emphasized content: `_Emphasized_`.
Emph, Emph,
/// A raw block with optional syntax highlighting: `` `...` ``. /// A raw block with optional syntax highlighting: `` `...` ``.
Raw(Arc<RawKind>), Raw(Arc<RawKind>),
@ -164,26 +160,26 @@ pub enum NodeKind {
Ref(EcoString), Ref(EcoString),
/// A section heading: `= Introduction`. /// A section heading: `= Introduction`.
Heading, Heading,
/// An item of an unordered list: `- ...`. /// An item in an unordered list: `- ...`.
ListItem, ListItem,
/// An item of an enumeration (ordered list): `+ ...` or `1. ...`. /// An item in an enumeration (ordered list): `+ ...` or `1. ...`.
EnumItem, EnumItem,
/// An explicit enumeration numbering: `23.`. /// An explicit enumeration numbering: `23.`.
EnumNumbering(usize), EnumNumbering(usize),
/// An item of a description list: `/ Term: Details. /// An item in a description list: `/ Term: Details`.
DescItem, DescItem,
/// A math formula: `$x$`, `$ x^2 $`. /// A mathematical formula: `$x$`, `$ x^2 $`.
Math, Math,
/// An atom in a math formula: `x`, `+`, `12`. /// An atom in a formula: `x`, `+`, `12`.
Atom(EcoString), Atom(EcoString),
/// A base with optional sub- and superscript in a math formula: `a_1^2`. /// A base with optional sub- and superscripts in a formula: `a_1^2`.
Script, Script,
/// A fraction in a math formula: `x/2`. /// A fraction in a formula: `x/2`.
Frac, Frac,
/// An alignment indicator in a math formula: `&`, `&&`. /// An alignment indicator in a formula: `&`, `&&`.
Align, Align,
/// An identifier: `center`. /// An identifier: `it`.
Ident(EcoString), Ident(EcoString),
/// A boolean: `true`, `false`. /// A boolean: `true`, `false`.
Bool(bool), Bool(bool),
@ -219,9 +215,9 @@ pub enum NodeKind {
FuncCall, FuncCall,
/// An invocation of a method: `array.push(v)`. /// An invocation of a method: `array.push(v)`.
MethodCall, MethodCall,
/// A function call's argument list: `(x, y)`. /// A function call's argument list: `(12pt, y)`.
Args, Args,
/// Spreaded arguments or a argument sink: `..x`. /// Spreaded arguments or an argument sink: `..x`.
Spread, Spread,
/// A closure: `(x, y) => z`. /// A closure: `(x, y) => z`.
Closure, Closure,
@ -231,15 +227,13 @@ pub enum NodeKind {
LetBinding, LetBinding,
/// A set rule: `set text(...)`. /// A set rule: `set text(...)`.
SetRule, SetRule,
/// A show rule: `show node: heading as [*{nody.body}*]`. /// A show rule: `show heading: it => [*{it.body}*]`.
ShowRule, ShowRule,
/// A wrap rule: `wrap body in columns(2, body)`.
WrapRule,
/// An if-else conditional: `if x { y } else { z }`. /// An if-else conditional: `if x { y } else { z }`.
Conditional, Conditional,
/// A while loop: `while x { ... }`. /// A while loop: `while x { y }`.
WhileLoop, WhileLoop,
/// A for loop: `for x in y { ... }`. /// A for loop: `for x in y { z }`.
ForLoop, ForLoop,
/// A for loop's destructuring pattern: `x` or `x, y`. /// A for loop's destructuring pattern: `x` or `x, y`.
ForPattern, ForPattern,
@ -249,12 +243,12 @@ pub enum NodeKind {
ImportItems, ImportItems,
/// A module include: `include "chapter1.typ"`. /// A module include: `include "chapter1.typ"`.
ModuleInclude, ModuleInclude,
/// A break statement: `break`. /// A break from a loop: `break`.
BreakStmt, LoopBreak,
/// A continue statement: `continue`. /// A continue in a loop: `continue`.
ContinueStmt, LoopContinue,
/// A return statement: `return x + 1`. /// A return from a function: `return`, `return x + 1`.
ReturnStmt, FuncReturn,
/// An invalid sequence of characters. /// An invalid sequence of characters.
Error(ErrorPos, EcoString), Error(ErrorPos, EcoString),
@ -367,7 +361,6 @@ impl NodeKind {
Self::Let => "keyword `let`", Self::Let => "keyword `let`",
Self::Set => "keyword `set`", Self::Set => "keyword `set`",
Self::Show => "keyword `show`", Self::Show => "keyword `show`",
Self::Wrap => "keyword `wrap`",
Self::If => "keyword `if`", Self::If => "keyword `if`",
Self::Else => "keyword `else`", Self::Else => "keyword `else`",
Self::For => "keyword `for`", Self::For => "keyword `for`",
@ -379,7 +372,6 @@ impl NodeKind {
Self::Import => "keyword `import`", Self::Import => "keyword `import`",
Self::Include => "keyword `include`", Self::Include => "keyword `include`",
Self::From => "keyword `from`", Self::From => "keyword `from`",
Self::As => "keyword `as`",
Self::Markup { .. } => "markup", Self::Markup { .. } => "markup",
Self::Text(_) => "text", Self::Text(_) => "text",
Self::Linebreak => "linebreak", Self::Linebreak => "linebreak",
@ -426,7 +418,6 @@ impl NodeKind {
Self::LetBinding => "`let` expression", Self::LetBinding => "`let` expression",
Self::SetRule => "`set` expression", Self::SetRule => "`set` expression",
Self::ShowRule => "`show` expression", Self::ShowRule => "`show` expression",
Self::WrapRule => "`wrap` expression",
Self::Conditional => "`if` expression", Self::Conditional => "`if` expression",
Self::WhileLoop => "while-loop expression", Self::WhileLoop => "while-loop expression",
Self::ForLoop => "for-loop expression", Self::ForLoop => "for-loop expression",
@ -434,9 +425,9 @@ impl NodeKind {
Self::ModuleImport => "`import` expression", Self::ModuleImport => "`import` expression",
Self::ImportItems => "import items", Self::ImportItems => "import items",
Self::ModuleInclude => "`include` expression", Self::ModuleInclude => "`include` expression",
Self::BreakStmt => "`break` expression", Self::LoopBreak => "`break` expression",
Self::ContinueStmt => "`continue` expression", Self::LoopContinue => "`continue` expression",
Self::ReturnStmt => "`return` expression", Self::FuncReturn => "`return` expression",
Self::Error(_, _) => "syntax error", Self::Error(_, _) => "syntax error",
} }
} }
@ -488,7 +479,6 @@ impl Hash for NodeKind {
Self::Let => {} Self::Let => {}
Self::Set => {} Self::Set => {}
Self::Show => {} Self::Show => {}
Self::Wrap => {}
Self::If => {} Self::If => {}
Self::Else => {} Self::Else => {}
Self::For => {} Self::For => {}
@ -500,7 +490,6 @@ impl Hash for NodeKind {
Self::Import => {} Self::Import => {}
Self::Include => {} Self::Include => {}
Self::From => {} Self::From => {}
Self::As => {}
Self::Markup { min_indent } => min_indent.hash(state), Self::Markup { min_indent } => min_indent.hash(state),
Self::Text(s) => s.hash(state), Self::Text(s) => s.hash(state),
Self::Linebreak => {} Self::Linebreak => {}
@ -548,7 +537,6 @@ impl Hash for NodeKind {
Self::LetBinding => {} Self::LetBinding => {}
Self::SetRule => {} Self::SetRule => {}
Self::ShowRule => {} Self::ShowRule => {}
Self::WrapRule => {}
Self::Conditional => {} Self::Conditional => {}
Self::WhileLoop => {} Self::WhileLoop => {}
Self::ForLoop => {} Self::ForLoop => {}
@ -556,9 +544,9 @@ impl Hash for NodeKind {
Self::ModuleImport => {} Self::ModuleImport => {}
Self::ImportItems => {} Self::ImportItems => {}
Self::ModuleInclude => {} Self::ModuleInclude => {}
Self::BreakStmt => {} Self::LoopBreak => {}
Self::ContinueStmt => {} Self::LoopContinue => {}
Self::ReturnStmt => {} Self::FuncReturn => {}
Self::Error(pos, msg) => (pos, msg).hash(state), Self::Error(pos, msg) => (pos, msg).hash(state),
} }
} }

View File

@ -208,7 +208,6 @@ where
}); });
} }
/// Parse a markup node.
fn markup_node(p: &mut Parser, at_start: &mut bool) { fn markup_node(p: &mut Parser, at_start: &mut bool) {
let Some(token) = p.peek() else { return }; let Some(token) = p.peek() else { return };
match token { match token {
@ -245,10 +244,10 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
NodeKind::Eq => heading(p, *at_start), NodeKind::Eq => heading(p, *at_start),
// Lists. // Lists.
NodeKind::Minus => list_node(p, *at_start), NodeKind::Minus => list_item(p, *at_start),
NodeKind::Plus | NodeKind::EnumNumbering(_) => enum_node(p, *at_start), NodeKind::Plus | NodeKind::EnumNumbering(_) => enum_item(p, *at_start),
NodeKind::Slash => { NodeKind::Slash => {
desc_node(p, *at_start).ok(); desc_item(p, *at_start).ok();
} }
NodeKind::Colon => { NodeKind::Colon => {
let marker = p.marker(); let marker = p.marker();
@ -261,7 +260,6 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
| NodeKind::Let | NodeKind::Let
| NodeKind::Set | NodeKind::Set
| NodeKind::Show | NodeKind::Show
| NodeKind::Wrap
| NodeKind::If | NodeKind::If
| NodeKind::While | NodeKind::While
| NodeKind::For | NodeKind::For
@ -282,7 +280,6 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
*at_start = false; *at_start = false;
} }
/// Parse strong content.
fn strong(p: &mut Parser) { fn strong(p: &mut Parser) {
p.perform(NodeKind::Strong, |p| { p.perform(NodeKind::Strong, |p| {
p.start_group(Group::Strong); p.start_group(Group::Strong);
@ -291,7 +288,6 @@ fn strong(p: &mut Parser) {
}) })
} }
/// Parse emphasized content.
fn emph(p: &mut Parser) { fn emph(p: &mut Parser) {
p.perform(NodeKind::Emph, |p| { p.perform(NodeKind::Emph, |p| {
p.start_group(Group::Emph); p.start_group(Group::Emph);
@ -300,7 +296,6 @@ fn emph(p: &mut Parser) {
}) })
} }
/// Parse a heading.
fn heading(p: &mut Parser, at_start: bool) { fn heading(p: &mut Parser, at_start: bool) {
let marker = p.marker(); let marker = p.marker();
let current_start = p.current_start(); let current_start = p.current_start();
@ -317,8 +312,7 @@ fn heading(p: &mut Parser, at_start: bool) {
} }
} }
/// Parse a single list item. fn list_item(p: &mut Parser, at_start: bool) {
fn list_node(p: &mut Parser, at_start: bool) {
let marker = p.marker(); let marker = p.marker();
let text: EcoString = p.peek_src().into(); let text: EcoString = p.peek_src().into();
p.assert(NodeKind::Minus); p.assert(NodeKind::Minus);
@ -332,8 +326,7 @@ fn list_node(p: &mut Parser, at_start: bool) {
} }
} }
/// Parse a single enum item. fn enum_item(p: &mut Parser, at_start: bool) {
fn enum_node(p: &mut Parser, at_start: bool) {
let marker = p.marker(); let marker = p.marker();
let text: EcoString = p.peek_src().into(); let text: EcoString = p.peek_src().into();
p.eat(); p.eat();
@ -347,8 +340,7 @@ fn enum_node(p: &mut Parser, at_start: bool) {
} }
} }
/// Parse a single description list item. fn desc_item(p: &mut Parser, at_start: bool) -> ParseResult {
fn desc_node(p: &mut Parser, at_start: bool) -> ParseResult {
let marker = p.marker(); let marker = p.marker();
let text: EcoString = p.peek_src().into(); let text: EcoString = p.peek_src().into();
p.eat(); p.eat();
@ -366,7 +358,6 @@ fn desc_node(p: &mut Parser, at_start: bool) -> ParseResult {
Ok(()) Ok(())
} }
/// Parse an expression within a markup mode.
fn markup_expr(p: &mut Parser) { fn markup_expr(p: &mut Parser) {
// Does the expression need termination or can content follow directly? // Does the expression need termination or can content follow directly?
let stmt = matches!( let stmt = matches!(
@ -375,7 +366,6 @@ fn markup_expr(p: &mut Parser) {
NodeKind::Let NodeKind::Let
| NodeKind::Set | NodeKind::Set
| NodeKind::Show | NodeKind::Show
| NodeKind::Wrap
| NodeKind::Import | NodeKind::Import
| NodeKind::Include | NodeKind::Include
) )
@ -389,7 +379,6 @@ fn markup_expr(p: &mut Parser) {
p.end_group(); p.end_group();
} }
/// Parse math.
fn math(p: &mut Parser) { fn math(p: &mut Parser) {
p.perform(NodeKind::Math, |p| { p.perform(NodeKind::Math, |p| {
p.start_group(Group::Math); p.start_group(Group::Math);
@ -400,12 +389,10 @@ fn math(p: &mut Parser) {
}); });
} }
/// Parse a math node.
fn math_node(p: &mut Parser) { fn math_node(p: &mut Parser) {
math_node_prec(p, 0, None) math_node_prec(p, 0, None)
} }
/// Parse a math node with operators having at least the minimum precedence.
fn math_node_prec(p: &mut Parser, min_prec: usize, stop: Option<NodeKind>) { fn math_node_prec(p: &mut Parser, min_prec: usize, stop: Option<NodeKind>) {
let marker = p.marker(); let marker = p.marker();
math_primary(p); math_primary(p);
@ -457,19 +444,18 @@ fn math_primary(p: &mut Parser) {
| NodeKind::Ident(_) => p.eat(), | NodeKind::Ident(_) => p.eat(),
// Groups. // Groups.
NodeKind::LeftParen => group(p, Group::Paren, '(', ')'), NodeKind::LeftParen => math_group(p, Group::Paren, '(', ')'),
NodeKind::LeftBracket => group(p, Group::Bracket, '[', ']'), NodeKind::LeftBracket => math_group(p, Group::Bracket, '[', ']'),
NodeKind::LeftBrace => group(p, Group::Brace, '{', '}'), NodeKind::LeftBrace => math_group(p, Group::Brace, '{', '}'),
// Alignment indactor. // Alignment indactor.
NodeKind::Amp => align(p), NodeKind::Amp => math_align(p),
_ => p.unexpected(), _ => p.unexpected(),
} }
} }
/// Parse grouped math. fn math_group(p: &mut Parser, group: Group, l: char, r: char) {
fn group(p: &mut Parser, group: Group, l: char, r: char) {
p.perform(NodeKind::Math, |p| { p.perform(NodeKind::Math, |p| {
let marker = p.marker(); let marker = p.marker();
p.start_group(group); p.start_group(group);
@ -483,15 +469,13 @@ fn group(p: &mut Parser, group: Group, l: char, r: char) {
}) })
} }
/// Parse an alignment indicator. fn math_align(p: &mut Parser) {
fn align(p: &mut Parser) {
p.perform(NodeKind::Align, |p| { p.perform(NodeKind::Align, |p| {
p.assert(NodeKind::Amp); p.assert(NodeKind::Amp);
while p.eat_if(NodeKind::Amp) {} while p.eat_if(NodeKind::Amp) {}
}) })
} }
/// Parse an expression.
fn expr(p: &mut Parser) -> ParseResult { fn expr(p: &mut Parser) -> ParseResult {
expr_prec(p, false, 0) expr_prec(p, false, 0)
} }
@ -571,7 +555,6 @@ fn expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult {
Ok(()) Ok(())
} }
/// Parse a primary expression.
fn primary(p: &mut Parser, atomic: bool) -> ParseResult { fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
if literal(p) { if literal(p) {
return Ok(()); return Ok(());
@ -599,18 +582,17 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
Some(NodeKind::LeftBracket) => Ok(content_block(p)), Some(NodeKind::LeftBracket) => Ok(content_block(p)),
// Keywords. // Keywords.
Some(NodeKind::Let) => let_expr(p), Some(NodeKind::Let) => let_binding(p),
Some(NodeKind::Set) => set_expr(p), Some(NodeKind::Set) => set_rule(p),
Some(NodeKind::Show) => show_expr(p), Some(NodeKind::Show) => show_rule(p),
Some(NodeKind::Wrap) => wrap_expr(p), Some(NodeKind::If) => conditional(p),
Some(NodeKind::If) => if_expr(p), Some(NodeKind::While) => while_loop(p),
Some(NodeKind::While) => while_expr(p), Some(NodeKind::For) => for_loop(p),
Some(NodeKind::For) => for_expr(p), Some(NodeKind::Import) => module_import(p),
Some(NodeKind::Import) => import_expr(p), Some(NodeKind::Include) => module_include(p),
Some(NodeKind::Include) => include_expr(p), Some(NodeKind::Break) => break_stmt(p),
Some(NodeKind::Break) => break_expr(p), Some(NodeKind::Continue) => continue_stmt(p),
Some(NodeKind::Continue) => continue_expr(p), Some(NodeKind::Return) => return_stmt(p),
Some(NodeKind::Return) => return_expr(p),
Some(NodeKind::Error(_, _)) => { Some(NodeKind::Error(_, _)) => {
p.eat(); p.eat();
@ -625,10 +607,8 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult {
} }
} }
/// Parse a literal.
fn literal(p: &mut Parser) -> bool { fn literal(p: &mut Parser) -> bool {
match p.peek() { match p.peek() {
// Basic values.
Some( Some(
NodeKind::None NodeKind::None
| NodeKind::Auto | NodeKind::Auto
@ -645,7 +625,6 @@ fn literal(p: &mut Parser) -> bool {
} }
} }
/// Parse an identifier.
fn ident(p: &mut Parser) -> ParseResult { fn ident(p: &mut Parser) -> ParseResult {
match p.peek() { match p.peek() {
Some(NodeKind::Ident(_)) => { Some(NodeKind::Ident(_)) => {
@ -762,8 +741,6 @@ fn collection(p: &mut Parser, keyed: bool) -> (CollectionKind, usize) {
(kind, items) (kind, items)
} }
/// Parse an expression or a named pair, returning whether it's a spread or a
/// named pair.
fn item(p: &mut Parser, keyed: bool) -> ParseResult<NodeKind> { fn item(p: &mut Parser, keyed: bool) -> ParseResult<NodeKind> {
let marker = p.marker(); let marker = p.marker();
if p.eat_if(NodeKind::Dots) { if p.eat_if(NodeKind::Dots) {
@ -806,8 +783,6 @@ fn item(p: &mut Parser, keyed: bool) -> ParseResult<NodeKind> {
} }
} }
/// Convert a collection into an array, producing errors for anything other than
/// expressions.
fn array(p: &mut Parser, marker: Marker) { fn array(p: &mut Parser, marker: Marker) {
marker.filter_children(p, |x| match x.kind() { marker.filter_children(p, |x| match x.kind() {
NodeKind::Named | NodeKind::Keyed => Err("expected expression"), NodeKind::Named | NodeKind::Keyed => Err("expected expression"),
@ -816,8 +791,6 @@ fn array(p: &mut Parser, marker: Marker) {
marker.end(p, NodeKind::Array); marker.end(p, NodeKind::Array);
} }
/// Convert a collection into a dictionary, producing errors for anything other
/// than named and keyed pairs.
fn dict(p: &mut Parser, marker: Marker) { fn dict(p: &mut Parser, marker: Marker) {
let mut used = HashSet::new(); let mut used = HashSet::new();
marker.filter_children(p, |x| match x.kind() { marker.filter_children(p, |x| match x.kind() {
@ -838,8 +811,6 @@ fn dict(p: &mut Parser, marker: Marker) {
marker.end(p, NodeKind::Dict); marker.end(p, NodeKind::Dict);
} }
/// Convert a collection into a list of parameters, producing errors for
/// anything other than identifiers, spread operations and named pairs.
fn params(p: &mut Parser, marker: Marker) { fn params(p: &mut Parser, marker: Marker) {
marker.filter_children(p, |x| match x.kind() { marker.filter_children(p, |x| match x.kind() {
kind if kind.is_paren() => Ok(()), kind if kind.is_paren() => Ok(()),
@ -866,7 +837,6 @@ fn code_block(p: &mut Parser) {
}); });
} }
/// Parse expressions.
fn code(p: &mut Parser) { fn code(p: &mut Parser) {
while !p.eof() { while !p.eof() {
p.start_group(Group::Expr); p.start_group(Group::Expr);
@ -880,7 +850,6 @@ fn code(p: &mut Parser) {
} }
} }
/// Parse a content block: `[...]`.
fn content_block(p: &mut Parser) { fn content_block(p: &mut Parser) {
p.perform(NodeKind::ContentBlock, |p| { p.perform(NodeKind::ContentBlock, |p| {
p.start_group(Group::Bracket); p.start_group(Group::Bracket);
@ -889,7 +858,6 @@ fn content_block(p: &mut Parser) {
}); });
} }
/// Parse the arguments to a function call.
fn args(p: &mut Parser) -> ParseResult { fn args(p: &mut Parser) -> ParseResult {
match p.peek_direct() { match p.peek_direct() {
Some(NodeKind::LeftParen) => {} Some(NodeKind::LeftParen) => {}
@ -931,8 +899,7 @@ fn args(p: &mut Parser) -> ParseResult {
Ok(()) Ok(())
} }
/// Parse a let expression. fn let_binding(p: &mut Parser) -> ParseResult {
fn let_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::LetBinding, |p| { p.perform(NodeKind::LetBinding, |p| {
p.assert(NodeKind::Let); p.assert(NodeKind::Let);
@ -965,45 +932,30 @@ fn let_expr(p: &mut Parser) -> ParseResult {
}) })
} }
/// Parse a set expression. fn set_rule(p: &mut Parser) -> ParseResult {
fn set_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::SetRule, |p| { p.perform(NodeKind::SetRule, |p| {
p.assert(NodeKind::Set); p.assert(NodeKind::Set);
ident(p)?; ident(p)?;
args(p) args(p)?;
}) if p.eat_if(NodeKind::If) {
}
/// Parse a show expression.
fn show_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::ShowRule, |p| {
p.assert(NodeKind::Show);
let marker = p.marker();
expr(p)?;
if p.eat_if(NodeKind::Colon) {
marker.filter_children(p, |child| match child.kind() {
NodeKind::Ident(_) | NodeKind::Colon => Ok(()),
_ => Err("expected identifier"),
});
expr(p)?; expr(p)?;
} }
p.expect(NodeKind::As)?; Ok(())
expr(p)
}) })
} }
/// Parse a wrap expression. fn show_rule(p: &mut Parser) -> ParseResult {
fn wrap_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::ShowRule, |p| {
p.perform(NodeKind::WrapRule, |p| { p.assert(NodeKind::Show);
p.assert(NodeKind::Wrap); expr(p)?;
ident(p)?; if p.eat_if(NodeKind::Colon) {
p.expect(NodeKind::In)?; expr(p)?;
expr(p) }
Ok(())
}) })
} }
/// Parse an if-else expresion. fn conditional(p: &mut Parser) -> ParseResult {
fn if_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::Conditional, |p| { p.perform(NodeKind::Conditional, |p| {
p.assert(NodeKind::If); p.assert(NodeKind::If);
@ -1012,7 +964,7 @@ fn if_expr(p: &mut Parser) -> ParseResult {
if p.eat_if(NodeKind::Else) { if p.eat_if(NodeKind::Else) {
if p.at(NodeKind::If) { if p.at(NodeKind::If) {
if_expr(p)?; conditional(p)?;
} else { } else {
body(p)?; body(p)?;
} }
@ -1022,8 +974,7 @@ fn if_expr(p: &mut Parser) -> ParseResult {
}) })
} }
/// Parse a while expresion. fn while_loop(p: &mut Parser) -> ParseResult {
fn while_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::WhileLoop, |p| { p.perform(NodeKind::WhileLoop, |p| {
p.assert(NodeKind::While); p.assert(NodeKind::While);
expr(p)?; expr(p)?;
@ -1031,8 +982,7 @@ fn while_expr(p: &mut Parser) -> ParseResult {
}) })
} }
/// Parse a for-in expression. fn for_loop(p: &mut Parser) -> ParseResult {
fn for_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::ForLoop, |p| { p.perform(NodeKind::ForLoop, |p| {
p.assert(NodeKind::For); p.assert(NodeKind::For);
for_pattern(p)?; for_pattern(p)?;
@ -1042,7 +992,6 @@ fn for_expr(p: &mut Parser) -> ParseResult {
}) })
} }
/// Parse a for loop pattern.
fn for_pattern(p: &mut Parser) -> ParseResult { fn for_pattern(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::ForPattern, |p| { p.perform(NodeKind::ForPattern, |p| {
ident(p)?; ident(p)?;
@ -1053,8 +1002,7 @@ fn for_pattern(p: &mut Parser) -> ParseResult {
}) })
} }
/// Parse an import expression. fn module_import(p: &mut Parser) -> ParseResult {
fn import_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::ModuleImport, |p| { p.perform(NodeKind::ModuleImport, |p| {
p.assert(NodeKind::Import); p.assert(NodeKind::Import);
@ -1081,33 +1029,29 @@ fn import_expr(p: &mut Parser) -> ParseResult {
}) })
} }
/// Parse an include expression. fn module_include(p: &mut Parser) -> ParseResult {
fn include_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::ModuleInclude, |p| { p.perform(NodeKind::ModuleInclude, |p| {
p.assert(NodeKind::Include); p.assert(NodeKind::Include);
expr(p) expr(p)
}) })
} }
/// Parse a break expression. fn break_stmt(p: &mut Parser) -> ParseResult {
fn break_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::LoopBreak, |p| {
p.perform(NodeKind::BreakStmt, |p| {
p.assert(NodeKind::Break); p.assert(NodeKind::Break);
Ok(()) Ok(())
}) })
} }
/// Parse a continue expression. fn continue_stmt(p: &mut Parser) -> ParseResult {
fn continue_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::LoopContinue, |p| {
p.perform(NodeKind::ContinueStmt, |p| {
p.assert(NodeKind::Continue); p.assert(NodeKind::Continue);
Ok(()) Ok(())
}) })
} }
/// Parse a return expression. fn return_stmt(p: &mut Parser) -> ParseResult {
fn return_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::FuncReturn, |p| {
p.perform(NodeKind::ReturnStmt, |p| {
p.assert(NodeKind::Return); p.assert(NodeKind::Return);
if !p.at(NodeKind::Comma) && !p.eof() { if !p.at(NodeKind::Comma) && !p.eof() {
expr(p)?; expr(p)?;
@ -1116,7 +1060,6 @@ fn return_expr(p: &mut Parser) -> ParseResult {
}) })
} }
/// Parse a control flow body.
fn body(p: &mut Parser) -> ParseResult { fn body(p: &mut Parser) -> ParseResult {
match p.peek() { match p.peek() {
Some(NodeKind::LeftBracket) => Ok(content_block(p)), Some(NodeKind::LeftBracket) => Ok(content_block(p)),

View File

@ -613,12 +613,10 @@ fn keyword(ident: &str) -> Option<NodeKind> {
"let" => NodeKind::Let, "let" => NodeKind::Let,
"set" => NodeKind::Set, "set" => NodeKind::Set,
"show" => NodeKind::Show, "show" => NodeKind::Show,
"wrap" => NodeKind::Wrap,
"if" => NodeKind::If, "if" => NodeKind::If,
"else" => NodeKind::Else, "else" => NodeKind::Else,
"for" => NodeKind::For, "for" => NodeKind::For,
"in" => NodeKind::In, "in" => NodeKind::In,
"as" => NodeKind::As,
"while" => NodeKind::While, "while" => NodeKind::While,
"break" => NodeKind::Break, "break" => NodeKind::Break,
"continue" => NodeKind::Continue, "continue" => NodeKind::Continue,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -38,7 +38,7 @@ fn main() {
// cache, a deterministic order is important for reproducibility. // cache, a deterministic order is important for reproducibility.
for entry in WalkDir::new("typ").sort_by_file_name() { for entry in WalkDir::new("typ").sort_by_file_name() {
let entry = entry.unwrap(); let entry = entry.unwrap();
if entry.depth() <= 1 { if entry.depth() == 0 {
continue; continue;
} }

View File

@ -9,7 +9,7 @@
--- ---
#set raw(around: none) #set raw(around: none)
#show it: raw as text("IBM Plex Sans", eval(it.text)) #show raw: it => text("IBM Plex Sans", eval(it.text))
Interacting Interacting
``` ```
@ -31,19 +31,18 @@ Blue #move(dy: -0.15em)[🌊]
--- ---
// Error: 23-30 cannot access file system from here // Error: 23-30 cannot access file system from here
#show it: raw as eval(it.text) #show raw: it => eval(it.text)
``` ```
#show strong as image("/res/tiger.jpg") #image("/res/tiger.jpg")
*No absolute tiger!*
``` ```
--- ---
// Error: 23-30 cannot access file system from here // Error: 23-30 cannot access file system from here
#show it: raw as eval(it.text) #show raw: it => eval(it.text)
``` ```
#show emph as image("../../res/giraffe.jpg") #show emph: _ => image("../../res/giraffe.jpg")
_No relative giraffe!_ _No relative giraffe!_
``` ```

View File

@ -116,7 +116,7 @@
// Everything should be in smallcaps. // Everything should be in smallcaps.
#for color in (red, blue, green, yellow) [ #for color in (red, blue, green, yellow) [
#set text("Roboto") #set text("Roboto")
#wrap body in text(fill: color, body) #show it => text(fill: color, it)
#smallcaps(if color != green [ #smallcaps(if color != green [
Some Some
] else [ ] else [

View File

@ -14,7 +14,7 @@
--- ---
// Test field on node. // Test field on node.
#show node: list as { #show list: node => {
test(node.items.len(), 3) test(node.items.len(), 3)
} }
@ -32,7 +32,7 @@
--- ---
// Error: 29-32 unknown field "fun" // Error: 29-32 unknown field "fun"
#show node: heading as node.fun #show heading: node => node.fun
= A = A
--- ---

View File

@ -1,4 +1,4 @@
// Test import statements. // Test module imports.
--- ---
// Test importing semantics. // Test importing semantics.

View File

@ -1,4 +1,4 @@
// Test include statements. // Test module includes.
--- ---
#set page(width: 200pt) #set page(width: 200pt)

View File

@ -47,10 +47,10 @@
--- ---
// Outset padding. // Outset padding.
#set raw(lang: "rust") #set raw(lang: "rust")
#show node: raw as [ #show raw: it => [
#set text(8pt) #set text(8pt)
#h(5.6pt, weak: true) #h(5.6pt, weak: true)
#rect(radius: 3pt, outset: (y: 3pt, x: 2.5pt), fill: rgb(239, 241, 243), node) #rect(radius: 3pt, outset: (y: 3pt, x: 2.5pt), fill: rgb(239, 241, 243), it)
#h(5.6pt, weak: true) #h(5.6pt, weak: true)
] ]

View File

@ -37,7 +37,7 @@ No: list \
--- ---
// Test grid like show rule. // Test grid like show rule.
#show it: desc as table( #show desc: it => table(
columns: 2, columns: 2,
padding: 3pt, padding: 3pt,
..it.items.map(item => (emph(item.term), item.body)).flatten(), ..it.items.map(item => (emph(item.term), item.body)).flatten(),

View File

@ -1,7 +1,7 @@
// Test headings. // Test headings.
--- ---
#show node: heading as text(blue, node.body) #show heading: it => text(blue, it.body)
= =
No heading No heading
@ -46,8 +46,8 @@ multiline.
= Heading = Heading
#set heading(family: "Roboto", fill: eastern) #set heading(family: "Roboto", fill: eastern)
#show it: heading as it.body #show heading: it => it.body
#show it: strong as it.body + [!] #show strong: it => it.body + [!]
===== Heading 🌍 ===== Heading 🌍
#heading(level: 5)[Heading] #heading(level: 5)[Heading]

View File

@ -48,6 +48,19 @@ Hello *{x}*
+ Rhino + Rhino
+ Tiger + Tiger
---
// Test conditional set.
#show ref: it => {
set text(red) if it.target == "unknown"
it
}
@hello from the @unknown
---
// Error: 19-24 expected boolean, found integer
#set text(red) if 1 + 2
--- ---
// Error: 11-25 set is only allowed directly in code and content blocks // Error: 11-25 set is only allowed directly in code and content blocks
{ let x = set text(blue) } { let x = set text(blue) }

View File

@ -1,4 +1,4 @@
// Test wrap. // Test bare show without pattern.
--- ---
#set page(height: 130pt) #set page(height: 130pt)
@ -9,28 +9,25 @@
T. Ypst T. Ypst
] ]
#wrap body in columns(2, body) #show columns.with(2)
Great typography is at the essence of great storytelling. It is the medium that Great typography is at the essence of great storytelling. It is the medium that
transports meaning from parchment to reader, the wave that sparks a flame transports meaning from parchment to reader, the wave that sparks a flame
in booklovers and the great fulfiller of human need. in booklovers and the great fulfiller of human need.
--- ---
// Test wrap in content block. // Test bare show in content block.
A [_B #wrap c in [*#c*]; C_] D A [_B #show c => [*#c*]; C_] D
--- ---
// Test wrap style precedence. // Test style precedence.
#set text(fill: eastern, size: 1.5em) #set text(fill: eastern, size: 1.5em)
#wrap body in text(fill: forest, body) #show text.with(fill: forest)
Forest Forest
--- ---
// Ok, whatever. #show [Shown]
{ Ignored
wrap body in 2 * body
2
}
--- ---
// Error: 4-18 wrap is only allowed directly in code and content blocks // Error: 4-18 show is only allowed directly in code and content blocks
{ (wrap body in 2) * body } { (show body => 2) * body }

View File

@ -3,7 +3,7 @@
--- ---
// Override lists. // Override lists.
#set list(around: none) #set list(around: none)
#show v: list as "(" + v.items.join(", ") + ")" #show list: it => "(" + it.items.join(", ") + ")"
- A - A
- B - B
@ -14,12 +14,12 @@
--- ---
// Test full reset. // Test full reset.
#set heading(size: 1em, strong: false, around: none) #set heading(size: 1em, strong: false, around: none)
#show heading as [B] #show heading: [B]
A [= Heading] C A [= Heading] C
--- ---
// Test full removal. // Test full removal.
#show heading as [] #show heading: none
#set heading(around: none) #set heading(around: none)
Where is Where is
@ -29,13 +29,13 @@ my heading?
--- ---
// Test integrated example. // Test integrated example.
#set heading(size: 1em) #set heading(size: 1em)
#show node: heading as { #show heading: it => {
move(dy: -1pt)[📖] move(dy: -1pt)[📖]
h(5pt) h(5pt)
if node.level == 1 { if it.level == 1 {
underline(text(1.25em, blue, node.body)) underline(text(1.25em, blue, it.body))
} else { } else {
text(red, node.body) text(red, it.body)
} }
} }
@ -50,10 +50,10 @@ Another text.
--- ---
// Test set and show in code blocks. // Test set and show in code blocks.
#show node: heading as { #show heading: it => {
set text(red) set text(red)
show "ding" as [🛎] show "ding": [🛎]
node.body it.body
} }
= Heading = Heading
@ -62,12 +62,12 @@ Another text.
// Test that scoping works as expected. // Test that scoping works as expected.
{ {
let world = [ World ] let world = [ World ]
show c: "W" as strong(c) show "W": strong
world world
{ {
set text(blue) set text(blue)
wrap it in { show it => {
show "o" as "Ø" show "o": "Ø"
it it
} }
world world
@ -76,22 +76,27 @@ Another text.
} }
--- ---
#show heading as 1234 #show heading: [1234]
= Heading = Heading
--- ---
// Error: 25-29 unknown field "page" // Error: 25-29 unknown field "page"
#show it: heading as it.page #show heading: it => it.page
= Heading = Heading
--- ---
// Error: 10-15 this function cannot be customized with show // Error: 7-12 this function cannot be customized with show
#show _: upper as {} #show upper: it => {}
---
// Error: 16-20 expected content or function, found integer
#show heading: 1234
= Heading
--- ---
// Error: 7-10 expected function, string or regular expression, found color // Error: 7-10 expected function, string or regular expression, found color
#show red as [] #show red: []
--- ---
// Error: 7-27 show is only allowed directly in code and content blocks // Error: 7-25 show is only allowed directly in code and content blocks
{ 1 + show heading as none } { 1 + show heading: none }

View File

@ -2,17 +2,18 @@
--- ---
// Test basic identity. // Test basic identity.
#show it: heading as it #show heading: it => it
= Heading = Heading
--- ---
// Test more recipes down the chain. // Test more recipes down the chain.
#show it: list as scale(origin: left, x: 80%, it) #show list: scale.with(origin: left, x: 80%)
#show heading as [] #show heading: []
#show enum as [] #show enum: []
- Actual - Actual
- Tight - Tight
- List - List
= Nope
--- ---
// Test recursive base recipe. (Burn it with fire!) // Test recursive base recipe. (Burn it with fire!)
@ -23,11 +24,11 @@
--- ---
// Test show rule in function. // Test show rule in function.
#let starwars(body) = [ #let starwars(body) = [
#show v: list as { #show list: it => {
stack(dir: ltr, stack(dir: ltr,
text(red, v), text(red, it),
1fr, 1fr,
scale(x: -100%, text(blue, v)), scale(x: -100%, text(blue, it)),
) )
} }
#body #body
@ -44,8 +45,8 @@
--- ---
// Test multi-recursion with nested lists. // Test multi-recursion with nested lists.
#set rect(inset: 2pt) #set rect(inset: 2pt)
#show v: list as rect(stroke: blue, v) #show list: rect.with(stroke: blue)
#show v: list as rect(stroke: red, v) #show list: rect.with(stroke: red)
- List - List
- Nested - Nested
@ -55,8 +56,8 @@
--- ---
// Inner heading is not finalized. Bug? // Inner heading is not finalized. Bug?
#set heading(around: none) #set heading(around: none)
#show it: heading as it.body #show heading: it => it.body
#show heading as [ #show heading: [
= A [ = A [
= B = B
] ]

View File

@ -3,22 +3,22 @@
--- ---
// Test classic example. // Test classic example.
#set text("Roboto") #set text("Roboto")
#show phrase: "Der Spiegel" as smallcaps[#phrase] #show "Der Spiegel": smallcaps
Die Zeitung Der Spiegel existiert. Die Zeitung Der Spiegel existiert.
--- ---
// Another classic example. // Another classic example.
#show "TeX" as [T#h(-0.145em)#move(dy: 0.233em)[E]#h(-0.135em)X] #show "TeX": [T#h(-0.145em)#move(dy: 0.233em)[E]#h(-0.135em)X]
#show name: regex("(Lua)?(La)?TeX") as box(text("Latin Modern Roman")[#name]) #show regex("(Lua)?(La)?TeX"): name => box(text("Latin Modern Roman")[#name])
TeX, LaTeX, LuaTeX and LuaLaTeX! TeX, LaTeX, LuaTeX and LuaLaTeX!
--- ---
// Test out-of-order guarding. // Test out-of-order guarding.
#show "Good" as [Typst!] #show "Good": [Typst!]
#show "Typst" as [Fun!] #show "Typst": [Fun!]
#show "Fun" as [Good!] #show "Fun": [Good!]
#show enum as [] #show enum: []
Good \ Good \
Fun \ Fun \
@ -26,32 +26,32 @@ Typst \
--- ---
// Test that replacements happen exactly once. // Test that replacements happen exactly once.
#show "A" as [BB] #show "A": [BB]
#show "B" as [CC] #show "B": [CC]
AA (8) AA (8)
--- ---
// Test caseless match and word boundaries. // Test caseless match and word boundaries.
#show regex("(?i)\bworld\b") as [🌍] #show regex("(?i)\bworld\b"): [🌍]
Treeworld, the World of worlds, is a world. Treeworld, the World of worlds, is a world.
--- ---
// This is a fun one. // This is a fun one.
#set par(justify: true) #set par(justify: true)
#show letter: regex("\S") as rect(inset: 2pt)[#upper(letter)] #show regex("\S"): letter => rect(inset: 2pt)[#upper(letter)]
#lorem(5) #lorem(5)
--- ---
// See also: https://github.com/mTvare6/hello-world.rs // See also: https://github.com/mTvare6/hello-world.rs
#show it: regex("(?i)rust") as [#it (🚀)] #show regex("(?i)rust"): it => [#it (🚀)]
Rust is memory-safe and blazingly fast. Let's rewrite everything in rust. Rust is memory-safe and blazingly fast. Let's rewrite everything in rust.
--- ---
// Replace worlds but only in lists. // Replace worlds but only in lists.
#show node: list as [ #show list: it => [
#show "World" as [🌎] #show "World": [🌎]
#node #it
] ]
World World
@ -60,6 +60,6 @@ World
--- ---
// Test absolute path in layout phase. // Test absolute path in layout phase.
#show "GRAPH" as image("/res/graph.png") #show "GRAPH": image("/res/graph.png")
The GRAPH has nodes. The GRAPH has nodes.

View File

@ -7,6 +7,7 @@
#lorem(100) #lorem(100)
#let hi = "Hello World" #let hi = "Hello World"
#show heading: emph
``` ```
--- ---

View File

@ -142,7 +142,7 @@
"captures": { "1": { "name": "punctuation.definition.reference.typst" } } "captures": { "1": { "name": "punctuation.definition.reference.typst" } }
}, },
{ {
"begin": "(#)(let|set|show|wrap|apply|select)\\b", "begin": "(#)(let|set|show)\\b",
"end": "\n|(;)|(?=])", "end": "\n|(;)|(?=])",
"beginCaptures": { "beginCaptures": {
"0": { "name": "keyword.other.typst" }, "0": { "name": "keyword.other.typst" },
@ -253,7 +253,7 @@
}, },
{ {
"name": "keyword.other.typst", "name": "keyword.other.typst",
"match": "\\b(let|as|in|from|set|show|wrap|apply|select)\\b" "match": "\\b(let|as|in|from|set|show)\\b"
}, },
{ {
"name": "keyword.control.conditional.typst", "name": "keyword.control.conditional.typst",
@ -277,6 +277,11 @@
"name": "entity.name.function.typst", "name": "entity.name.function.typst",
"match": "\\b[[:alpha:]_][[:alnum:]_-]*!?(?=\\[|\\()" "match": "\\b[[:alpha:]_][[:alnum:]_-]*!?(?=\\[|\\()"
}, },
{
"comment": "Function name",
"name": "entity.name.function.typst",
"match": "(?<=\\bshow\\s*)\\b[[:alpha:]_][[:alnum:]_-]*(?=\\s*:)"
},
{ {
"comment": "Function arguments", "comment": "Function arguments",
"begin": "(?<=\\b[[:alpha:]_][[:alnum:]_-]*!?)\\(", "begin": "(?<=\\b[[:alpha:]_][[:alnum:]_-]*!?)\\(",

View File

@ -38,12 +38,12 @@ function activate(context) {
const rerunCmd = vscode.commands.registerCommand("ShortcutMenuBar.testRerun", () => { const rerunCmd = vscode.commands.registerCommand("ShortcutMenuBar.testRerun", () => {
const uri = vscode.window.activeTextEditor.document.uri const uri = vscode.window.activeTextEditor.document.uri
const components = uri.fsPath.split('tests') const components = uri.fsPath.split(/tests[\/\\]/)
const dir = components[0] const dir = components[0]
const subPath = components[1] const subPath = components[1]
cp.exec( cp.exec(
`cargo test --manifest-path ${dir}/Cargo.toml --test typeset ${subPath}`, `cargo test --manifest-path ${dir}/Cargo.toml --all --test tests -- ${subPath}`,
(err, stdout, stderr) => { (err, stdout, stderr) => {
console.log('Ran tests') console.log('Ran tests')
refreshPanel(stdout, stderr) refreshPanel(stdout, stderr)