/*! # Abstract Syntax Tree Interface Typst's Abstract Syntax Tree (AST) is a lazy, typed view over the untyped Concrete Syntax Tree (CST) and is rooted in the [`Markup`] node. ## The AST is a View Most AST nodes are wrapper structs around [`SyntaxNode`] pointers. This summary will use a running example of the [`Raw`] node type, which is declared (after macro expansion) as: `struct Raw<'a>(&'a SyntaxNode);`. [`SyntaxNode`]s are generated by the parser and constitute the Concrete Syntax Tree (CST). The CST is _concrete_ because it has the property that an in-order tree traversal will recreate the text of the source file exactly. [`SyntaxNode`]s in the CST contain their [`SyntaxKind`], but don't themselves provide access to the semantic meaning of their contents. That semantic meaning is available through the Abstract Syntax Tree by iterating over CST nodes and inspecting their contents. The format is prepared ahead-of-time by the parser so that this module can unpack the abstract meaning from the CST's structure. Raw nodes are parsed by recognizing paired backtick delimiters, which you will find as CST nodes with the [`RawDelim`] kind. However, the AST doesn't include these delimiters because it _abstracts_ over the backticks. Instead, the parent raw node will only use its child [`RawDelim`] CST nodes to determine whether the element is a block or inline. ## The AST is Typed AST nodes all implement the [`AstNode`] trait, but nodes can also implement their own unique methods. These unique methods are the "real" interface of the AST, and provide access to the abstract, semantic, representation of each kind of node. For example, the [`Raw`] node provides 3 methods that specify its abstract representation: [`Raw::lines()`] returns the raw text as an iterator of lines, [`Raw::lang()`] provides the optionally present [`RawLang`] language tag, and [`Raw::block()`] gives a bool for whether the raw element is a block or inline. This semantic information is unavailable in the CST. Only by converting a CST node to an AST struct will Rust let you call a method of that struct. This is a safe interface because the only way to create an AST node outside this file is to call [`AstNode::from_untyped`]. The `node!` macro implements `from_untyped` by checking the node's kind before constructing it, returning `Some()` only if the kind matches. So we know that it will have the expected children underneath, otherwise the parser wouldn't have produced this node. ## The AST is rooted in the [`Markup`] node The AST is rooted in the [`Markup`] node, which provides only one method: [`Markup::exprs`]. This returns an iterator of the main [`Expr`] enum. [`Expr`] is important because it contains the majority of expressions that Typst will evaluate. Not just markup, but also math and code expressions. Not all expression types are available from the parser at every step, but this does decrease the amount of wrapper enums needed in the AST (and this file is long enough already). Expressions also branch off into the remaining tree. You can view enums in this file as edges on a graph: areas where the tree has paths from one type to another (accessed through methods), then structs are the nodes of the graph, providing methods that return enums, etc. etc. ## The AST is Lazy Being lazy means that the untyped CST nodes are converted to typed AST nodes only as the tree is traversed. If we parse a file and a raw block is contained in a branch of an if-statement that we don't take, then we won't pay the cost of creating an iterator over the lines or checking whether it was a block or inline (although it will still be parsed into nodes). This is also a factor of the current "tree-interpreter" evaluation model. A bytecode interpreter might instead eagerly convert the AST into bytecode, but it would still traverse using this lazy interface. While the tree-interpreter evaluation is straightforward and easy to add new features onto, it has to re-traverse the AST every time a function is evaluated. A bytecode interpreter using the lazy interface would only need to traverse each node once, improving throughput at the cost of initial latency and development flexibility. */ use std::num::NonZeroUsize; use std::ops::Deref; use std::path::Path; use std::str::FromStr; use ecow::EcoString; use unscanny::Scanner; use crate::package::PackageSpec; use crate::{is_ident, is_newline, Span, SyntaxKind, SyntaxNode}; /// A typed AST node. pub trait AstNode<'a>: Sized { /// Convert a node into its typed variant. fn from_untyped(node: &'a SyntaxNode) -> Option; /// A reference to the underlying syntax node. fn to_untyped(self) -> &'a SyntaxNode; /// The source code location. fn span(self) -> Span { self.to_untyped().span() } } // A generic interface for converting untyped nodes into typed AST nodes. impl SyntaxNode { /// Whether the node can be cast to the given AST node. pub fn is<'a, T: AstNode<'a>>(&'a self) -> bool { self.cast::().is_some() } /// Try to convert the node to a typed AST node. pub fn cast<'a, T: AstNode<'a>>(&'a self) -> Option { T::from_untyped(self) } /// Find the first child that can cast to the AST type `T`. fn try_cast_first<'a, T: AstNode<'a>>(&'a self) -> Option { self.children().find_map(Self::cast) } /// Find the last child that can cast to the AST type `T`. fn try_cast_last<'a, T: AstNode<'a>>(&'a self) -> Option { self.children().rev().find_map(Self::cast) } /// Get the first child of AST type `T` or a placeholder if none. fn cast_first<'a, T: AstNode<'a> + Default>(&'a self) -> T { self.try_cast_first().unwrap_or_default() } /// Get the last child of AST type `T` or a placeholder if none. fn cast_last<'a, T: AstNode<'a> + Default>(&'a self) -> T { self.try_cast_last().unwrap_or_default() } } /// Implements [`AstNode`] for a struct whose name matches a [`SyntaxKind`] /// variant. /// /// The struct becomes a wrapper around a [`SyntaxNode`] pointer, and the /// implementation of [`AstNode::from_untyped`] checks that the pointer's kind /// matches when converting, returning `Some` or `None` respectively. /// /// The generated struct is the basis for typed accessor methods for properties /// of this AST node. For example, the [`Raw`] struct has methods for accessing /// its content by lines, its optional language tag, and whether the raw element /// is inline or a block. These methods are accessible only _after_ a /// `SyntaxNode` is coerced to the `Raw` struct type (via `from_untyped`), /// guaranteeing their implementations will work with the expected structure. macro_rules! node { ($(#[$attr:meta])* struct $name:ident) => { // Create the struct as a wrapper around a `SyntaxNode` reference. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[repr(transparent)] $(#[$attr])* pub struct $name<'a>(&'a SyntaxNode); impl<'a> AstNode<'a> for $name<'a> { #[inline] fn from_untyped(node: &'a SyntaxNode) -> Option { if node.kind() == SyntaxKind::$name { Some(Self(node)) } else { Option::None } } #[inline] fn to_untyped(self) -> &'a SyntaxNode { self.0 } } impl Default for $name<'_> { #[inline] fn default() -> Self { static PLACEHOLDER: SyntaxNode = SyntaxNode::placeholder(SyntaxKind::$name); Self(&PLACEHOLDER) } } }; } node! { /// The syntactical root capable of representing a full parsed document. struct Markup } impl<'a> Markup<'a> { /// The expressions. pub fn exprs(self) -> impl DoubleEndedIterator> { let mut was_stmt = false; self.0 .children() .filter(move |node| { // Ignore newline directly after statements without semicolons. let kind = node.kind(); let keep = !was_stmt || node.kind() != SyntaxKind::Space; was_stmt = kind.is_stmt(); keep }) .filter_map(Expr::cast_with_space) } } /// An expression in markup, math or code. #[derive(Debug, Copy, Clone, Hash)] pub enum Expr<'a> { /// Plain text without markup. Text(Text<'a>), /// Whitespace in markup or math. Has at most one newline in markup, as more /// indicate a paragraph break. Space(Space<'a>), /// A forced line break: `\`. Linebreak(Linebreak<'a>), /// A paragraph break, indicated by one or multiple blank lines. Parbreak(Parbreak<'a>), /// An escape sequence: `\#`, `\u{1F5FA}`. Escape(Escape<'a>), /// A shorthand for a unicode codepoint. For example, `~` for non-breaking /// space or `-?` for a soft hyphen. Shorthand(Shorthand<'a>), /// A smart quote: `'` or `"`. SmartQuote(SmartQuote<'a>), /// Strong content: `*Strong*`. Strong(Strong<'a>), /// Emphasized content: `_Emphasized_`. Emph(Emph<'a>), /// Raw text with optional syntax highlighting: `` `...` ``. Raw(Raw<'a>), /// A hyperlink: `https://typst.org`. Link(Link<'a>), /// A label: ``. Label(Label<'a>), /// A reference: `@target`, `@target[..]`. Ref(Ref<'a>), /// A section heading: `= Introduction`. Heading(Heading<'a>), /// An item in a bullet list: `- ...`. ListItem(ListItem<'a>), /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. EnumItem(EnumItem<'a>), /// An item in a term list: `/ Term: Details`. TermItem(TermItem<'a>), /// A mathematical equation: `$x$`, `$ x^2 $`. Equation(Equation<'a>), /// The contents of a mathematical equation: `x^2 + 1`. Math(Math<'a>), /// A lone text fragment in math: `x`, `25`, `3.1415`, `=`, `[`. MathText(MathText<'a>), /// An identifier in math: `pi`. MathIdent(MathIdent<'a>), /// A shorthand for a unicode codepoint in math: `a <= b`. MathShorthand(MathShorthand<'a>), /// An alignment point in math: `&`. MathAlignPoint(MathAlignPoint<'a>), /// Matched delimiters in math: `[x + y]`. MathDelimited(MathDelimited<'a>), /// A base with optional attachments in math: `a_1^2`. MathAttach(MathAttach<'a>), /// Grouped math primes MathPrimes(MathPrimes<'a>), /// A fraction in math: `x/2`. MathFrac(MathFrac<'a>), /// A root in math: `√x`, `∛x` or `∜x`. MathRoot(MathRoot<'a>), /// An identifier: `left`. Ident(Ident<'a>), /// The `none` literal. None(None<'a>), /// The `auto` literal. Auto(Auto<'a>), /// A boolean: `true`, `false`. Bool(Bool<'a>), /// An integer: `120`. Int(Int<'a>), /// A floating-point number: `1.2`, `10e-4`. Float(Float<'a>), /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`. Numeric(Numeric<'a>), /// A quoted string: `"..."`. Str(Str<'a>), /// A code block: `{ let x = 1; x + 2 }`. CodeBlock(CodeBlock<'a>), /// A content block: `[*Hi* there!]`. ContentBlock(ContentBlock<'a>), /// A grouped expression: `(1 + 2)`. Parenthesized(Parenthesized<'a>), /// An array: `(1, "hi", 12cm)`. Array(Array<'a>), /// A dictionary: `(thickness: 3pt, dash: "solid")`. Dict(Dict<'a>), /// A unary operation: `-x`. Unary(Unary<'a>), /// A binary operation: `a + b`. Binary(Binary<'a>), /// A field access: `properties.age`. FieldAccess(FieldAccess<'a>), /// An invocation of a function or method: `f(x, y)`. FuncCall(FuncCall<'a>), /// A closure: `(x, y) => z`. Closure(Closure<'a>), /// A let binding: `let x = 1`. LetBinding(LetBinding<'a>), /// A destructuring assignment: `(x, y) = (1, 2)`. DestructAssignment(DestructAssignment<'a>), /// A set rule: `set text(...)`. SetRule(SetRule<'a>), /// A show rule: `show heading: it => emph(it.body)`. ShowRule(ShowRule<'a>), /// A contextual expression: `context text.lang`. Contextual(Contextual<'a>), /// An if-else conditional: `if x { y } else { z }`. Conditional(Conditional<'a>), /// A while loop: `while x { y }`. WhileLoop(WhileLoop<'a>), /// A for loop: `for x in y { z }`. ForLoop(ForLoop<'a>), /// A module import: `import "utils.typ": a, b, c`. ModuleImport(ModuleImport<'a>), /// A module include: `include "chapter1.typ"`. ModuleInclude(ModuleInclude<'a>), /// A break from a loop: `break`. LoopBreak(LoopBreak<'a>), /// A continue in a loop: `continue`. LoopContinue(LoopContinue<'a>), /// A return from a function: `return`, `return x + 1`. FuncReturn(FuncReturn<'a>), } impl<'a> Expr<'a> { fn cast_with_space(node: &'a SyntaxNode) -> Option { match node.kind() { SyntaxKind::Space => Some(Self::Space(Space(node))), _ => Self::from_untyped(node), } } } impl<'a> AstNode<'a> for Expr<'a> { fn from_untyped(node: &'a SyntaxNode) -> Option { match node.kind() { SyntaxKind::Space => Option::None, // Skipped unless using `cast_with_space`. SyntaxKind::Linebreak => Some(Self::Linebreak(Linebreak(node))), SyntaxKind::Parbreak => Some(Self::Parbreak(Parbreak(node))), SyntaxKind::Text => Some(Self::Text(Text(node))), SyntaxKind::Escape => Some(Self::Escape(Escape(node))), SyntaxKind::Shorthand => Some(Self::Shorthand(Shorthand(node))), SyntaxKind::SmartQuote => Some(Self::SmartQuote(SmartQuote(node))), SyntaxKind::Strong => Some(Self::Strong(Strong(node))), SyntaxKind::Emph => Some(Self::Emph(Emph(node))), SyntaxKind::Raw => Some(Self::Raw(Raw(node))), SyntaxKind::Link => Some(Self::Link(Link(node))), SyntaxKind::Label => Some(Self::Label(Label(node))), SyntaxKind::Ref => Some(Self::Ref(Ref(node))), SyntaxKind::Heading => Some(Self::Heading(Heading(node))), SyntaxKind::ListItem => Some(Self::ListItem(ListItem(node))), SyntaxKind::EnumItem => Some(Self::EnumItem(EnumItem(node))), SyntaxKind::TermItem => Some(Self::TermItem(TermItem(node))), SyntaxKind::Equation => Some(Self::Equation(Equation(node))), SyntaxKind::Math => Some(Self::Math(Math(node))), SyntaxKind::MathText => Some(Self::MathText(MathText(node))), SyntaxKind::MathIdent => Some(Self::MathIdent(MathIdent(node))), SyntaxKind::MathShorthand => Some(Self::MathShorthand(MathShorthand(node))), SyntaxKind::MathAlignPoint => { Some(Self::MathAlignPoint(MathAlignPoint(node))) } SyntaxKind::MathDelimited => Some(Self::MathDelimited(MathDelimited(node))), SyntaxKind::MathAttach => Some(Self::MathAttach(MathAttach(node))), SyntaxKind::MathPrimes => Some(Self::MathPrimes(MathPrimes(node))), SyntaxKind::MathFrac => Some(Self::MathFrac(MathFrac(node))), SyntaxKind::MathRoot => Some(Self::MathRoot(MathRoot(node))), SyntaxKind::Ident => Some(Self::Ident(Ident(node))), SyntaxKind::None => Some(Self::None(None(node))), SyntaxKind::Auto => Some(Self::Auto(Auto(node))), SyntaxKind::Bool => Some(Self::Bool(Bool(node))), SyntaxKind::Int => Some(Self::Int(Int(node))), SyntaxKind::Float => Some(Self::Float(Float(node))), SyntaxKind::Numeric => Some(Self::Numeric(Numeric(node))), SyntaxKind::Str => Some(Self::Str(Str(node))), SyntaxKind::CodeBlock => Some(Self::CodeBlock(CodeBlock(node))), SyntaxKind::ContentBlock => Some(Self::ContentBlock(ContentBlock(node))), SyntaxKind::Parenthesized => Some(Self::Parenthesized(Parenthesized(node))), SyntaxKind::Array => Some(Self::Array(Array(node))), SyntaxKind::Dict => Some(Self::Dict(Dict(node))), SyntaxKind::Unary => Some(Self::Unary(Unary(node))), SyntaxKind::Binary => Some(Self::Binary(Binary(node))), SyntaxKind::FieldAccess => Some(Self::FieldAccess(FieldAccess(node))), SyntaxKind::FuncCall => Some(Self::FuncCall(FuncCall(node))), SyntaxKind::Closure => Some(Self::Closure(Closure(node))), SyntaxKind::LetBinding => Some(Self::LetBinding(LetBinding(node))), SyntaxKind::DestructAssignment => { Some(Self::DestructAssignment(DestructAssignment(node))) } SyntaxKind::SetRule => Some(Self::SetRule(SetRule(node))), SyntaxKind::ShowRule => Some(Self::ShowRule(ShowRule(node))), SyntaxKind::Contextual => Some(Self::Contextual(Contextual(node))), SyntaxKind::Conditional => Some(Self::Conditional(Conditional(node))), SyntaxKind::WhileLoop => Some(Self::WhileLoop(WhileLoop(node))), SyntaxKind::ForLoop => Some(Self::ForLoop(ForLoop(node))), SyntaxKind::ModuleImport => Some(Self::ModuleImport(ModuleImport(node))), SyntaxKind::ModuleInclude => Some(Self::ModuleInclude(ModuleInclude(node))), SyntaxKind::LoopBreak => Some(Self::LoopBreak(LoopBreak(node))), SyntaxKind::LoopContinue => Some(Self::LoopContinue(LoopContinue(node))), SyntaxKind::FuncReturn => Some(Self::FuncReturn(FuncReturn(node))), _ => Option::None, } } fn to_untyped(self) -> &'a SyntaxNode { match self { Self::Text(v) => v.to_untyped(), Self::Space(v) => v.to_untyped(), Self::Linebreak(v) => v.to_untyped(), Self::Parbreak(v) => v.to_untyped(), Self::Escape(v) => v.to_untyped(), Self::Shorthand(v) => v.to_untyped(), Self::SmartQuote(v) => v.to_untyped(), Self::Strong(v) => v.to_untyped(), Self::Emph(v) => v.to_untyped(), Self::Raw(v) => v.to_untyped(), Self::Link(v) => v.to_untyped(), Self::Label(v) => v.to_untyped(), Self::Ref(v) => v.to_untyped(), Self::Heading(v) => v.to_untyped(), Self::ListItem(v) => v.to_untyped(), Self::EnumItem(v) => v.to_untyped(), Self::TermItem(v) => v.to_untyped(), Self::Equation(v) => v.to_untyped(), Self::Math(v) => v.to_untyped(), Self::MathText(v) => v.to_untyped(), Self::MathIdent(v) => v.to_untyped(), Self::MathShorthand(v) => v.to_untyped(), Self::MathAlignPoint(v) => v.to_untyped(), Self::MathDelimited(v) => v.to_untyped(), Self::MathAttach(v) => v.to_untyped(), Self::MathPrimes(v) => v.to_untyped(), Self::MathFrac(v) => v.to_untyped(), Self::MathRoot(v) => v.to_untyped(), Self::Ident(v) => v.to_untyped(), Self::None(v) => v.to_untyped(), Self::Auto(v) => v.to_untyped(), Self::Bool(v) => v.to_untyped(), Self::Int(v) => v.to_untyped(), Self::Float(v) => v.to_untyped(), Self::Numeric(v) => v.to_untyped(), Self::Str(v) => v.to_untyped(), Self::CodeBlock(v) => v.to_untyped(), Self::ContentBlock(v) => v.to_untyped(), Self::Array(v) => v.to_untyped(), Self::Dict(v) => v.to_untyped(), Self::Parenthesized(v) => v.to_untyped(), Self::Unary(v) => v.to_untyped(), Self::Binary(v) => v.to_untyped(), Self::FieldAccess(v) => v.to_untyped(), Self::FuncCall(v) => v.to_untyped(), Self::Closure(v) => v.to_untyped(), Self::LetBinding(v) => v.to_untyped(), Self::DestructAssignment(v) => v.to_untyped(), Self::SetRule(v) => v.to_untyped(), Self::ShowRule(v) => v.to_untyped(), Self::Contextual(v) => v.to_untyped(), Self::Conditional(v) => v.to_untyped(), Self::WhileLoop(v) => v.to_untyped(), Self::ForLoop(v) => v.to_untyped(), Self::ModuleImport(v) => v.to_untyped(), Self::ModuleInclude(v) => v.to_untyped(), Self::LoopBreak(v) => v.to_untyped(), Self::LoopContinue(v) => v.to_untyped(), Self::FuncReturn(v) => v.to_untyped(), } } } impl Expr<'_> { /// Can this expression be embedded into markup with a hash? pub fn hash(self) -> bool { matches!( self, Self::Ident(_) | Self::None(_) | Self::Auto(_) | Self::Bool(_) | Self::Int(_) | Self::Float(_) | Self::Numeric(_) | Self::Str(_) | Self::CodeBlock(_) | Self::ContentBlock(_) | Self::Array(_) | Self::Dict(_) | Self::Parenthesized(_) | Self::FieldAccess(_) | Self::FuncCall(_) | Self::LetBinding(_) | Self::SetRule(_) | Self::ShowRule(_) | Self::Contextual(_) | Self::Conditional(_) | Self::WhileLoop(_) | Self::ForLoop(_) | Self::ModuleImport(_) | Self::ModuleInclude(_) | Self::LoopBreak(_) | Self::LoopContinue(_) | Self::FuncReturn(_) ) } /// Is this a literal? pub fn is_literal(self) -> bool { matches!( self, Self::None(_) | Self::Auto(_) | Self::Bool(_) | Self::Int(_) | Self::Float(_) | Self::Numeric(_) | Self::Str(_) ) } } impl Default for Expr<'_> { fn default() -> Self { Expr::None(None::default()) } } node! { /// Plain text without markup. struct Text } impl<'a> Text<'a> { /// Get the text. pub fn get(self) -> &'a EcoString { self.0.text() } } node! { /// Whitespace in markup or math. Has at most one newline in markup, as more /// indicate a paragraph break. struct Space } node! { /// A forced line break: `\`. struct Linebreak } node! { /// A paragraph break, indicated by one or multiple blank lines. struct Parbreak } node! { /// An escape sequence: `\#`, `\u{1F5FA}`. struct Escape } impl Escape<'_> { /// Get the escaped character. pub fn get(self) -> char { let mut s = Scanner::new(self.0.text()); s.expect('\\'); if s.eat_if("u{") { let hex = s.eat_while(char::is_ascii_hexdigit); u32::from_str_radix(hex, 16) .ok() .and_then(std::char::from_u32) .unwrap_or_default() } else { s.eat().unwrap_or_default() } } } node! { /// A shorthand for a unicode codepoint. For example, `~` for a non-breaking /// space or `-?` for a soft hyphen. struct Shorthand } impl Shorthand<'_> { /// A list of all shorthands in markup mode. pub const LIST: &'static [(&'static str, char)] = &[ ("...", '…'), ("~", '\u{00A0}'), ("-", '\u{2212}'), // Only before a digit ("--", '\u{2013}'), ("---", '\u{2014}'), ("-?", '\u{00AD}'), ]; /// Get the shorthanded character. pub fn get(self) -> char { let text = self.0.text(); Self::LIST .iter() .find(|&&(s, _)| s == text) .map_or_else(char::default, |&(_, c)| c) } } node! { /// A smart quote: `'` or `"`. struct SmartQuote } impl SmartQuote<'_> { /// Whether this is a double quote. pub fn double(self) -> bool { self.0.text() == "\"" } } node! { /// Strong content: `*Strong*`. struct Strong } impl<'a> Strong<'a> { /// The contents of the strong node. pub fn body(self) -> Markup<'a> { self.0.cast_first() } } node! { /// Emphasized content: `_Emphasized_`. struct Emph } impl<'a> Emph<'a> { /// The contents of the emphasis node. pub fn body(self) -> Markup<'a> { self.0.cast_first() } } node! { /// Raw text with optional syntax highlighting: `` `...` ``. struct Raw } impl<'a> Raw<'a> { /// The lines in the raw block. pub fn lines(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(SyntaxNode::cast) } /// An optional identifier specifying the language to syntax-highlight in. pub fn lang(self) -> Option> { // Only blocky literals are supposed to contain a language. let delim: RawDelim = self.0.try_cast_first()?; if delim.0.len() < 3 { return Option::None; } self.0.try_cast_first() } /// Whether the raw text should be displayed in a separate block. pub fn block(self) -> bool { self.0 .try_cast_first() .is_some_and(|delim: RawDelim| delim.0.len() >= 3) && self.0.children().any(|e| { e.kind() == SyntaxKind::RawTrimmed && e.text().chars().any(is_newline) }) } } node! { /// A language tag at the start of raw element: ``typ ``. struct RawLang } impl<'a> RawLang<'a> { /// Get the language tag. pub fn get(self) -> &'a EcoString { self.0.text() } } node! { /// A raw delimiter in single or 3+ backticks: `` ` ``. struct RawDelim } node! { /// A hyperlink: `https://typst.org`. struct Link } impl<'a> Link<'a> { /// Get the URL. pub fn get(self) -> &'a EcoString { self.0.text() } } node! { /// A label: ``. struct Label } impl<'a> Label<'a> { /// Get the label's text. pub fn get(self) -> &'a str { self.0.text().trim_start_matches('<').trim_end_matches('>') } } node! { /// A reference: `@target`, `@target[..]`. struct Ref } impl<'a> Ref<'a> { /// Get the target. pub fn target(self) -> &'a str { self.0 .children() .find(|node| node.kind() == SyntaxKind::RefMarker) .map(|node| node.text().trim_start_matches('@')) .unwrap_or_default() } /// Get the supplement. pub fn supplement(self) -> Option> { self.0.try_cast_last() } } node! { /// A section heading: `= Introduction`. struct Heading } impl<'a> Heading<'a> { /// The contents of the heading. pub fn body(self) -> Markup<'a> { self.0.cast_first() } /// The section depth (number of equals signs). pub fn depth(self) -> NonZeroUsize { self.0 .children() .find(|node| node.kind() == SyntaxKind::HeadingMarker) .and_then(|node| node.len().try_into().ok()) .unwrap_or(NonZeroUsize::new(1).unwrap()) } } node! { /// An item in a bullet list: `- ...`. struct ListItem } impl<'a> ListItem<'a> { /// The contents of the list item. pub fn body(self) -> Markup<'a> { self.0.cast_first() } } node! { /// An item in an enumeration (numbered list): `+ ...` or `1. ...`. struct EnumItem } impl<'a> EnumItem<'a> { /// The explicit numbering, if any: `23.`. pub fn number(self) -> Option { self.0.children().find_map(|node| match node.kind() { SyntaxKind::EnumMarker => node.text().trim_end_matches('.').parse().ok(), _ => Option::None, }) } /// The contents of the list item. pub fn body(self) -> Markup<'a> { self.0.cast_first() } } node! { /// An item in a term list: `/ Term: Details`. struct TermItem } impl<'a> TermItem<'a> { /// The term described by the item. pub fn term(self) -> Markup<'a> { self.0.cast_first() } /// The description of the term. pub fn description(self) -> Markup<'a> { self.0.cast_last() } } node! { /// A mathematical equation: `$x$`, `$ x^2 $`. struct Equation } impl<'a> Equation<'a> { /// The contained math. pub fn body(self) -> Math<'a> { self.0.cast_first() } /// Whether the equation should be displayed as a separate block. pub fn block(self) -> bool { let is_space = |node: Option<&SyntaxNode>| { node.map(SyntaxNode::kind) == Some(SyntaxKind::Space) }; is_space(self.0.children().nth(1)) && is_space(self.0.children().nth_back(1)) } } node! { /// The contents of a mathematical equation: `x^2 + 1`. struct Math } impl<'a> Math<'a> { /// The expressions the mathematical content consists of. pub fn exprs(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(Expr::cast_with_space) } } node! { /// A lone text fragment in math: `x`, `25`, `3.1415`, `=`, `[`. struct MathText } /// The underlying text kind. pub enum MathTextKind<'a> { Character(char), Number(&'a EcoString), } impl<'a> MathText<'a> { /// Return the underlying text. pub fn get(self) -> MathTextKind<'a> { let text = self.0.text(); let mut chars = text.chars(); let c = chars.next().unwrap(); if c.is_numeric() { // Numbers are potentially grouped as multiple characters. This is // done in `Lexer::math_text()`. MathTextKind::Number(text) } else { assert!(chars.next().is_none()); MathTextKind::Character(c) } } } node! { /// An identifier in math: `pi`. struct MathIdent } impl<'a> MathIdent<'a> { /// Get the identifier. pub fn get(self) -> &'a EcoString { self.0.text() } /// Get the identifier as a string slice. pub fn as_str(self) -> &'a str { self.get() } } impl Deref for MathIdent<'_> { type Target = str; /// Dereference to a string. Note that this shortens the lifetime, so you /// may need to use [`get()`](Self::get) instead in some situations. fn deref(&self) -> &Self::Target { self.as_str() } } node! { /// A shorthand for a unicode codepoint in math: `a <= b`. struct MathShorthand } impl MathShorthand<'_> { /// A list of all shorthands in math mode. pub const LIST: &'static [(&'static str, char)] = &[ ("...", '…'), ("-", '−'), ("*", '∗'), ("~", '∼'), ("!=", '≠'), (":=", '≔'), ("::=", '⩴'), ("=:", '≕'), ("<<", '≪'), ("<<<", '⋘'), (">>", '≫'), (">>>", '⋙'), ("<=", '≤'), (">=", '≥'), ("->", '→'), ("-->", '⟶'), ("|->", '↦'), (">->", '↣'), ("->>", '↠'), ("<-", '←'), ("<--", '⟵'), ("<-<", '↢'), ("<<-", '↞'), ("<->", '↔'), ("<-->", '⟷'), ("~>", '⇝'), ("~~>", '⟿'), ("<~", '⇜'), ("<~~", '⬳'), ("=>", '⇒'), ("|=>", '⤇'), ("==>", '⟹'), ("<==", '⟸'), ("<=>", '⇔'), ("<==>", '⟺'), ("[|", '⟦'), ("|]", '⟧'), ("||", '‖'), ]; /// Get the shorthanded character. pub fn get(self) -> char { let text = self.0.text(); Self::LIST .iter() .find(|&&(s, _)| s == text) .map_or_else(char::default, |&(_, c)| c) } } node! { /// An alignment point in math: `&`. struct MathAlignPoint } node! { /// Matched delimiters in math: `[x + y]`. struct MathDelimited } impl<'a> MathDelimited<'a> { /// The opening delimiter. pub fn open(self) -> Expr<'a> { self.0.cast_first() } /// The contents, including the delimiters. pub fn body(self) -> Math<'a> { self.0.cast_first() } /// The closing delimiter. pub fn close(self) -> Expr<'a> { self.0.cast_last() } } node! { /// A base with optional attachments in math: `a_1^2`. struct MathAttach } impl<'a> MathAttach<'a> { /// The base, to which things are attached. pub fn base(self) -> Expr<'a> { self.0.cast_first() } /// The bottom attachment. pub fn bottom(self) -> Option> { self.0 .children() .skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore)) .find_map(SyntaxNode::cast) } /// The top attachment. pub fn top(self) -> Option> { self.0 .children() .skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat)) .find_map(SyntaxNode::cast) } /// Extract attached primes if present. pub fn primes(self) -> Option> { self.0 .children() .skip_while(|node| node.cast::>().is_none()) .nth(1) .and_then(|n| n.cast()) } } node! { /// Grouped primes in math: `a'''`. struct MathPrimes } impl MathPrimes<'_> { /// The number of grouped primes. pub fn count(self) -> usize { self.0 .children() .filter(|node| matches!(node.kind(), SyntaxKind::Prime)) .count() } } node! { /// A fraction in math: `x/2` struct MathFrac } impl<'a> MathFrac<'a> { /// The numerator. pub fn num(self) -> Expr<'a> { self.0.cast_first() } /// The denominator. pub fn denom(self) -> Expr<'a> { self.0.cast_last() } } node! { /// A root in math: `√x`, `∛x` or `∜x`. struct MathRoot } impl<'a> MathRoot<'a> { /// The index of the root. pub fn index(self) -> Option { match self.0.children().next().map(|node| node.text().as_str()) { Some("∜") => Some(4), Some("∛") => Some(3), Some("√") => Option::None, _ => Option::None, } } /// The radicand. pub fn radicand(self) -> Expr<'a> { self.0.cast_first() } } node! { /// An identifier: `it`. struct Ident } impl<'a> Ident<'a> { /// Get the identifier. pub fn get(self) -> &'a EcoString { self.0.text() } /// Get the identifier as a string slice. pub fn as_str(self) -> &'a str { self.get() } } impl Deref for Ident<'_> { type Target = str; /// Dereference to a string. Note that this shortens the lifetime, so you /// may need to use [`get()`](Self::get) instead in some situations. fn deref(&self) -> &Self::Target { self.as_str() } } node! { /// The `none` literal. struct None } node! { /// The `auto` literal. struct Auto } node! { /// A boolean: `true`, `false`. struct Bool } impl Bool<'_> { /// Get the boolean value. pub fn get(self) -> bool { self.0.text() == "true" } } node! { /// An integer: `120`. struct Int } impl Int<'_> { /// Get the integer value. pub fn get(self) -> i64 { let text = self.0.text(); if let Some(rest) = text.strip_prefix("0x") { i64::from_str_radix(rest, 16) } else if let Some(rest) = text.strip_prefix("0o") { i64::from_str_radix(rest, 8) } else if let Some(rest) = text.strip_prefix("0b") { i64::from_str_radix(rest, 2) } else { text.parse() } .unwrap_or_default() } } node! { /// A floating-point number: `1.2`, `10e-4`. struct Float } impl Float<'_> { /// Get the floating-point value. pub fn get(self) -> f64 { self.0.text().parse().unwrap_or_default() } } node! { /// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`. struct Numeric } impl Numeric<'_> { /// Get the numeric value and unit. pub fn get(self) -> (f64, Unit) { let text = self.0.text(); let count = text .chars() .rev() .take_while(|c| matches!(c, 'a'..='z' | '%')) .count(); let split = text.len() - count; let value = text[..split].parse().unwrap_or_default(); let unit = match &text[split..] { "pt" => Unit::Pt, "mm" => Unit::Mm, "cm" => Unit::Cm, "in" => Unit::In, "deg" => Unit::Deg, "rad" => Unit::Rad, "em" => Unit::Em, "fr" => Unit::Fr, "%" => Unit::Percent, _ => Unit::Percent, }; (value, unit) } } /// Unit of a numeric value. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Unit { /// Points. Pt, /// Millimeters. Mm, /// Centimeters. Cm, /// Inches. In, /// Radians. Rad, /// Degrees. Deg, /// Font-relative: `1em` is the same as the font size. Em, /// Fractions: `fr`. Fr, /// Percentage: `%`. Percent, } node! { /// A quoted string: `"..."`. struct Str } impl Str<'_> { /// Get the string value with resolved escape sequences. pub fn get(self) -> EcoString { let text = self.0.text(); let unquoted = &text[1..text.len() - 1]; if !unquoted.contains('\\') { return unquoted.into(); } let mut out = EcoString::with_capacity(unquoted.len()); let mut s = Scanner::new(unquoted); while let Some(c) = s.eat() { if c != '\\' { out.push(c); continue; } let start = s.locate(-1); match s.eat() { Some('\\') => out.push('\\'), Some('"') => out.push('"'), Some('n') => out.push('\n'), Some('r') => out.push('\r'), Some('t') => out.push('\t'), Some('u') if s.eat_if('{') => { let sequence = s.eat_while(char::is_ascii_hexdigit); s.eat_if('}'); match u32::from_str_radix(sequence, 16) .ok() .and_then(std::char::from_u32) { Some(c) => out.push(c), Option::None => out.push_str(s.from(start)), } } _ => out.push_str(s.from(start)), } } out } } node! { /// A code block: `{ let x = 1; x + 2 }`. struct CodeBlock } impl<'a> CodeBlock<'a> { /// The contained code. pub fn body(self) -> Code<'a> { self.0.cast_first() } } node! { /// The body of a code block. struct Code } impl<'a> Code<'a> { /// The list of expressions contained in the code. pub fn exprs(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(SyntaxNode::cast) } } node! { /// A content block: `[*Hi* there!]`. struct ContentBlock } impl<'a> ContentBlock<'a> { /// The contained markup. pub fn body(self) -> Markup<'a> { self.0.cast_first() } } node! { /// A grouped expression: `(1 + 2)`. struct Parenthesized } impl<'a> Parenthesized<'a> { /// The wrapped expression. /// /// Should only be accessed if this is contained in an `Expr`. pub fn expr(self) -> Expr<'a> { self.0.cast_first() } /// The wrapped pattern. /// /// Should only be accessed if this is contained in a `Pattern`. pub fn pattern(self) -> Pattern<'a> { self.0.cast_first() } } node! { /// An array: `(1, "hi", 12cm)`. struct Array } impl<'a> Array<'a> { /// The array's items. pub fn items(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(SyntaxNode::cast) } } /// An item in an array. #[derive(Debug, Copy, Clone, Hash)] pub enum ArrayItem<'a> { /// A bare expression: `12`. Pos(Expr<'a>), /// A spread expression: `..things`. Spread(Spread<'a>), } impl<'a> AstNode<'a> for ArrayItem<'a> { fn from_untyped(node: &'a SyntaxNode) -> Option { match node.kind() { SyntaxKind::Spread => Some(Self::Spread(Spread(node))), _ => node.cast().map(Self::Pos), } } fn to_untyped(self) -> &'a SyntaxNode { match self { Self::Pos(v) => v.to_untyped(), Self::Spread(v) => v.to_untyped(), } } } node! { /// A dictionary: `(thickness: 3pt, dash: "solid")`. struct Dict } impl<'a> Dict<'a> { /// The dictionary's items. pub fn items(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(SyntaxNode::cast) } } /// An item in an dictionary expression. #[derive(Debug, Copy, Clone, Hash)] pub enum DictItem<'a> { /// A named pair: `thickness: 3pt`. Named(Named<'a>), /// A keyed pair: `"spacy key": true`. Keyed(Keyed<'a>), /// A spread expression: `..things`. Spread(Spread<'a>), } impl<'a> AstNode<'a> for DictItem<'a> { fn from_untyped(node: &'a SyntaxNode) -> Option { match node.kind() { SyntaxKind::Named => Some(Self::Named(Named(node))), SyntaxKind::Keyed => Some(Self::Keyed(Keyed(node))), SyntaxKind::Spread => Some(Self::Spread(Spread(node))), _ => Option::None, } } fn to_untyped(self) -> &'a SyntaxNode { match self { Self::Named(v) => v.to_untyped(), Self::Keyed(v) => v.to_untyped(), Self::Spread(v) => v.to_untyped(), } } } node! { /// A named pair: `thickness: 3pt`. struct Named } impl<'a> Named<'a> { /// The name: `thickness`. pub fn name(self) -> Ident<'a> { self.0.cast_first() } /// The right-hand side of the pair: `3pt`. /// /// This should only be accessed if this `Named` is contained in a /// `DictItem`, `Arg`, or `Param`. pub fn expr(self) -> Expr<'a> { self.0.cast_last() } /// The right-hand side of the pair as a pattern. /// /// This should only be accessed if this `Named` is contained in a /// `Destructuring`. pub fn pattern(self) -> Pattern<'a> { self.0.cast_last() } } node! { /// A keyed pair: `"spacy key": true`. struct Keyed } impl<'a> Keyed<'a> { /// The key: `"spacy key"`. pub fn key(self) -> Expr<'a> { self.0.cast_first() } /// The right-hand side of the pair: `true`. /// /// This should only be accessed if this `Keyed` is contained in a /// `DictItem`. pub fn expr(self) -> Expr<'a> { self.0.cast_last() } } node! { /// A spread: `..x` or `..x.at(0)`. struct Spread } impl<'a> Spread<'a> { /// The spread expression. /// /// This should only be accessed if this `Spread` is contained in an /// `ArrayItem`, `DictItem`, or `Arg`. pub fn expr(self) -> Expr<'a> { self.0.cast_first() } /// The sink identifier, if present. /// /// This should only be accessed if this `Spread` is contained in a /// `Param` or binding `DestructuringItem`. pub fn sink_ident(self) -> Option> { self.0.try_cast_first() } /// The sink expressions, if present. /// /// This should only be accessed if this `Spread` is contained in a /// `DestructuringItem`. pub fn sink_expr(self) -> Option> { self.0.try_cast_first() } } node! { /// A unary operation: `-x`. struct Unary } impl<'a> Unary<'a> { /// The operator: `-`. pub fn op(self) -> UnOp { self.0 .children() .find_map(|node| UnOp::from_kind(node.kind())) .unwrap_or(UnOp::Pos) } /// The expression to operate on: `x`. pub fn expr(self) -> Expr<'a> { self.0.cast_last() } } /// A unary operator. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum UnOp { /// The plus operator: `+`. Pos, /// The negation operator: `-`. Neg, /// The boolean `not`. Not, } impl UnOp { /// Try to convert the token into a unary operation. pub fn from_kind(token: SyntaxKind) -> Option { Some(match token { SyntaxKind::Plus => Self::Pos, SyntaxKind::Minus => Self::Neg, SyntaxKind::Not => Self::Not, _ => return Option::None, }) } /// The precedence of this operator. pub fn precedence(self) -> usize { match self { Self::Pos | Self::Neg => 7, Self::Not => 4, } } /// The string representation of this operation. pub fn as_str(self) -> &'static str { match self { Self::Pos => "+", Self::Neg => "-", Self::Not => "not", } } } node! { /// A binary operation: `a + b`. struct Binary } impl<'a> Binary<'a> { /// The binary operator: `+`. pub fn op(self) -> BinOp { let mut not = false; self.0 .children() .find_map(|node| match node.kind() { SyntaxKind::Not => { not = true; Option::None } SyntaxKind::In if not => Some(BinOp::NotIn), _ => BinOp::from_kind(node.kind()), }) .unwrap_or(BinOp::Add) } /// The left-hand side of the operation: `a`. pub fn lhs(self) -> Expr<'a> { self.0.cast_first() } /// The right-hand side of the operation: `b`. pub fn rhs(self) -> Expr<'a> { self.0.cast_last() } } /// A binary operator. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum BinOp { /// The addition operator: `+`. Add, /// The subtraction operator: `-`. Sub, /// The multiplication operator: `*`. Mul, /// The division operator: `/`. Div, /// The short-circuiting boolean `and`. And, /// The short-circuiting boolean `or`. Or, /// The equality operator: `==`. Eq, /// The inequality operator: `!=`. Neq, /// The less-than operator: `<`. Lt, /// The less-than or equal operator: `<=`. Leq, /// The greater-than operator: `>`. Gt, /// The greater-than or equal operator: `>=`. Geq, /// The assignment operator: `=`. Assign, /// The containment operator: `in`. In, /// The inverse containment operator: `not in`. NotIn, /// The add-assign operator: `+=`. AddAssign, /// The subtract-assign operator: `-=`. SubAssign, /// The multiply-assign operator: `*=`. MulAssign, /// The divide-assign operator: `/=`. DivAssign, } impl BinOp { /// Try to convert the token into a binary operation. pub fn from_kind(token: SyntaxKind) -> Option { Some(match token { SyntaxKind::Plus => Self::Add, SyntaxKind::Minus => Self::Sub, SyntaxKind::Star => Self::Mul, SyntaxKind::Slash => Self::Div, SyntaxKind::And => Self::And, SyntaxKind::Or => Self::Or, SyntaxKind::EqEq => Self::Eq, SyntaxKind::ExclEq => Self::Neq, SyntaxKind::Lt => Self::Lt, SyntaxKind::LtEq => Self::Leq, SyntaxKind::Gt => Self::Gt, SyntaxKind::GtEq => Self::Geq, SyntaxKind::Eq => Self::Assign, SyntaxKind::In => Self::In, SyntaxKind::PlusEq => Self::AddAssign, SyntaxKind::HyphEq => Self::SubAssign, SyntaxKind::StarEq => Self::MulAssign, SyntaxKind::SlashEq => Self::DivAssign, _ => return Option::None, }) } /// The precedence of this operator. pub fn precedence(self) -> usize { match self { Self::Mul => 6, Self::Div => 6, Self::Add => 5, Self::Sub => 5, Self::Eq => 4, Self::Neq => 4, Self::Lt => 4, Self::Leq => 4, Self::Gt => 4, Self::Geq => 4, Self::In => 4, Self::NotIn => 4, Self::And => 3, Self::Or => 2, Self::Assign => 1, Self::AddAssign => 1, Self::SubAssign => 1, Self::MulAssign => 1, Self::DivAssign => 1, } } /// The associativity of this operator. pub fn assoc(self) -> Assoc { match self { Self::Add => Assoc::Left, Self::Sub => Assoc::Left, Self::Mul => Assoc::Left, Self::Div => Assoc::Left, Self::And => Assoc::Left, Self::Or => Assoc::Left, Self::Eq => Assoc::Left, Self::Neq => Assoc::Left, Self::Lt => Assoc::Left, Self::Leq => Assoc::Left, Self::Gt => Assoc::Left, Self::Geq => Assoc::Left, Self::In => Assoc::Left, Self::NotIn => Assoc::Left, Self::Assign => Assoc::Right, Self::AddAssign => Assoc::Right, Self::SubAssign => Assoc::Right, Self::MulAssign => Assoc::Right, Self::DivAssign => Assoc::Right, } } /// The string representation of this operation. pub fn as_str(self) -> &'static str { match self { Self::Add => "+", Self::Sub => "-", Self::Mul => "*", Self::Div => "/", Self::And => "and", Self::Or => "or", Self::Eq => "==", Self::Neq => "!=", Self::Lt => "<", Self::Leq => "<=", Self::Gt => ">", Self::Geq => ">=", Self::In => "in", Self::NotIn => "not in", Self::Assign => "=", Self::AddAssign => "+=", Self::SubAssign => "-=", Self::MulAssign => "*=", Self::DivAssign => "/=", } } } /// The associativity of a binary operator. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Assoc { /// Left-associative: `a + b + c` is equivalent to `(a + b) + c`. Left, /// Right-associative: `a = b = c` is equivalent to `a = (b = c)`. Right, } node! { /// A field access: `properties.age`. struct FieldAccess } impl<'a> FieldAccess<'a> { /// The expression to access the field on. pub fn target(self) -> Expr<'a> { self.0.cast_first() } /// The name of the field. pub fn field(self) -> Ident<'a> { self.0.cast_last() } } node! { /// An invocation of a function or method: `f(x, y)`. struct FuncCall } impl<'a> FuncCall<'a> { /// The function to call. pub fn callee(self) -> Expr<'a> { self.0.cast_first() } /// The arguments to the function. pub fn args(self) -> Args<'a> { self.0.cast_last() } } node! { /// A function call's argument list: `(12pt, y)`. struct Args } impl<'a> Args<'a> { /// The positional and named arguments. pub fn items(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(SyntaxNode::cast) } /// Whether there is a comma at the end. pub fn trailing_comma(self) -> bool { self.0 .children() .rev() .skip(1) .find(|n| !n.kind().is_trivia()) .is_some_and(|n| n.kind() == SyntaxKind::Comma) } } /// An argument to a function call. #[derive(Debug, Copy, Clone, Hash)] pub enum Arg<'a> { /// A positional argument: `12`. Pos(Expr<'a>), /// A named argument: `draw: false`. Named(Named<'a>), /// A spread argument: `..things`. Spread(Spread<'a>), } impl<'a> AstNode<'a> for Arg<'a> { fn from_untyped(node: &'a SyntaxNode) -> Option { match node.kind() { SyntaxKind::Named => Some(Self::Named(Named(node))), SyntaxKind::Spread => Some(Self::Spread(Spread(node))), _ => node.cast().map(Self::Pos), } } fn to_untyped(self) -> &'a SyntaxNode { match self { Self::Pos(v) => v.to_untyped(), Self::Named(v) => v.to_untyped(), Self::Spread(v) => v.to_untyped(), } } } node! { /// A closure: `(x, y) => z`. struct Closure } impl<'a> Closure<'a> { /// The name of the closure. /// /// This only exists if you use the function syntax sugar: `let f(x) = y`. pub fn name(self) -> Option> { self.0.children().next()?.cast() } /// The parameter bindings. pub fn params(self) -> Params<'a> { self.0.cast_first() } /// The body of the closure. pub fn body(self) -> Expr<'a> { self.0.cast_last() } } node! { /// A closure's parameters: `(x, y)`. struct Params } impl<'a> Params<'a> { /// The parameter bindings. pub fn children(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(SyntaxNode::cast) } } /// A parameter to a closure. #[derive(Debug, Copy, Clone, Hash)] pub enum Param<'a> { /// A positional parameter: `x`. Pos(Pattern<'a>), /// A named parameter with a default value: `draw: false`. Named(Named<'a>), /// An argument sink: `..args` or `..`. Spread(Spread<'a>), } impl<'a> AstNode<'a> for Param<'a> { fn from_untyped(node: &'a SyntaxNode) -> Option { match node.kind() { SyntaxKind::Named => Some(Self::Named(Named(node))), SyntaxKind::Spread => Some(Self::Spread(Spread(node))), _ => node.cast().map(Self::Pos), } } fn to_untyped(self) -> &'a SyntaxNode { match self { Self::Pos(v) => v.to_untyped(), Self::Named(v) => v.to_untyped(), Self::Spread(v) => v.to_untyped(), } } } /// The kind of a pattern. #[derive(Debug, Copy, Clone, Hash)] pub enum Pattern<'a> { /// A single expression: `x`. Normal(Expr<'a>), /// A placeholder: `_`. Placeholder(Underscore<'a>), /// A parenthesized pattern. Parenthesized(Parenthesized<'a>), /// A destructuring pattern: `(x, _, ..y)`. Destructuring(Destructuring<'a>), } impl<'a> AstNode<'a> for Pattern<'a> { fn from_untyped(node: &'a SyntaxNode) -> Option { match node.kind() { SyntaxKind::Underscore => Some(Self::Placeholder(Underscore(node))), SyntaxKind::Parenthesized => Some(Self::Parenthesized(Parenthesized(node))), SyntaxKind::Destructuring => Some(Self::Destructuring(Destructuring(node))), _ => node.cast().map(Self::Normal), } } fn to_untyped(self) -> &'a SyntaxNode { match self { Self::Normal(v) => v.to_untyped(), Self::Placeholder(v) => v.to_untyped(), Self::Parenthesized(v) => v.to_untyped(), Self::Destructuring(v) => v.to_untyped(), } } } impl<'a> Pattern<'a> { /// Returns a list of all new bindings introduced by the pattern. pub fn bindings(self) -> Vec> { match self { Self::Normal(Expr::Ident(ident)) => vec![ident], Self::Parenthesized(v) => v.pattern().bindings(), Self::Destructuring(v) => v.bindings(), _ => vec![], } } } impl Default for Pattern<'_> { fn default() -> Self { Self::Normal(Expr::default()) } } node! { /// An underscore: `_` struct Underscore } node! { /// A destructuring pattern: `x` or `(x, _, ..y)`. struct Destructuring } impl<'a> Destructuring<'a> { /// The items of the destructuring. pub fn items(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(SyntaxNode::cast) } /// Returns a list of all new bindings introduced by the destructuring. pub fn bindings(self) -> Vec> { self.items() .flat_map(|binding| match binding { DestructuringItem::Pattern(pattern) => pattern.bindings(), DestructuringItem::Named(named) => named.pattern().bindings(), DestructuringItem::Spread(spread) => { spread.sink_ident().into_iter().collect() } }) .collect() } } /// The kind of an element in a destructuring pattern. #[derive(Debug, Copy, Clone, Hash)] pub enum DestructuringItem<'a> { /// A sub-pattern: `x`. Pattern(Pattern<'a>), /// A renamed destructuring: `x: y`. Named(Named<'a>), /// A destructuring sink: `..y` or `..`. Spread(Spread<'a>), } impl<'a> AstNode<'a> for DestructuringItem<'a> { fn from_untyped(node: &'a SyntaxNode) -> Option { match node.kind() { SyntaxKind::Named => Some(Self::Named(Named(node))), SyntaxKind::Spread => Some(Self::Spread(Spread(node))), _ => node.cast().map(Self::Pattern), } } fn to_untyped(self) -> &'a SyntaxNode { match self { Self::Pattern(v) => v.to_untyped(), Self::Named(v) => v.to_untyped(), Self::Spread(v) => v.to_untyped(), } } } node! { /// A let binding: `let x = 1`. struct LetBinding } /// The kind of a let binding, either a normal one or a closure. #[derive(Debug)] pub enum LetBindingKind<'a> { /// A normal binding: `let x = 1`. Normal(Pattern<'a>), /// A closure binding: `let f(x) = 1`. Closure(Ident<'a>), } impl<'a> LetBindingKind<'a> { /// Returns a list of all new bindings introduced by the let binding. pub fn bindings(self) -> Vec> { match self { LetBindingKind::Normal(pattern) => pattern.bindings(), LetBindingKind::Closure(ident) => vec![ident], } } } impl<'a> LetBinding<'a> { /// The kind of the let binding. pub fn kind(self) -> LetBindingKind<'a> { match self.0.cast_first() { Pattern::Normal(Expr::Closure(closure)) => { LetBindingKind::Closure(closure.name().unwrap_or_default()) } pattern => LetBindingKind::Normal(pattern), } } /// The expression the binding is initialized with. pub fn init(self) -> Option> { match self.kind() { LetBindingKind::Normal(Pattern::Normal(_) | Pattern::Parenthesized(_)) => { self.0.children().filter_map(SyntaxNode::cast).nth(1) } LetBindingKind::Normal(_) => self.0.try_cast_first(), LetBindingKind::Closure(_) => self.0.try_cast_first(), } } } node! { /// An assignment expression `(x, y) = (1, 2)`. struct DestructAssignment } impl<'a> DestructAssignment<'a> { /// The pattern of the assignment. pub fn pattern(self) -> Pattern<'a> { self.0.cast_first() } /// The expression that is assigned. pub fn value(self) -> Expr<'a> { self.0.cast_last() } } node! { /// A set rule: `set text(...)`. struct SetRule } impl<'a> SetRule<'a> { /// The function to set style properties for. pub fn target(self) -> Expr<'a> { self.0.cast_first() } /// The style properties to set. pub fn args(self) -> Args<'a> { self.0.cast_last() } /// A condition under which the set rule applies. pub fn condition(self) -> Option> { self.0 .children() .skip_while(|child| child.kind() != SyntaxKind::If) .find_map(SyntaxNode::cast) } } node! { /// A show rule: `show heading: it => emph(it.body)`. struct ShowRule } impl<'a> ShowRule<'a> { /// Defines which nodes the show rule applies to. pub fn selector(self) -> Option> { self.0 .children() .rev() .skip_while(|child| child.kind() != SyntaxKind::Colon) .find_map(SyntaxNode::cast) } /// The transformation recipe. pub fn transform(self) -> Expr<'a> { self.0.cast_last() } } node! { /// A contextual expression: `context text.lang`. struct Contextual } impl<'a> Contextual<'a> { /// The expression which depends on the context. pub fn body(self) -> Expr<'a> { self.0.cast_first() } } node! { /// An if-else conditional: `if x { y } else { z }`. struct Conditional } impl<'a> Conditional<'a> { /// The condition which selects the body to evaluate. pub fn condition(self) -> Expr<'a> { self.0.cast_first() } /// The expression to evaluate if the condition is true. pub fn if_body(self) -> Expr<'a> { self.0 .children() .filter_map(SyntaxNode::cast) .nth(1) .unwrap_or_default() } /// The expression to evaluate if the condition is false. pub fn else_body(self) -> Option> { self.0.children().filter_map(SyntaxNode::cast).nth(2) } } node! { /// A while loop: `while x { y }`. struct WhileLoop } impl<'a> WhileLoop<'a> { /// The condition which selects whether to evaluate the body. pub fn condition(self) -> Expr<'a> { self.0.cast_first() } /// The expression to evaluate while the condition is true. pub fn body(self) -> Expr<'a> { self.0.cast_last() } } node! { /// A for loop: `for x in y { z }`. struct ForLoop } impl<'a> ForLoop<'a> { /// The pattern to assign to. pub fn pattern(self) -> Pattern<'a> { self.0.cast_first() } /// The expression to iterate over. pub fn iterable(self) -> Expr<'a> { self.0 .children() .skip_while(|&c| c.kind() != SyntaxKind::In) .find_map(SyntaxNode::cast) .unwrap_or_default() } /// The expression to evaluate for each iteration. pub fn body(self) -> Expr<'a> { self.0.cast_last() } } node! { /// A module import: `import "utils.typ": a, b, c`. struct ModuleImport } impl<'a> ModuleImport<'a> { /// The module or path from which the items should be imported. pub fn source(self) -> Expr<'a> { self.0.cast_first() } /// The items to be imported. pub fn imports(self) -> Option> { self.0.children().find_map(|node| match node.kind() { SyntaxKind::Star => Some(Imports::Wildcard), SyntaxKind::ImportItems => node.cast().map(Imports::Items), _ => Option::None, }) } /// The name that will be bound for a bare import. This name must be /// statically known. It can come from: /// - an identifier /// - a field access /// - a string that is a valid file path where the file stem is a valid /// identifier /// - a string that is a valid package spec pub fn bare_name(self) -> Result { match self.source() { Expr::Ident(ident) => Ok(ident.get().clone()), Expr::FieldAccess(access) => Ok(access.field().get().clone()), Expr::Str(string) => { let string = string.get(); let name = if string.starts_with('@') { PackageSpec::from_str(&string) .map_err(|_| BareImportError::PackageInvalid)? .name } else { Path::new(string.as_str()) .file_stem() .and_then(|path| path.to_str()) .ok_or(BareImportError::PathInvalid)? .into() }; if !is_ident(&name) { return Err(BareImportError::PathInvalid); } Ok(name) } _ => Err(BareImportError::Dynamic), } } /// The name this module was assigned to, if it was renamed with `as` /// (`renamed` in `import "..." as renamed`). pub fn new_name(self) -> Option> { self.0 .children() .skip_while(|child| child.kind() != SyntaxKind::As) .find_map(SyntaxNode::cast) } } /// Reasons why a bare name cannot be determined for an import source. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum BareImportError { /// There is no statically resolvable binding name. Dynamic, /// The import source is not a valid path or the path stem not a valid /// identifier. PathInvalid, /// The import source is not a valid package spec. PackageInvalid, } /// The items that ought to be imported from a file. #[derive(Debug, Copy, Clone, Hash)] pub enum Imports<'a> { /// All items in the scope of the file should be imported. Wildcard, /// The specified items from the file should be imported. Items(ImportItems<'a>), } node! { /// Items to import from a module: `a, b, c`. struct ImportItems } impl<'a> ImportItems<'a> { /// Returns an iterator over the items to import from the module. pub fn iter(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(|child| match child.kind() { SyntaxKind::RenamedImportItem => child.cast().map(ImportItem::Renamed), SyntaxKind::ImportItemPath => child.cast().map(ImportItem::Simple), _ => Option::None, }) } } node! { /// A path to a submodule's imported name: `a.b.c`. struct ImportItemPath } impl<'a> ImportItemPath<'a> { /// An iterator over the path's components. pub fn iter(self) -> impl DoubleEndedIterator> { self.0.children().filter_map(SyntaxNode::cast) } /// The name of the imported item. This is the last segment in the path. pub fn name(self) -> Ident<'a> { self.0.cast_last() } } /// An imported item, potentially renamed to another identifier. #[derive(Debug, Copy, Clone, Hash)] pub enum ImportItem<'a> { /// A non-renamed import (the item's name in the scope is the same as its /// name). Simple(ImportItemPath<'a>), /// A renamed import (the item was bound to a different name in the scope /// than the one it was defined as). Renamed(RenamedImportItem<'a>), } impl<'a> ImportItem<'a> { /// The path to the imported item. pub fn path(self) -> ImportItemPath<'a> { match self { Self::Simple(path) => path, Self::Renamed(renamed_item) => renamed_item.path(), } } /// The original name of the imported item, at its source. This will be the /// equal to the bound name if the item wasn't renamed with 'as'. pub fn original_name(self) -> Ident<'a> { match self { Self::Simple(path) => path.name(), Self::Renamed(renamed_item) => renamed_item.original_name(), } } /// The name which this import item was bound to. Corresponds to the new /// name, if it was renamed; otherwise, it's just its original name. pub fn bound_name(self) -> Ident<'a> { match self { Self::Simple(path) => path.name(), Self::Renamed(renamed_item) => renamed_item.new_name(), } } } node! { /// A renamed import item: `a as d` struct RenamedImportItem } impl<'a> RenamedImportItem<'a> { /// The path to the imported item. pub fn path(self) -> ImportItemPath<'a> { self.0.cast_first() } /// The original name of the imported item (`a` in `a as d` or `c.b.a as d`). pub fn original_name(self) -> Ident<'a> { self.path().name() } /// The new name of the imported item (`d` in `a as d`). pub fn new_name(self) -> Ident<'a> { self.0.cast_last() } } node! { /// A module include: `include "chapter1.typ"`. struct ModuleInclude } impl<'a> ModuleInclude<'a> { /// The module or path from which the content should be included. pub fn source(self) -> Expr<'a> { self.0.cast_last() } } node! { /// A break from a loop: `break`. struct LoopBreak } node! { /// A continue in a loop: `continue`. struct LoopContinue } node! { /// A return from a function: `return`, `return x + 1`. struct FuncReturn } impl<'a> FuncReturn<'a> { /// The expression to return. pub fn body(self) -> Option> { self.0.try_cast_last() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_expr_default() { assert!(Expr::default().to_untyped().cast::().is_some()); } }