typst/src/syntax/ast.rs

1574 lines
42 KiB
Rust

//! A typed layer over the untyped syntax tree.
//!
//! The AST is rooted in the [`MarkupNode`].
use std::num::NonZeroUsize;
use std::ops::Deref;
use super::{NodeData, NodeKind, RawFields, Span, SyntaxNode, Unit};
use crate::util::EcoString;
/// A typed AST node.
pub trait TypedNode: Sized {
/// Convert a node into its typed variant.
fn from_untyped(node: &SyntaxNode) -> Option<Self>;
/// A reference to the underlying syntax node.
fn as_untyped(&self) -> &SyntaxNode;
/// The source code location.
fn span(&self) -> Span {
self.as_untyped().span()
}
}
macro_rules! node {
($(#[$attr:meta])* $name:ident) => {
node!{ $(#[$attr])* $name: NodeKind::$name { .. } }
};
($(#[$attr:meta])* $name:ident: $variants:pat) => {
#[derive(Debug, Clone, PartialEq, Hash)]
#[repr(transparent)]
$(#[$attr])*
pub struct $name(SyntaxNode);
impl TypedNode for $name {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
if matches!(node.kind(), $variants) {
Some(Self(node.clone()))
} else {
None
}
}
fn as_untyped(&self) -> &SyntaxNode {
&self.0
}
}
};
}
node! {
/// The syntactical root capable of representing a full parsed document.
Markup
}
impl Markup {
/// The children.
pub fn children(&self) -> impl DoubleEndedIterator<Item = MarkupNode> + '_ {
let mut was_stmt = false;
self.0
.children()
.filter(move |node| {
// Ignore linebreak directly after statements without semicolons.
let kind = node.kind();
let keep = !was_stmt || !matches!(kind, NodeKind::Space { newlines: 1 });
was_stmt = kind.is_stmt();
keep
})
.filter_map(SyntaxNode::cast)
}
}
/// A single piece of markup.
#[derive(Debug, Clone, PartialEq)]
pub enum MarkupNode {
/// Whitespace.
Space(Space),
/// A forced line break: `\`.
Linebreak(Linebreak),
/// Plain text without markup.
Text(Text),
/// An escape sequence: `\#`, `\u{1F5FA}`.
Escape(Escape),
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
/// space or `-?` for a soft hyphen.
Shorthand(Shorthand),
/// A smart quote: `'` or `"`.
SmartQuote(SmartQuote),
/// Strong content: `*Strong*`.
Strong(Strong),
/// Emphasized content: `_Emphasized_`.
Emph(Emph),
/// Raw text with optional syntax highlighting: `` `...` ``.
Raw(Raw),
/// A hyperlink: `https://typst.org`.
Link(Link),
/// A label: `<label>`.
Label(Label),
/// A reference: `@target`.
Ref(Ref),
/// A section heading: `= Introduction`.
Heading(Heading),
/// An item in an unordered list: `- ...`.
List(ListItem),
/// An item in an enumeration (ordered list): `+ ...` or `1. ...`.
Enum(EnumItem),
/// An item in a description list: `/ Term: Details`.
Desc(DescItem),
/// A math formula: `$x$`, `$ x^2 $`.
Math(Math),
/// An expression.
Expr(Expr),
}
impl TypedNode for MarkupNode {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
NodeKind::Space { .. } => node.cast().map(Self::Space),
NodeKind::Linebreak => node.cast().map(Self::Linebreak),
NodeKind::Text(_) => node.cast().map(Self::Text),
NodeKind::Escape(_) => node.cast().map(Self::Escape),
NodeKind::Shorthand(_) => node.cast().map(Self::Shorthand),
NodeKind::SmartQuote { .. } => node.cast().map(Self::SmartQuote),
NodeKind::Strong => node.cast().map(Self::Strong),
NodeKind::Emph => node.cast().map(Self::Emph),
NodeKind::Raw(_) => node.cast().map(Self::Raw),
NodeKind::Link(_) => node.cast().map(Self::Link),
NodeKind::Label(_) => node.cast().map(Self::Label),
NodeKind::Ref(_) => node.cast().map(Self::Ref),
NodeKind::Heading => node.cast().map(Self::Heading),
NodeKind::ListItem => node.cast().map(Self::List),
NodeKind::EnumItem => node.cast().map(Self::Enum),
NodeKind::DescItem => node.cast().map(Self::Desc),
NodeKind::Math => node.cast().map(Self::Math),
_ => node.cast().map(Self::Expr),
}
}
fn as_untyped(&self) -> &SyntaxNode {
match self {
Self::Space(v) => v.as_untyped(),
Self::Linebreak(v) => v.as_untyped(),
Self::Text(v) => v.as_untyped(),
Self::Escape(v) => v.as_untyped(),
Self::Shorthand(v) => v.as_untyped(),
Self::SmartQuote(v) => v.as_untyped(),
Self::Strong(v) => v.as_untyped(),
Self::Emph(v) => v.as_untyped(),
Self::Raw(v) => v.as_untyped(),
Self::Link(v) => v.as_untyped(),
Self::Label(v) => v.as_untyped(),
Self::Ref(v) => v.as_untyped(),
Self::Heading(v) => v.as_untyped(),
Self::List(v) => v.as_untyped(),
Self::Enum(v) => v.as_untyped(),
Self::Desc(v) => v.as_untyped(),
Self::Math(v) => v.as_untyped(),
Self::Expr(v) => v.as_untyped(),
}
}
}
node! {
/// Whitespace.
Space
}
impl Space {
/// Get the number of newlines.
pub fn newlines(&self) -> usize {
match self.0.kind() {
&NodeKind::Space { newlines } => newlines,
_ => panic!("space is of wrong kind"),
}
}
}
node! {
/// A forced line break: `\`.
Linebreak
}
node! {
/// Plain text without markup.
Text
}
impl Text {
/// Get the text.
pub fn get(&self) -> &EcoString {
match self.0.kind() {
NodeKind::Text(v) => v,
_ => panic!("text is of wrong kind"),
}
}
}
node! {
/// An escape sequence: `\#`, `\u{1F5FA}`.
Escape
}
impl Escape {
/// Get the escaped character.
pub fn get(&self) -> char {
match self.0.kind() {
&NodeKind::Escape(v) => v,
_ => panic!("escape is of wrong kind"),
}
}
}
node! {
/// A shorthand for a unicode codepoint. For example, `~` for non-breaking
/// space or `-?` for a soft hyphen.
Shorthand
}
impl Shorthand {
/// Get the shorthanded character.
pub fn get(&self) -> char {
match self.0.kind() {
&NodeKind::Shorthand(v) => v,
_ => panic!("shorthand is of wrong kind"),
}
}
}
node! {
/// A smart quote: `'` or `"`.
SmartQuote
}
impl SmartQuote {
/// Whether this is a double quote.
pub fn double(&self) -> bool {
match self.0.kind() {
&NodeKind::SmartQuote { double } => double,
_ => panic!("smart quote is of wrong kind"),
}
}
}
node! {
/// Strong content: `*Strong*`.
Strong
}
impl Strong {
/// The contents of the strong node.
pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("strong node is missing markup body")
}
}
node! {
/// Emphasized content: `_Emphasized_`.
Emph
}
impl Emph {
/// The contents of the emphasis node.
pub fn body(&self) -> Markup {
self.0
.cast_first_child()
.expect("emphasis node is missing markup body")
}
}
node! {
/// Raw text with optional syntax highlighting: `` `...` ``.
Raw
}
impl Raw {
/// The raw text.
pub fn text(&self) -> &EcoString {
&self.get().text
}
/// An optional identifier specifying the language to syntax-highlight in.
pub fn lang(&self) -> Option<&EcoString> {
self.get().lang.as_ref()
}
/// Whether the raw block is block-level.
pub fn block(&self) -> bool {
self.get().block
}
/// The raw fields.
fn get(&self) -> &RawFields {
match self.0.kind() {
NodeKind::Raw(v) => v.as_ref(),
_ => panic!("raw is of wrong kind"),
}
}
}
node! {
/// A hyperlink: `https://typst.org`.
Link
}
impl Link {
/// Get the URL.
pub fn url(&self) -> &EcoString {
match self.0.kind() {
NodeKind::Link(url) => url,
_ => panic!("link is of wrong kind"),
}
}
}
node! {
/// A label: `<label>`.
Label
}
impl Label {
/// Get the label.
pub fn get(&self) -> &EcoString {
match self.0.kind() {
NodeKind::Label(v) => v,
_ => panic!("label is of wrong kind"),
}
}
}
node! {
/// A reference: `@target`.
Ref
}
impl Ref {
/// Get the target.
pub fn get(&self) -> &EcoString {
match self.0.kind() {
NodeKind::Ref(v) => v,
_ => panic!("reference is of wrong kind"),
}
}
}
node! {
/// A section heading: `= Introduction`.
Heading
}
impl Heading {
/// The contents of the heading.
pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("heading is missing markup body")
}
/// The section depth (numer of equals signs).
pub fn level(&self) -> NonZeroUsize {
self.0
.children()
.filter(|n| n.kind() == &NodeKind::Eq)
.count()
.try_into()
.expect("heading is missing equals sign")
}
}
node! {
/// An item in an unordered list: `- ...`.
ListItem
}
impl ListItem {
/// The contents of the list item.
pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("list item is missing body")
}
}
node! {
/// An item in an enumeration (ordered list): `+ ...` or `1. ...`.
EnumItem
}
impl EnumItem {
/// The explicit numbering, if any: `23.`.
pub fn number(&self) -> Option<usize> {
self.0.children().find_map(|node| match node.kind() {
NodeKind::EnumNumbering(num) => Some(*num),
_ => None,
})
}
/// The contents of the list item.
pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("enum item is missing body")
}
}
node! {
/// An item in a description list: `/ Term: Details`.
DescItem
}
impl DescItem {
/// The term described by the item.
pub fn term(&self) -> Markup {
self.0
.cast_first_child()
.expect("description list item is missing term")
}
/// The description of the term.
pub fn body(&self) -> Markup {
self.0
.cast_last_child()
.expect("description list item is missing body")
}
}
node! {
/// A math formula: `$x$`, `$ x^2 $`.
Math
}
impl Math {
/// The children.
pub fn children(&self) -> impl DoubleEndedIterator<Item = MathNode> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
/// Whether this is a display-level math formula.
pub fn display(&self) -> bool {
matches!(self.children().next(), Some(MathNode::Space(_)))
&& matches!(self.children().last(), Some(MathNode::Space(_)))
}
}
/// A single piece of a math formula.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum MathNode {
/// Whitespace.
Space(Space),
/// A forced line break: `\`.
Linebreak(Linebreak),
/// An escape sequence: `\#`, `\u{1F5FA}`.
Escape(Escape),
/// An atom: `x`, `+`, `12`.
Atom(Atom),
/// A base with optional sub- and superscripts: `a_1^2`.
Script(Script),
/// A fraction: `x/2`.
Frac(Frac),
/// An alignment indicator: `&`, `&&`.
Align(Align),
/// Grouped mathematical material.
Group(Math),
/// An expression.
Expr(Expr),
}
impl TypedNode for MathNode {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
NodeKind::Space { .. } => node.cast().map(Self::Space),
NodeKind::Linebreak => node.cast().map(Self::Linebreak),
NodeKind::Escape(_) => node.cast().map(Self::Escape),
NodeKind::Atom(_) => node.cast().map(Self::Atom),
NodeKind::Script => node.cast().map(Self::Script),
NodeKind::Frac => node.cast().map(Self::Frac),
NodeKind::Align => node.cast().map(Self::Align),
NodeKind::Math => node.cast().map(Self::Group),
_ => node.cast().map(Self::Expr),
}
}
fn as_untyped(&self) -> &SyntaxNode {
match self {
Self::Space(v) => v.as_untyped(),
Self::Linebreak(v) => v.as_untyped(),
Self::Escape(v) => v.as_untyped(),
Self::Atom(v) => v.as_untyped(),
Self::Script(v) => v.as_untyped(),
Self::Frac(v) => v.as_untyped(),
Self::Align(v) => v.as_untyped(),
Self::Group(v) => v.as_untyped(),
Self::Expr(v) => v.as_untyped(),
}
}
}
node! {
/// A atom in a formula: `x`, `+`, `12`.
Atom
}
impl Atom {
/// Get the atom's text.
pub fn get(&self) -> &EcoString {
match self.0.kind() {
NodeKind::Atom(v) => v,
_ => panic!("atom is of wrong kind"),
}
}
}
node! {
/// A base with an optional sub- and superscript in a formula: `a_1^2`.
Script
}
impl Script {
/// The base of the script.
pub fn base(&self) -> MathNode {
self.0.cast_first_child().expect("script node is missing base")
}
/// The subscript.
pub fn sub(&self) -> Option<MathNode> {
self.0
.children()
.skip_while(|node| !matches!(node.kind(), NodeKind::Underscore))
.nth(1)
.map(|node| node.cast().expect("script node has invalid subscript"))
}
/// The superscript.
pub fn sup(&self) -> Option<MathNode> {
self.0
.children()
.skip_while(|node| !matches!(node.kind(), NodeKind::Hat))
.nth(1)
.map(|node| node.cast().expect("script node has invalid superscript"))
}
}
node! {
/// A fraction in a formula: `x/2`
Frac
}
impl Frac {
/// The numerator.
pub fn num(&self) -> MathNode {
self.0.cast_first_child().expect("fraction is missing numerator")
}
/// The denominator.
pub fn denom(&self) -> MathNode {
self.0.cast_last_child().expect("fraction is missing denominator")
}
}
node! {
/// An alignment indicator in a formula: `&`, `&&`.
Align
}
impl Align {
/// The number of ampersands.
pub fn count(&self) -> usize {
self.0.children().filter(|n| n.kind() == &NodeKind::Amp).count()
}
}
/// An expression.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Expr {
/// A literal: `1`, `true`, ...
Lit(Lit),
/// An identifier: `left`.
Ident(Ident),
/// A code block: `{ let x = 1; x + 2 }`.
Code(CodeBlock),
/// A content block: `[*Hi* there!]`.
Content(ContentBlock),
/// A grouped expression: `(1 + 2)`.
Parenthesized(Parenthesized),
/// An array: `(1, "hi", 12cm)`.
Array(Array),
/// A dictionary: `(thickness: 3pt, pattern: dashed)`.
Dict(Dict),
/// A unary operation: `-x`.
Unary(Unary),
/// A binary operation: `a + b`.
Binary(Binary),
/// A field access: `properties.age`.
FieldAccess(FieldAccess),
/// An invocation of a function: `f(x, y)`.
FuncCall(FuncCall),
/// An invocation of a method: `array.push(v)`.
MethodCall(MethodCall),
/// A closure: `(x, y) => z`.
Closure(Closure),
/// A let binding: `let x = 1`.
Let(LetBinding),
/// A set rule: `set text(...)`.
Set(SetRule),
/// A show rule: `show heading: it => [*{it.body}*]`.
Show(ShowRule),
/// An if-else conditional: `if x { y } else { z }`.
Conditional(Conditional),
/// A while loop: `while x { y }`.
While(WhileLoop),
/// A for loop: `for x in y { z }`.
For(ForLoop),
/// A module import: `import a, b, c from "utils.typ"`.
Import(ModuleImport),
/// A module include: `include "chapter1.typ"`.
Include(ModuleInclude),
/// A break from a loop: `break`.
Break(LoopBreak),
/// A continue in a loop: `continue`.
Continue(LoopContinue),
/// A return from a function: `return`, `return x + 1`.
Return(FuncReturn),
}
impl TypedNode for Expr {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
NodeKind::Ident(_) => node.cast().map(Self::Ident),
NodeKind::CodeBlock => node.cast().map(Self::Code),
NodeKind::ContentBlock => node.cast().map(Self::Content),
NodeKind::Parenthesized => node.cast().map(Self::Parenthesized),
NodeKind::Array => node.cast().map(Self::Array),
NodeKind::Dict => node.cast().map(Self::Dict),
NodeKind::Unary => node.cast().map(Self::Unary),
NodeKind::Binary => node.cast().map(Self::Binary),
NodeKind::FieldAccess => node.cast().map(Self::FieldAccess),
NodeKind::FuncCall => node.cast().map(Self::FuncCall),
NodeKind::MethodCall => node.cast().map(Self::MethodCall),
NodeKind::Closure => node.cast().map(Self::Closure),
NodeKind::LetBinding => node.cast().map(Self::Let),
NodeKind::SetRule => node.cast().map(Self::Set),
NodeKind::ShowRule => node.cast().map(Self::Show),
NodeKind::Conditional => node.cast().map(Self::Conditional),
NodeKind::WhileLoop => node.cast().map(Self::While),
NodeKind::ForLoop => node.cast().map(Self::For),
NodeKind::ModuleImport => node.cast().map(Self::Import),
NodeKind::ModuleInclude => node.cast().map(Self::Include),
NodeKind::LoopBreak => node.cast().map(Self::Break),
NodeKind::LoopContinue => node.cast().map(Self::Continue),
NodeKind::FuncReturn => node.cast().map(Self::Return),
_ => node.cast().map(Self::Lit),
}
}
fn as_untyped(&self) -> &SyntaxNode {
match self {
Self::Lit(v) => v.as_untyped(),
Self::Code(v) => v.as_untyped(),
Self::Content(v) => v.as_untyped(),
Self::Ident(v) => v.as_untyped(),
Self::Array(v) => v.as_untyped(),
Self::Dict(v) => v.as_untyped(),
Self::Parenthesized(v) => v.as_untyped(),
Self::Unary(v) => v.as_untyped(),
Self::Binary(v) => v.as_untyped(),
Self::FieldAccess(v) => v.as_untyped(),
Self::FuncCall(v) => v.as_untyped(),
Self::MethodCall(v) => v.as_untyped(),
Self::Closure(v) => v.as_untyped(),
Self::Let(v) => v.as_untyped(),
Self::Set(v) => v.as_untyped(),
Self::Show(v) => v.as_untyped(),
Self::Conditional(v) => v.as_untyped(),
Self::While(v) => v.as_untyped(),
Self::For(v) => v.as_untyped(),
Self::Import(v) => v.as_untyped(),
Self::Include(v) => v.as_untyped(),
Self::Break(v) => v.as_untyped(),
Self::Continue(v) => v.as_untyped(),
Self::Return(v) => v.as_untyped(),
}
}
}
impl Expr {
/// Whether the expression can be shortened in markup with a hashtag.
pub fn has_short_form(&self) -> bool {
matches!(
self,
Self::Ident(_)
| Self::FuncCall(_)
| Self::Let(_)
| Self::Set(_)
| Self::Show(_)
| Self::Conditional(_)
| Self::While(_)
| Self::For(_)
| Self::Import(_)
| Self::Include(_)
)
}
}
node! {
/// A literal: `1`, `true`, ...
Lit: NodeKind::None
| NodeKind::Auto
| NodeKind::Bool(_)
| NodeKind::Int(_)
| NodeKind::Float(_)
| NodeKind::Numeric(_, _)
| NodeKind::Str(_)
}
impl Lit {
/// The kind of literal.
pub fn kind(&self) -> LitKind {
match *self.0.kind() {
NodeKind::None => LitKind::None,
NodeKind::Auto => LitKind::Auto,
NodeKind::Bool(v) => LitKind::Bool(v),
NodeKind::Int(v) => LitKind::Int(v),
NodeKind::Float(v) => LitKind::Float(v),
NodeKind::Numeric(v, unit) => LitKind::Numeric(v, unit),
NodeKind::Str(ref v) => LitKind::Str(v.clone()),
_ => panic!("literal is of wrong kind"),
}
}
}
/// The kind of a literal.
#[derive(Debug, Clone, PartialEq)]
pub enum LitKind {
/// The none literal: `none`.
None,
/// The auto literal: `auto`.
Auto,
/// A boolean: `true`, `false`.
Bool(bool),
/// An integer: `120`.
Int(i64),
/// A floating-point number: `1.2`, `10e-4`.
Float(f64),
/// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`.
Numeric(f64, Unit),
/// A quoted string: `"..."`.
Str(EcoString),
}
node! {
/// A code block: `{ let x = 1; x + 2 }`.
CodeBlock
}
impl CodeBlock {
/// The list of expressions contained in the block.
pub fn exprs(&self) -> impl DoubleEndedIterator<Item = Expr> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
node! {
/// A content block: `[*Hi* there!]`.
ContentBlock
}
impl ContentBlock {
/// The contained markup.
pub fn body(&self) -> Markup {
self.0.cast_first_child().expect("content block is missing body")
}
}
node! {
/// A grouped expression: `(1 + 2)`.
Parenthesized
}
impl Parenthesized {
/// The wrapped expression.
pub fn expr(&self) -> Expr {
self.0
.cast_first_child()
.expect("parenthesized expression is missing expression")
}
}
node! {
/// An array: `(1, "hi", 12cm)`.
Array
}
impl Array {
/// The array's items.
pub fn items(&self) -> impl DoubleEndedIterator<Item = ArrayItem> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
/// An item in an array.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum ArrayItem {
/// A bare expression: `12`.
Pos(Expr),
/// A spreaded expression: `..things`.
Spread(Expr),
}
impl TypedNode for ArrayItem {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
NodeKind::Spread => node.cast_first_child().map(Self::Spread),
_ => node.cast().map(Self::Pos),
}
}
fn as_untyped(&self) -> &SyntaxNode {
match self {
Self::Pos(v) => v.as_untyped(),
Self::Spread(v) => v.as_untyped(),
}
}
}
node! {
/// A dictionary: `(thickness: 3pt, pattern: dashed)`.
Dict
}
impl Dict {
/// The dictionary's items.
pub fn items(&self) -> impl DoubleEndedIterator<Item = DictItem> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
/// An item in an dictionary expresssion.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum DictItem {
/// A named pair: `thickness: 3pt`.
Named(Named),
/// A keyed pair: `"spacy key": true`.
Keyed(Keyed),
/// A spreaded expression: `..things`.
Spread(Expr),
}
impl TypedNode for DictItem {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
NodeKind::Named => node.cast().map(Self::Named),
NodeKind::Keyed => node.cast().map(Self::Keyed),
NodeKind::Spread => node.cast_first_child().map(Self::Spread),
_ => None,
}
}
fn as_untyped(&self) -> &SyntaxNode {
match self {
Self::Named(v) => v.as_untyped(),
Self::Keyed(v) => v.as_untyped(),
Self::Spread(v) => v.as_untyped(),
}
}
}
node! {
/// A named pair: `thickness: 3pt`.
Named
}
impl Named {
/// The name: `thickness`.
pub fn name(&self) -> Ident {
self.0.cast_first_child().expect("named pair is missing name")
}
/// The right-hand side of the pair: `3pt`.
pub fn expr(&self) -> Expr {
self.0.cast_last_child().expect("named pair is missing expression")
}
}
node! {
/// A keyed pair: `"spacy key": true`.
Keyed
}
impl Keyed {
/// The key: `"spacy key"`.
pub fn key(&self) -> EcoString {
self.0
.children()
.find_map(|node| match node.kind() {
NodeKind::Str(key) => Some(key.clone()),
_ => None,
})
.expect("keyed pair is missing key")
}
/// The right-hand side of the pair: `true`.
pub fn expr(&self) -> Expr {
self.0.cast_last_child().expect("keyed pair is missing expression")
}
}
node! {
/// A unary operation: `-x`.
Unary
}
impl Unary {
/// The operator: `-`.
pub fn op(&self) -> UnOp {
self.0
.children()
.find_map(|node| UnOp::from_token(node.kind()))
.expect("unary operation is missing operator")
}
/// The expression to operate on: `x`.
pub fn expr(&self) -> Expr {
self.0.cast_last_child().expect("unary operation is missing child")
}
}
/// 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_token(token: &NodeKind) -> Option<Self> {
Some(match token {
NodeKind::Plus => Self::Pos,
NodeKind::Minus => Self::Neg,
NodeKind::Not => Self::Not,
_ => return 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`.
Binary
}
impl Binary {
/// The binary operator: `+`.
pub fn op(&self) -> BinOp {
let mut not = false;
self.0
.children()
.find_map(|node| match node.kind() {
NodeKind::Not => {
not = true;
None
}
NodeKind::In if not => Some(BinOp::NotIn),
_ => BinOp::from_token(node.kind()),
})
.expect("binary operation is missing operator")
}
/// The left-hand side of the operation: `a`.
pub fn lhs(&self) -> Expr {
self.0
.cast_first_child()
.expect("binary operation is missing left-hand side")
}
/// The right-hand side of the operation: `b`.
pub fn rhs(&self) -> Expr {
self.0
.cast_last_child()
.expect("binary operation is missing right-hand side")
}
}
/// 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 inversed containment operator: `not in`.
NotIn,
/// The add-assign operator: `+=`.
AddAssign,
/// The subtract-assign oeprator: `-=`.
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_token(token: &NodeKind) -> Option<Self> {
Some(match token {
NodeKind::Plus => Self::Add,
NodeKind::Minus => Self::Sub,
NodeKind::Star => Self::Mul,
NodeKind::Slash => Self::Div,
NodeKind::And => Self::And,
NodeKind::Or => Self::Or,
NodeKind::EqEq => Self::Eq,
NodeKind::ExclEq => Self::Neq,
NodeKind::Lt => Self::Lt,
NodeKind::LtEq => Self::Leq,
NodeKind::Gt => Self::Gt,
NodeKind::GtEq => Self::Geq,
NodeKind::Eq => Self::Assign,
NodeKind::In => Self::In,
NodeKind::PlusEq => Self::AddAssign,
NodeKind::HyphEq => Self::SubAssign,
NodeKind::StarEq => Self::MulAssign,
NodeKind::SlashEq => Self::DivAssign,
_ => return 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`.
FieldAccess
}
impl FieldAccess {
/// The expression to access the field on.
pub fn target(&self) -> Expr {
self.0.cast_first_child().expect("field access is missing object")
}
/// The name of the field.
pub fn field(&self) -> Ident {
self.0.cast_last_child().expect("field access is missing name")
}
}
node! {
/// An invocation of a function: `f(x, y)`.
FuncCall
}
impl FuncCall {
/// The function to call.
pub fn callee(&self) -> Expr {
self.0.cast_first_child().expect("function call is missing callee")
}
/// The arguments to the function.
pub fn args(&self) -> Args {
self.0
.cast_last_child()
.expect("function call is missing argument list")
}
}
node! {
/// An invocation of a method: `array.push(v)`.
MethodCall
}
impl MethodCall {
/// The expresion to call the method on.
pub fn target(&self) -> Expr {
self.0.cast_first_child().expect("method call is missing target")
}
/// The name of the method.
pub fn method(&self) -> Ident {
self.0.cast_last_child().expect("method call is missing name")
}
/// The arguments to the method.
pub fn args(&self) -> Args {
self.0
.cast_last_child()
.expect("method call is missing argument list")
}
}
node! {
/// A function call's argument list: `(12pt, y)`.
Args
}
impl Args {
/// The positional and named arguments.
pub fn items(&self) -> impl DoubleEndedIterator<Item = Arg> + '_ {
self.0.children().filter_map(SyntaxNode::cast)
}
}
/// An argument to a function call.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Arg {
/// A positional argument: `12`.
Pos(Expr),
/// A named argument: `draw: false`.
Named(Named),
/// A spreaded argument: `..things`.
Spread(Expr),
}
impl TypedNode for Arg {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
NodeKind::Named => node.cast().map(Self::Named),
NodeKind::Spread => node.cast_first_child().map(Self::Spread),
_ => node.cast().map(Self::Pos),
}
}
fn as_untyped(&self) -> &SyntaxNode {
match self {
Self::Pos(v) => v.as_untyped(),
Self::Named(v) => v.as_untyped(),
Self::Spread(v) => v.as_untyped(),
}
}
}
node! {
/// A closure: `(x, y) => z`.
Closure
}
impl Closure {
/// The name of the closure.
///
/// This only exists if you use the function syntax sugar: `let f(x) = y`.
pub fn name(&self) -> Option<Ident> {
self.0.cast_first_child()
}
/// The parameter bindings.
pub fn params(&self) -> impl DoubleEndedIterator<Item = Param> + '_ {
self.0
.children()
.find(|x| x.kind() == &NodeKind::Params)
.expect("closure is missing parameter list")
.children()
.filter_map(SyntaxNode::cast)
}
/// The body of the closure.
pub fn body(&self) -> Expr {
self.0.cast_last_child().expect("closure is missing body")
}
}
/// A parameter to a closure.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Param {
/// A positional parameter: `x`.
Pos(Ident),
/// A named parameter with a default value: `draw: false`.
Named(Named),
/// An argument sink: `..args`.
Sink(Ident),
}
impl TypedNode for Param {
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
match node.kind() {
NodeKind::Ident(_) => node.cast().map(Self::Pos),
NodeKind::Named => node.cast().map(Self::Named),
NodeKind::Spread => node.cast_first_child().map(Self::Sink),
_ => None,
}
}
fn as_untyped(&self) -> &SyntaxNode {
match self {
Self::Pos(v) => v.as_untyped(),
Self::Named(v) => v.as_untyped(),
Self::Sink(v) => v.as_untyped(),
}
}
}
node! {
/// A let binding: `let x = 1`.
LetBinding
}
impl LetBinding {
/// The binding to assign to.
pub fn binding(&self) -> Ident {
match self.0.cast_first_child() {
Some(Expr::Ident(binding)) => binding,
Some(Expr::Closure(closure)) => {
closure.name().expect("let-bound closure is missing name")
}
_ => panic!("let is missing binding"),
}
}
/// The expression the binding is initialized with.
pub fn init(&self) -> Option<Expr> {
if self.0.cast_first_child::<Ident>().is_some() {
// This is a normal binding like `let x = 1`.
self.0.children().filter_map(SyntaxNode::cast).nth(1)
} else {
// This is a closure binding like `let f(x) = 1`.
self.0.cast_first_child()
}
}
}
node! {
/// A set rule: `set text(...)`.
SetRule
}
impl SetRule {
/// The function to set style properties for.
pub fn target(&self) -> Ident {
self.0.cast_first_child().expect("set rule is missing target")
}
/// The style properties to set.
pub fn args(&self) -> Args {
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! {
/// A show rule: `show heading: it => [*{it.body}*]`.
ShowRule
}
impl ShowRule {
/// Defines which nodes the show rule applies to.
pub fn selector(&self) -> Option<Expr> {
self.0
.children()
.rev()
.skip_while(|child| child.kind() != &NodeKind::Colon)
.find_map(SyntaxNode::cast)
}
/// The transformation recipe.
pub fn transform(&self) -> Expr {
self.0.cast_last_child().expect("show rule is missing transform")
}
}
node! {
/// An if-else conditional: `if x { y } else { z }`.
Conditional
}
impl Conditional {
/// The condition which selects the body to evaluate.
pub fn condition(&self) -> Expr {
self.0.cast_first_child().expect("conditional is missing condition")
}
/// The expression to evaluate if the condition is true.
pub fn if_body(&self) -> Expr {
self.0
.children()
.filter_map(SyntaxNode::cast)
.nth(1)
.expect("conditional is missing body")
}
/// The expression to evaluate if the condition is false.
pub fn else_body(&self) -> Option<Expr> {
self.0.children().filter_map(SyntaxNode::cast).nth(2)
}
}
node! {
/// A while loop: `while x { y }`.
WhileLoop
}
impl WhileLoop {
/// The condition which selects whether to evaluate the body.
pub fn condition(&self) -> Expr {
self.0.cast_first_child().expect("while loop is missing condition")
}
/// The expression to evaluate while the condition is true.
pub fn body(&self) -> Expr {
self.0.cast_last_child().expect("while loop is missing body")
}
}
node! {
/// A for loop: `for x in y { z }`.
ForLoop
}
impl ForLoop {
/// The pattern to assign to.
pub fn pattern(&self) -> ForPattern {
self.0.cast_first_child().expect("for loop is missing pattern")
}
/// The expression to iterate over.
pub fn iter(&self) -> Expr {
self.0.cast_first_child().expect("for loop is missing iterable")
}
/// The expression to evaluate for each iteration.
pub fn body(&self) -> Expr {
self.0.cast_last_child().expect("for loop is missing body")
}
}
node! {
/// A for loop's destructuring pattern: `x` or `x, y`.
ForPattern
}
impl ForPattern {
/// The key part of the pattern: index for arrays, name for dictionaries.
pub fn key(&self) -> Option<Ident> {
let mut children = self.0.children().filter_map(SyntaxNode::cast);
let key = children.next();
if children.next().is_some() {
key
} else {
None
}
}
/// The value part of the pattern.
pub fn value(&self) -> Ident {
self.0.cast_last_child().expect("for loop pattern is missing value")
}
}
node! {
/// A module import: `import a, b, c from "utils.typ"`.
ModuleImport
}
impl ModuleImport {
/// The items to be imported.
pub fn imports(&self) -> Imports {
self.0
.children()
.find_map(|node| match node.kind() {
NodeKind::Star => Some(Imports::Wildcard),
NodeKind::ImportItems => {
let items = node.children().filter_map(SyntaxNode::cast).collect();
Some(Imports::Items(items))
}
_ => None,
})
.expect("module import is missing items")
}
/// The path to the file that should be imported.
pub fn path(&self) -> Expr {
self.0.cast_last_child().expect("module import is missing path")
}
}
/// The items that ought to be imported from a file.
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum Imports {
/// All items in the scope of the file should be imported.
Wildcard,
/// The specified items from the file should be imported.
Items(Vec<Ident>),
}
node! {
/// A module include: `include "chapter1.typ"`.
ModuleInclude
}
impl ModuleInclude {
/// The path to the file that should be included.
pub fn path(&self) -> Expr {
self.0.cast_last_child().expect("module include is missing path")
}
}
node! {
/// A break from a loop: `break`.
LoopBreak
}
node! {
/// A continue in a loop: `continue`.
LoopContinue
}
node! {
/// A return from a function: `return`, `return x + 1`.
FuncReturn
}
impl FuncReturn {
/// The expression to return.
pub fn body(&self) -> Option<Expr> {
self.0.cast_last_child()
}
}
node! {
/// An identifier: `it`.
Ident
}
impl Ident {
/// Get the identifier.
pub fn get(&self) -> &EcoString {
match self.0.kind() {
NodeKind::Ident(id) => id,
_ => panic!("identifier is of wrong kind"),
}
}
/// Take out the container identifier.
pub fn take(self) -> EcoString {
match self.0 {
SyntaxNode::Leaf(NodeData { kind: NodeKind::Ident(id), .. }) => id,
_ => panic!("identifier is of wrong kind"),
}
}
}
impl Deref for Ident {
type Target = str;
fn deref(&self) -> &Self::Target {
self.get()
}
}