summaryrefslogtreecommitdiff
path: root/src/syntax/ast.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/syntax/ast.rs')
-rw-r--r--src/syntax/ast.rs231
1 files changed, 173 insertions, 58 deletions
diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs
index 8d3696a8..6a016e79 100644
--- a/src/syntax/ast.rs
+++ b/src/syntax/ast.rs
@@ -5,7 +5,7 @@
use std::num::NonZeroUsize;
use std::ops::Deref;
-use super::{NodeData, NodeKind, Span, Spanned, SyntaxNode};
+use super::{NodeData, NodeKind, Span, SyntaxNode};
use crate::geom::{AngleUnit, LengthUnit};
use crate::util::EcoString;
@@ -60,34 +60,7 @@ node! {
impl Markup {
/// The markup nodes.
pub fn nodes(&self) -> impl Iterator<Item = MarkupNode> + '_ {
- self.0.children().filter_map(|node| match node.kind() {
- NodeKind::Space { newlines: (2 ..) } => Some(MarkupNode::Parbreak),
- NodeKind::Space { .. } => Some(MarkupNode::Space),
- NodeKind::Linebreak => Some(MarkupNode::Linebreak),
- NodeKind::Text(s) => Some(MarkupNode::Text(s.clone())),
- NodeKind::Escape(c) => Some(MarkupNode::Text((*c).into())),
- NodeKind::NonBreakingSpace => Some(MarkupNode::Text('\u{00A0}'.into())),
- NodeKind::Shy => Some(MarkupNode::Text('\u{00AD}'.into())),
- NodeKind::EnDash => Some(MarkupNode::Text('\u{2013}'.into())),
- NodeKind::EmDash => Some(MarkupNode::Text('\u{2014}'.into())),
- NodeKind::Ellipsis => Some(MarkupNode::Text('\u{2026}'.into())),
- &NodeKind::Quote { double } => Some(MarkupNode::Quote { double }),
- NodeKind::Strong => node.cast().map(MarkupNode::Strong),
- NodeKind::Emph => node.cast().map(MarkupNode::Emph),
- NodeKind::Link(url) => Some(MarkupNode::Link(url.clone())),
- NodeKind::Raw(raw) => Some(MarkupNode::Raw(raw.as_ref().clone())),
- NodeKind::Math(math) => Some(MarkupNode::Math(Spanned::new(
- math.as_ref().clone(),
- node.span(),
- ))),
- NodeKind::Heading => node.cast().map(MarkupNode::Heading),
- NodeKind::List => node.cast().map(MarkupNode::List),
- NodeKind::Enum => node.cast().map(MarkupNode::Enum),
- NodeKind::Desc => node.cast().map(MarkupNode::Desc),
- NodeKind::Label(v) => Some(MarkupNode::Label(v.clone())),
- NodeKind::Ref(v) => Some(MarkupNode::Ref(v.clone())),
- _ => node.cast().map(MarkupNode::Expr),
- })
+ self.0.children().filter_map(SyntaxNode::cast)
}
}
@@ -113,7 +86,7 @@ pub enum MarkupNode {
/// A raw block with optional syntax highlighting: `` `...` ``.
Raw(RawNode),
/// A math formula: `$a^2 = b^2 + c^2$`.
- Math(Spanned<MathNode>),
+ Math(Math),
/// A section heading: `= Introduction`.
Heading(HeadingNode),
/// An item in an unordered list: `- ...`.
@@ -130,6 +103,40 @@ pub enum MarkupNode {
Expr(Expr),
}
+impl TypedNode for MarkupNode {
+ fn from_untyped(node: &SyntaxNode) -> Option<Self> {
+ match node.kind() {
+ NodeKind::Space { newlines: (2 ..) } => Some(Self::Parbreak),
+ NodeKind::Space { .. } => Some(Self::Space),
+ NodeKind::Linebreak => Some(Self::Linebreak),
+ NodeKind::Text(s) => Some(Self::Text(s.clone())),
+ NodeKind::Escape(c) => Some(Self::Text((*c).into())),
+ NodeKind::Tilde => Some(Self::Text('\u{00A0}'.into())),
+ NodeKind::HyphQuest => Some(Self::Text('\u{00AD}'.into())),
+ NodeKind::Hyph2 => Some(Self::Text('\u{2013}'.into())),
+ NodeKind::Hyph3 => Some(Self::Text('\u{2014}'.into())),
+ NodeKind::Dot3 => Some(Self::Text('\u{2026}'.into())),
+ NodeKind::Quote { double } => Some(Self::Quote { double: *double }),
+ NodeKind::Strong => node.cast().map(Self::Strong),
+ NodeKind::Emph => node.cast().map(Self::Emph),
+ NodeKind::Link(url) => Some(Self::Link(url.clone())),
+ NodeKind::Raw(raw) => Some(Self::Raw(raw.as_ref().clone())),
+ NodeKind::Math => node.cast().map(Self::Math),
+ NodeKind::Heading => node.cast().map(Self::Heading),
+ NodeKind::List => node.cast().map(Self::List),
+ NodeKind::Enum => node.cast().map(Self::Enum),
+ NodeKind::Desc => node.cast().map(Self::Desc),
+ NodeKind::Label(v) => Some(Self::Label(v.clone())),
+ NodeKind::Ref(v) => Some(Self::Ref(v.clone())),
+ _ => node.cast().map(Self::Expr),
+ }
+ }
+
+ fn as_untyped(&self) -> &SyntaxNode {
+ unimplemented!("MarkupNode::as_untyped")
+ }
+}
+
node! {
/// Strong content: `*Strong*`.
StrongNode: Strong
@@ -169,14 +176,122 @@ pub struct RawNode {
pub block: bool,
}
-/// A math formula: `$x$`, `$[x^2]$`.
+node! {
+ /// A math formula: `$x$`, `$ x^2 $`.
+ Math: NodeKind::Math { .. }
+}
+
+impl Math {
+ /// The math nodes.
+ pub fn nodes(&self) -> impl Iterator<Item = MathNode> + '_ {
+ self.0.children().filter_map(SyntaxNode::cast)
+ }
+}
+
+/// A single piece of a math formula.
#[derive(Debug, Clone, PartialEq, Hash)]
-pub struct MathNode {
- /// The formula between the dollars / brackets.
- pub formula: EcoString,
- /// Whether the formula is display-level, that is, it contains whitespace
- /// after the starting dollar sign and before the ending dollar sign.
- pub display: bool,
+pub enum MathNode {
+ /// Whitespace.
+ Space,
+ /// A forced line break.
+ Linebreak,
+ /// An atom: `x`, `+`, `12`.
+ Atom(EcoString),
+ /// A base with an optional sub- and superscript: `a_1^2`.
+ Script(ScriptNode),
+ /// A fraction: `x/2`.
+ Frac(FracNode),
+ /// A math alignment indicator: `&`, `&&`.
+ Align(AlignNode),
+ /// 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 { .. } => Some(Self::Space),
+ NodeKind::LeftBrace => Some(Self::Atom('{'.into())),
+ NodeKind::RightBrace => Some(Self::Atom('}'.into())),
+ NodeKind::LeftBracket => Some(Self::Atom('['.into())),
+ NodeKind::RightBracket => Some(Self::Atom(']'.into())),
+ NodeKind::LeftParen => Some(Self::Atom('('.into())),
+ NodeKind::RightParen => Some(Self::Atom(')'.into())),
+ NodeKind::Linebreak => Some(Self::Linebreak),
+ NodeKind::Escape(c) => Some(Self::Atom((*c).into())),
+ NodeKind::Atom(atom) => Some(Self::Atom(atom.clone())),
+ 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 {
+ unimplemented!("MathNode::as_untyped")
+ }
+}
+
+node! {
+ /// A base with an optional sub- and superscript in a formula: `a_1^2`.
+ ScriptNode: Script
+}
+
+impl ScriptNode {
+ /// The base of the script.
+ pub fn base(&self) -> MathNode {
+ self.0.cast_first_child().expect("subscript 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`
+ FracNode: Frac
+}
+
+impl FracNode {
+ /// 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! {
+ /// A math alignment indicator: `&`, `&&`.
+ AlignNode: Align
+}
+
+impl AlignNode {
+ /// The number of ampersands.
+ pub fn count(&self) -> usize {
+ self.0.children().filter(|n| n.kind() == &NodeKind::Amp).count()
+ }
}
node! {
@@ -799,27 +914,27 @@ impl BinOp {
}
/// The associativity of this operator.
- pub fn associativity(self) -> Associativity {
+ pub fn assoc(self) -> Assoc {
match self {
- Self::Add => Associativity::Left,
- Self::Sub => Associativity::Left,
- Self::Mul => Associativity::Left,
- Self::Div => Associativity::Left,
- Self::And => Associativity::Left,
- Self::Or => Associativity::Left,
- Self::Eq => Associativity::Left,
- Self::Neq => Associativity::Left,
- Self::Lt => Associativity::Left,
- Self::Leq => Associativity::Left,
- Self::Gt => Associativity::Left,
- Self::Geq => Associativity::Left,
- Self::In => Associativity::Left,
- Self::NotIn => Associativity::Left,
- Self::Assign => Associativity::Right,
- Self::AddAssign => Associativity::Right,
- Self::SubAssign => Associativity::Right,
- Self::MulAssign => Associativity::Right,
- Self::DivAssign => Associativity::Right,
+ 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,
}
}
@@ -851,7 +966,7 @@ impl BinOp {
/// The associativity of a binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum Associativity {
+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)`.