/*!
# 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.
*/

// The AST should never panic when traversing a CST, even if the CST is in an
// invalid structure, e.g. if we have a syntax error or make an incorrect edit
// from the `typst-ide` crate. We disallow common panics in this file using
// these lints, and we provide an alternative to panicking with the
// `AstNode::placeholder()` method.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unreachable)]

use std::num::NonZeroUsize;
use std::ops::Deref;
use std::path::Path;
use std::str::FromStr;

use ecow::EcoString;
use typst_utils::NonZeroExt;
use unscanny::Scanner;

use crate::package::PackageSpec;
use crate::{Span, SyntaxKind, SyntaxNode, is_ident, is_newline};

/// A typed AST node.
pub trait AstNode<'a>: Sized {
    /// Convert a node into its typed variant.
    fn from_untyped(node: &'a SyntaxNode) -> Option<Self>;

    /// 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 placeholder for this node type. If the underlying CST has syntax
    /// errors or did not come from the parser, the AST is not guaranteed to be
    /// valid. But instead of panicking in that case, we return placeholder
    /// nodes.
    fn placeholder() -> Self;
}

// 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::<T>().is_some()
    }

    /// Try to convert the node to a typed AST node.
    pub fn cast<'a, T: AstNode<'a>>(&'a self) -> Option<T> {
        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<T> {
        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<T> {
        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>>(&'a self) -> T {
        self.try_cast_first().unwrap_or_else(T::placeholder)
    }

    /// Get the last child of AST type `T` or a placeholder if none.
    fn cast_last<'a, T: AstNode<'a>>(&'a self) -> T {
        self.try_cast_last().unwrap_or_else(T::placeholder)
    }
}

/// 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<Self> {
                if node.kind() == SyntaxKind::$name {
                    Some(Self(node))
                } else {
                    Option::None
                }
            }

            #[inline]
            fn to_untyped(self) -> &'a SyntaxNode {
                self.0
            }

            #[inline]
            fn placeholder() -> Self {
                static PLACEHOLDER: SyntaxNode
                    = SyntaxNode::placeholder(SyntaxKind::$name);
                Self(&PLACEHOLDER)
            }
        }
    };
}

node! {
    /// A line comment: `// ...`.
    struct LineComment
}

impl<'a> LineComment<'a> {
    /// The contents of the line comment.
    pub fn text(&self) -> &'a str {
        let text = self.0.text();
        text.strip_prefix("//").unwrap_or(text)
    }
}

node! {
    /// A block comment: `/* ... */`.
    struct BlockComment
}

impl<'a> BlockComment<'a> {
    /// The contents of the block comment.
    pub fn text(&self) -> &'a str {
        let text = self.0.text();
        text.strip_prefix("/*")
            .and_then(|text| text.strip_suffix("*/"))
            .unwrap_or(text)
    }
}

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<Item = Expr<'a>> {
        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: `<intro>`.
    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 field access in math: `arrow.r.long.double.bar`.
    MathFieldAccess(MathFieldAccess<'a>),
    /// A shorthand for a unicode codepoint in math: `a <= b`.
    MathShorthand(MathShorthand<'a>),
    /// An alignment point in math: `&`.
    MathAlignPoint(MathAlignPoint<'a>),
    /// A function call in math: `mat(delim: "[", a, b; ..#($c$,), d)`
    MathCall(MathCall<'a>),
    /// Matched delimiters in math: `[x + y]`.
    MathDelimited(MathDelimited<'a>),
    /// A base with optional attachments in math: `a_1^2`.
    MathAttach(MathAttach<'a>),
    /// Grouped primes in math: `a'''`.
    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<Self> {
        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<Self> {
        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::MathFieldAccess => {
                Some(Self::MathFieldAccess(MathFieldAccess(node)))
            }
            SyntaxKind::MathShorthand => Some(Self::MathShorthand(MathShorthand(node))),
            SyntaxKind::MathAlignPoint => {
                Some(Self::MathAlignPoint(MathAlignPoint(node)))
            }
            SyntaxKind::MathCall => Some(Self::MathCall(MathCall(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::MathFieldAccess(v) => v.to_untyped(),
            Self::MathShorthand(v) => v.to_untyped(),
            Self::MathAlignPoint(v) => v.to_untyped(),
            Self::MathCall(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(),
        }
    }

    fn placeholder() -> Self {
        Self::None(None::placeholder())
    }
}

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(_)
        )
    }
}

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<Item = Text<'a>> {
        self.0.children().filter_map(SyntaxNode::cast)
    }

    /// An optional identifier specifying the language to syntax-highlight in.
    pub fn lang(self) -> Option<RawLang<'a>> {
        // 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: `<intro>`.
    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.
    ///
    /// Will not be empty.
    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<ContentBlock<'a>> {
        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::ONE)
    }
}

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<u64> {
        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<Item = Expr<'a>> {
        self.0.children().filter_map(Expr::cast_with_space)
    }

    /// Whether this `Math` node was originally parenthesized.
    pub fn was_deparenthesized(self) -> bool {
        let mut iter = self.0.children();
        matches!(iter.next().map(SyntaxNode::kind), Some(SyntaxKind::LeftParen))
            && matches!(
                iter.next_back().map(SyntaxNode::kind),
                Some(SyntaxKind::RightParen)
            )
    }
}

node! {
    /// A lone text fragment in math: `x`, `25`, `3.1415`, `=`, `[`.
    struct MathText
}

/// The underlying text kind.
pub enum MathTextKind<'a> {
    Grapheme(&'a EcoString),
    Number(&'a EcoString),
}

impl<'a> MathText<'a> {
    /// Return the underlying text.
    pub fn get(self) -> MathTextKind<'a> {
        let text = self.0.text();
        if text.chars().next().unwrap_or_default().is_numeric() {
            // Numbers are potentially grouped as multiple characters. This is
            // done in `Lexer::math_text()`.
            MathTextKind::Number(text)
        } else {
            MathTextKind::Grapheme(text)
        }
    }
}

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 field access in math: `arrow.r.long.double.bar`.
    struct MathFieldAccess
}

impl<'a> MathFieldAccess<'a> {
    /// The expression to access the field on.
    pub fn target(self) -> MathAccess<'a> {
        self.0.cast_first()
    }

    /// The name of the field.
    pub fn field(self) -> MathIdent<'a> {
        self.0.cast_last()
    }
}

/// A variable or field access in math.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum MathAccess<'a> {
    MathIdent(MathIdent<'a>),
    MathFieldAccess(MathFieldAccess<'a>),
}

impl<'a> AstNode<'a> for MathAccess<'a> {
    fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
        match node.kind() {
            SyntaxKind::MathIdent => Some(Self::MathIdent(MathIdent(node))),
            SyntaxKind::MathFieldAccess => {
                Some(Self::MathFieldAccess(MathFieldAccess(node)))
            }
            _ => Option::None,
        }
    }

    fn to_untyped(self) -> &'a SyntaxNode {
        match self {
            Self::MathIdent(v) => v.to_untyped(),
            Self::MathFieldAccess(v) => v.to_untyped(),
        }
    }

    fn placeholder() -> Self {
        Self::MathIdent(MathIdent::placeholder())
    }
}

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! {
    /// A function call in math: `mat(delim: "[", a, b; ..#($c$,), d)`.
    struct MathCall
}

impl<'a> MathCall<'a> {
    /// The function to call. If not actually a function, will be rendered as
    /// content next to its arguments.
    pub fn callee(self) -> MathAccess<'a> {
        self.0.cast_first()
    }

    /// The arguments to the function.
    pub fn args(self) -> MathArgs<'a> {
        self.0.cast_last()
    }
}

node! {
    /// Function arguments in math: `(delim: "[", a, b; ..#($c$,), d)`.
    struct MathArgs
}

/// An argument in a [`MathCall`] for an actual function and whether it ends in
/// a semicolon.
#[derive(Debug, Copy, Clone, Hash)]
pub struct MathArg<'a> {
    /// The argument.
    pub arg: Arg<'a>,
    /// Whether the argument ends with a semicolon and should create
    /// two-dimensional args. This excludes semicolons that end embedded code
    /// expressions.
    pub ends_in_semicolon: bool,
}

/// Items at the top-level of a [`MathCall`] argument list that will be rendered
/// into content if unparsing for a non-function.
///
/// This enum does not implement [`AstNode`] because the `Semicolon` variant
/// requires extra context to convert correctly making it a likely footgun.
#[derive(Debug, Copy, Clone, Hash)]
pub enum MathArgItem<'a> {
    /// A normal argument.
    Arg(Arg<'a>),
    /// A space between arguments and other punctuation.
    Space(Space<'a>),
    /// A comma separating arguments.
    Comma(char, &'a SyntaxNode),
    /// A semicolon separating arguments. This excludes semicolons that end
    /// embedded code expressions.
    Semicolon(char, &'a SyntaxNode),
    /// The left paren at the start of the arguments.
    LeftParen(char, &'a SyntaxNode),
    /// The right paren at the end of the arguments.
    RightParen(char, &'a SyntaxNode),
}

impl<'a> MathArgs<'a> {
    /// Arguments for actual function calls in math.
    pub fn arg_items(self) -> impl Iterator<Item = MathArg<'a>> {
        let mut content_items = self.content_items().peekable();
        std::iter::from_fn(move || {
            let arg = content_items.find_map(|node| match node {
                MathArgItem::Arg(arg) => Some(arg),
                _ => Option::None,
            })?;

            // `self.content_items()` handles code-ending semicolons for us :)
            let ends_in_semicolon = loop {
                match content_items.peek() {
                    Option::None | Some(MathArgItem::Arg(_)) => break false,
                    Some(MathArgItem::Semicolon(_, _)) => break true,
                    Some(_) => {}
                }
                content_items.next();
            };

            Some(MathArg { arg, ends_in_semicolon })
        })
    }

    /// Items at the top-level of the argument list that will be rendered into
    /// content if unparsing for a non-function.
    pub fn content_items(self) -> impl Iterator<Item = MathArgItem<'a>> {
        let mut children = self.0.children().peekable();
        let mut prev_hash = false;
        std::iter::from_fn(move || {
            for node in children.by_ref() {
                if let Some(arg) = node.cast() {
                    return Some(MathArgItem::Arg(arg));
                }
                let semicolon_ends_code = prev_hash;
                prev_hash = false;
                let item = match node.kind() {
                    SyntaxKind::Space => MathArgItem::Space(Space(node)),
                    SyntaxKind::Comma => MathArgItem::Comma(',', node),
                    SyntaxKind::LeftParen => MathArgItem::LeftParen('(', node),
                    SyntaxKind::RightParen => MathArgItem::RightParen(')', node),
                    SyntaxKind::Semicolon if !semicolon_ends_code => {
                        MathArgItem::Semicolon(';', node)
                    }
                    SyntaxKind::Hash => {
                        prev_hash = true;
                        continue;
                    }
                    _ => continue,
                };
                return Some(item);
            }
            Option::None
        })
    }
}

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<Expr<'a>> {
        self.0
            .children()
            .skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore))
            .find_map(SyntaxNode::cast)
    }

    /// The top attachment.
    pub fn top(self) -> Option<Expr<'a>> {
        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<MathPrimes<'a>> {
        self.0
            .children()
            .skip_while(|node| node.cast::<Expr<'_>>().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 {
        // We can use byte length since single quotes are one byte.
        self.0.text().len()
    }
}

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<u8> {
        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<Item = Expr<'a>> {
        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<Item = ArrayItem<'a>> {
        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<Self> {
        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(),
        }
    }

    fn placeholder() -> Self {
        Self::Pos(Expr::placeholder())
    }
}

node! {
    /// A dictionary: `(thickness: 3pt, dash: "solid")`.
    struct Dict
}

impl<'a> Dict<'a> {
    /// The dictionary's items.
    pub fn items(self) -> impl DoubleEndedIterator<Item = DictItem<'a>> {
        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<Self> {
        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(),
        }
    }

    fn placeholder() -> Self {
        Self::Spread(Spread::placeholder())
    }
}

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<Ident<'a>> {
        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<Expr<'a>> {
        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<Self> {
        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) -> u8 {
        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<Self> {
        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) -> u8 {
        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<Item = Arg<'a>> {
        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<Self> {
        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(),
        }
    }

    fn placeholder() -> Self {
        Self::Pos(Expr::placeholder())
    }
}

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<Ident<'a>> {
        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<Item = Param<'a>> {
        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<Self> {
        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(),
        }
    }

    fn placeholder() -> Self {
        Self::Pos(Pattern::placeholder())
    }
}

/// 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<Self> {
        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(),
        }
    }

    fn placeholder() -> Self {
        Self::Normal(Expr::placeholder())
    }
}

impl<'a> Pattern<'a> {
    /// Returns a list of all new bindings introduced by the pattern.
    pub fn bindings(self) -> Vec<Ident<'a>> {
        match self {
            Self::Normal(Expr::Ident(ident)) => vec![ident],
            Self::Parenthesized(v) => v.pattern().bindings(),
            Self::Destructuring(v) => v.bindings(),
            _ => vec![],
        }
    }
}

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<Item = DestructuringItem<'a>> {
        self.0.children().filter_map(SyntaxNode::cast)
    }

    /// Returns a list of all new bindings introduced by the destructuring.
    pub fn bindings(self) -> Vec<Ident<'a>> {
        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<Self> {
        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(),
        }
    }

    fn placeholder() -> Self {
        Self::Pattern(Pattern::placeholder())
    }
}

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<Ident<'a>> {
        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_else(Ident::placeholder))
            }
            pattern => LetBindingKind::Normal(pattern),
        }
    }

    /// The expression the binding is initialized with.
    pub fn init(self) -> Option<Expr<'a>> {
        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<Expr<'a>> {
        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<Expr<'a>> {
        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_else(Expr::placeholder)
    }

    /// The expression to evaluate if the condition is false.
    pub fn else_body(self) -> Option<Expr<'a>> {
        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_else(Expr::placeholder)
    }

    /// 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<Imports<'a>> {
        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<EcoString, BareImportError> {
        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<Ident<'a>> {
        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<Item = ImportItem<'a>> {
        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<Item = Ident<'a>> {
        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<Expr<'a>> {
        self.0.try_cast_last()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_expr_placeholder() {
        assert!(Expr::placeholder().to_untyped().cast::<Expr>().is_some());
    }
}
