use std::num::NonZeroUsize;
use std::ops::Deref;
use std::path::Path;
use std::str::FromStr;
use ecow::EcoString;
use unscanny::Scanner;
use crate::package::PackageSpec;
use crate::{Span, SyntaxKind, SyntaxNode, is_ident, is_newline};
pub trait AstNode<'a>: Sized {
fn from_untyped(node: &'a SyntaxNode) -> Option<Self>;
fn to_untyped(self) -> &'a SyntaxNode;
fn span(self) -> Span {
self.to_untyped().span()
}
}
impl SyntaxNode {
pub fn is<'a, T: AstNode<'a>>(&'a self) -> bool {
self.cast::<T>().is_some()
}
pub fn cast<'a, T: AstNode<'a>>(&'a self) -> Option<T> {
T::from_untyped(self)
}
fn try_cast_first<'a, T: AstNode<'a>>(&'a self) -> Option<T> {
self.children().find_map(Self::cast)
}
fn try_cast_last<'a, T: AstNode<'a>>(&'a self) -> Option<T> {
self.children().rev().find_map(Self::cast)
}
fn cast_first<'a, T: AstNode<'a> + Default>(&'a self) -> T {
self.try_cast_first().unwrap_or_default()
}
fn cast_last<'a, T: AstNode<'a> + Default>(&'a self) -> T {
self.try_cast_last().unwrap_or_default()
}
}
macro_rules! node {
($(#[$attr:meta])* struct $name:ident) => {
#[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
}
}
impl Default for $name<'_> {
#[inline]
fn default() -> Self {
static PLACEHOLDER: SyntaxNode
= SyntaxNode::placeholder(SyntaxKind::$name);
Self(&PLACEHOLDER)
}
}
};
}
node! {
struct Markup
}
impl<'a> Markup<'a> {
pub fn exprs(self) -> impl DoubleEndedIterator<Item = Expr<'a>> {
let mut was_stmt = false;
self.0
.children()
.filter(move |node| {
let kind = node.kind();
let keep = !was_stmt || node.kind() != SyntaxKind::Space;
was_stmt = kind.is_stmt();
keep
})
.filter_map(Expr::cast_with_space)
}
}
#[derive(Debug, Copy, Clone, Hash)]
pub enum Expr<'a> {
Text(Text<'a>),
Space(Space<'a>),
Linebreak(Linebreak<'a>),
Parbreak(Parbreak<'a>),
Escape(Escape<'a>),
Shorthand(Shorthand<'a>),
SmartQuote(SmartQuote<'a>),
Strong(Strong<'a>),
Emph(Emph<'a>),
Raw(Raw<'a>),
Link(Link<'a>),
Label(Label<'a>),
Ref(Ref<'a>),
Heading(Heading<'a>),
ListItem(ListItem<'a>),
EnumItem(EnumItem<'a>),
TermItem(TermItem<'a>),
Equation(Equation<'a>),
Math(Math<'a>),
MathText(MathText<'a>),
MathIdent(MathIdent<'a>),
MathShorthand(MathShorthand<'a>),
MathAlignPoint(MathAlignPoint<'a>),
MathDelimited(MathDelimited<'a>),
MathAttach(MathAttach<'a>),
MathPrimes(MathPrimes<'a>),
MathFrac(MathFrac<'a>),
MathRoot(MathRoot<'a>),
Ident(Ident<'a>),
None(None<'a>),
Auto(Auto<'a>),
Bool(Bool<'a>),
Int(Int<'a>),
Float(Float<'a>),
Numeric(Numeric<'a>),
Str(Str<'a>),
CodeBlock(CodeBlock<'a>),
ContentBlock(ContentBlock<'a>),
Parenthesized(Parenthesized<'a>),
Array(Array<'a>),
Dict(Dict<'a>),
Unary(Unary<'a>),
Binary(Binary<'a>),
FieldAccess(FieldAccess<'a>),
FuncCall(FuncCall<'a>),
Closure(Closure<'a>),
LetBinding(LetBinding<'a>),
DestructAssignment(DestructAssignment<'a>),
SetRule(SetRule<'a>),
ShowRule(ShowRule<'a>),
Contextual(Contextual<'a>),
Conditional(Conditional<'a>),
WhileLoop(WhileLoop<'a>),
ForLoop(ForLoop<'a>),
ModuleImport(ModuleImport<'a>),
ModuleInclude(ModuleInclude<'a>),
LoopBreak(LoopBreak<'a>),
LoopContinue(LoopContinue<'a>),
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, SyntaxKind::Linebreak => Some(Self::Linebreak(Linebreak(node))),
SyntaxKind::Parbreak => Some(Self::Parbreak(Parbreak(node))),
SyntaxKind::Text => Some(Self::Text(Text(node))),
SyntaxKind::Escape => Some(Self::Escape(Escape(node))),
SyntaxKind::Shorthand => Some(Self::Shorthand(Shorthand(node))),
SyntaxKind::SmartQuote => Some(Self::SmartQuote(SmartQuote(node))),
SyntaxKind::Strong => Some(Self::Strong(Strong(node))),
SyntaxKind::Emph => Some(Self::Emph(Emph(node))),
SyntaxKind::Raw => Some(Self::Raw(Raw(node))),
SyntaxKind::Link => Some(Self::Link(Link(node))),
SyntaxKind::Label => Some(Self::Label(Label(node))),
SyntaxKind::Ref => Some(Self::Ref(Ref(node))),
SyntaxKind::Heading => Some(Self::Heading(Heading(node))),
SyntaxKind::ListItem => Some(Self::ListItem(ListItem(node))),
SyntaxKind::EnumItem => Some(Self::EnumItem(EnumItem(node))),
SyntaxKind::TermItem => Some(Self::TermItem(TermItem(node))),
SyntaxKind::Equation => Some(Self::Equation(Equation(node))),
SyntaxKind::Math => Some(Self::Math(Math(node))),
SyntaxKind::MathText => Some(Self::MathText(MathText(node))),
SyntaxKind::MathIdent => Some(Self::MathIdent(MathIdent(node))),
SyntaxKind::MathShorthand => Some(Self::MathShorthand(MathShorthand(node))),
SyntaxKind::MathAlignPoint => {
Some(Self::MathAlignPoint(MathAlignPoint(node)))
}
SyntaxKind::MathDelimited => Some(Self::MathDelimited(MathDelimited(node))),
SyntaxKind::MathAttach => Some(Self::MathAttach(MathAttach(node))),
SyntaxKind::MathPrimes => Some(Self::MathPrimes(MathPrimes(node))),
SyntaxKind::MathFrac => Some(Self::MathFrac(MathFrac(node))),
SyntaxKind::MathRoot => Some(Self::MathRoot(MathRoot(node))),
SyntaxKind::Ident => Some(Self::Ident(Ident(node))),
SyntaxKind::None => Some(Self::None(None(node))),
SyntaxKind::Auto => Some(Self::Auto(Auto(node))),
SyntaxKind::Bool => Some(Self::Bool(Bool(node))),
SyntaxKind::Int => Some(Self::Int(Int(node))),
SyntaxKind::Float => Some(Self::Float(Float(node))),
SyntaxKind::Numeric => Some(Self::Numeric(Numeric(node))),
SyntaxKind::Str => Some(Self::Str(Str(node))),
SyntaxKind::CodeBlock => Some(Self::CodeBlock(CodeBlock(node))),
SyntaxKind::ContentBlock => Some(Self::ContentBlock(ContentBlock(node))),
SyntaxKind::Parenthesized => Some(Self::Parenthesized(Parenthesized(node))),
SyntaxKind::Array => Some(Self::Array(Array(node))),
SyntaxKind::Dict => Some(Self::Dict(Dict(node))),
SyntaxKind::Unary => Some(Self::Unary(Unary(node))),
SyntaxKind::Binary => Some(Self::Binary(Binary(node))),
SyntaxKind::FieldAccess => Some(Self::FieldAccess(FieldAccess(node))),
SyntaxKind::FuncCall => Some(Self::FuncCall(FuncCall(node))),
SyntaxKind::Closure => Some(Self::Closure(Closure(node))),
SyntaxKind::LetBinding => Some(Self::LetBinding(LetBinding(node))),
SyntaxKind::DestructAssignment => {
Some(Self::DestructAssignment(DestructAssignment(node)))
}
SyntaxKind::SetRule => Some(Self::SetRule(SetRule(node))),
SyntaxKind::ShowRule => Some(Self::ShowRule(ShowRule(node))),
SyntaxKind::Contextual => Some(Self::Contextual(Contextual(node))),
SyntaxKind::Conditional => Some(Self::Conditional(Conditional(node))),
SyntaxKind::WhileLoop => Some(Self::WhileLoop(WhileLoop(node))),
SyntaxKind::ForLoop => Some(Self::ForLoop(ForLoop(node))),
SyntaxKind::ModuleImport => Some(Self::ModuleImport(ModuleImport(node))),
SyntaxKind::ModuleInclude => Some(Self::ModuleInclude(ModuleInclude(node))),
SyntaxKind::LoopBreak => Some(Self::LoopBreak(LoopBreak(node))),
SyntaxKind::LoopContinue => Some(Self::LoopContinue(LoopContinue(node))),
SyntaxKind::FuncReturn => Some(Self::FuncReturn(FuncReturn(node))),
_ => Option::None,
}
}
fn to_untyped(self) -> &'a SyntaxNode {
match self {
Self::Text(v) => v.to_untyped(),
Self::Space(v) => v.to_untyped(),
Self::Linebreak(v) => v.to_untyped(),
Self::Parbreak(v) => v.to_untyped(),
Self::Escape(v) => v.to_untyped(),
Self::Shorthand(v) => v.to_untyped(),
Self::SmartQuote(v) => v.to_untyped(),
Self::Strong(v) => v.to_untyped(),
Self::Emph(v) => v.to_untyped(),
Self::Raw(v) => v.to_untyped(),
Self::Link(v) => v.to_untyped(),
Self::Label(v) => v.to_untyped(),
Self::Ref(v) => v.to_untyped(),
Self::Heading(v) => v.to_untyped(),
Self::ListItem(v) => v.to_untyped(),
Self::EnumItem(v) => v.to_untyped(),
Self::TermItem(v) => v.to_untyped(),
Self::Equation(v) => v.to_untyped(),
Self::Math(v) => v.to_untyped(),
Self::MathText(v) => v.to_untyped(),
Self::MathIdent(v) => v.to_untyped(),
Self::MathShorthand(v) => v.to_untyped(),
Self::MathAlignPoint(v) => v.to_untyped(),
Self::MathDelimited(v) => v.to_untyped(),
Self::MathAttach(v) => v.to_untyped(),
Self::MathPrimes(v) => v.to_untyped(),
Self::MathFrac(v) => v.to_untyped(),
Self::MathRoot(v) => v.to_untyped(),
Self::Ident(v) => v.to_untyped(),
Self::None(v) => v.to_untyped(),
Self::Auto(v) => v.to_untyped(),
Self::Bool(v) => v.to_untyped(),
Self::Int(v) => v.to_untyped(),
Self::Float(v) => v.to_untyped(),
Self::Numeric(v) => v.to_untyped(),
Self::Str(v) => v.to_untyped(),
Self::CodeBlock(v) => v.to_untyped(),
Self::ContentBlock(v) => v.to_untyped(),
Self::Array(v) => v.to_untyped(),
Self::Dict(v) => v.to_untyped(),
Self::Parenthesized(v) => v.to_untyped(),
Self::Unary(v) => v.to_untyped(),
Self::Binary(v) => v.to_untyped(),
Self::FieldAccess(v) => v.to_untyped(),
Self::FuncCall(v) => v.to_untyped(),
Self::Closure(v) => v.to_untyped(),
Self::LetBinding(v) => v.to_untyped(),
Self::DestructAssignment(v) => v.to_untyped(),
Self::SetRule(v) => v.to_untyped(),
Self::ShowRule(v) => v.to_untyped(),
Self::Contextual(v) => v.to_untyped(),
Self::Conditional(v) => v.to_untyped(),
Self::WhileLoop(v) => v.to_untyped(),
Self::ForLoop(v) => v.to_untyped(),
Self::ModuleImport(v) => v.to_untyped(),
Self::ModuleInclude(v) => v.to_untyped(),
Self::LoopBreak(v) => v.to_untyped(),
Self::LoopContinue(v) => v.to_untyped(),
Self::FuncReturn(v) => v.to_untyped(),
}
}
}
impl Expr<'_> {
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(_)
)
}
pub fn is_literal(self) -> bool {
matches!(
self,
Self::None(_)
| Self::Auto(_)
| Self::Bool(_)
| Self::Int(_)
| Self::Float(_)
| Self::Numeric(_)
| Self::Str(_)
)
}
}
impl Default for Expr<'_> {
fn default() -> Self {
Expr::None(None::default())
}
}
node! {
struct Text
}
impl<'a> Text<'a> {
pub fn get(self) -> &'a EcoString {
self.0.text()
}
}
node! {
struct Space
}
node! {
struct Linebreak
}
node! {
struct Parbreak
}
node! {
struct Escape
}
impl Escape<'_> {
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! {
struct Shorthand
}
impl Shorthand<'_> {
pub const LIST: &'static [(&'static str, char)] = &[
("...", '…'),
("~", '\u{00A0}'),
("-", '\u{2212}'), ("--", '\u{2013}'),
("---", '\u{2014}'),
("-?", '\u{00AD}'),
];
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! {
struct SmartQuote
}
impl SmartQuote<'_> {
pub fn double(self) -> bool {
self.0.text() == "\""
}
}
node! {
struct Strong
}
impl<'a> Strong<'a> {
pub fn body(self) -> Markup<'a> {
self.0.cast_first()
}
}
node! {
struct Emph
}
impl<'a> Emph<'a> {
pub fn body(self) -> Markup<'a> {
self.0.cast_first()
}
}
node! {
struct Raw
}
impl<'a> Raw<'a> {
pub fn lines(self) -> impl DoubleEndedIterator<Item = Text<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
pub fn lang(self) -> Option<RawLang<'a>> {
let delim: RawDelim = self.0.try_cast_first()?;
if delim.0.len() < 3 {
return Option::None;
}
self.0.try_cast_first()
}
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! {
struct RawLang
}
impl<'a> RawLang<'a> {
pub fn get(self) -> &'a EcoString {
self.0.text()
}
}
node! {
struct RawDelim
}
node! {
struct Link
}
impl<'a> Link<'a> {
pub fn get(self) -> &'a EcoString {
self.0.text()
}
}
node! {
struct Label
}
impl<'a> Label<'a> {
pub fn get(self) -> &'a str {
self.0.text().trim_start_matches('<').trim_end_matches('>')
}
}
node! {
struct Ref
}
impl<'a> Ref<'a> {
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()
}
pub fn supplement(self) -> Option<ContentBlock<'a>> {
self.0.try_cast_last()
}
}
node! {
struct Heading
}
impl<'a> Heading<'a> {
pub fn body(self) -> Markup<'a> {
self.0.cast_first()
}
pub fn depth(self) -> NonZeroUsize {
self.0
.children()
.find(|node| node.kind() == SyntaxKind::HeadingMarker)
.and_then(|node| node.len().try_into().ok())
.unwrap_or(NonZeroUsize::new(1).unwrap())
}
}
node! {
struct ListItem
}
impl<'a> ListItem<'a> {
pub fn body(self) -> Markup<'a> {
self.0.cast_first()
}
}
node! {
struct EnumItem
}
impl<'a> EnumItem<'a> {
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,
})
}
pub fn body(self) -> Markup<'a> {
self.0.cast_first()
}
}
node! {
struct TermItem
}
impl<'a> TermItem<'a> {
pub fn term(self) -> Markup<'a> {
self.0.cast_first()
}
pub fn description(self) -> Markup<'a> {
self.0.cast_last()
}
}
node! {
struct Equation
}
impl<'a> Equation<'a> {
pub fn body(self) -> Math<'a> {
self.0.cast_first()
}
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! {
struct Math
}
impl<'a> Math<'a> {
pub fn exprs(self) -> impl DoubleEndedIterator<Item = Expr<'a>> {
self.0.children().filter_map(Expr::cast_with_space)
}
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! {
struct MathText
}
pub enum MathTextKind<'a> {
Character(char),
Number(&'a EcoString),
}
impl<'a> MathText<'a> {
pub fn get(self) -> MathTextKind<'a> {
let text = self.0.text();
let mut chars = text.chars();
let c = chars.next().unwrap();
if c.is_numeric() {
MathTextKind::Number(text)
} else {
assert!(chars.next().is_none());
MathTextKind::Character(c)
}
}
}
node! {
struct MathIdent
}
impl<'a> MathIdent<'a> {
pub fn get(self) -> &'a EcoString {
self.0.text()
}
pub fn as_str(self) -> &'a str {
self.get()
}
}
impl Deref for MathIdent<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
node! {
struct MathShorthand
}
impl MathShorthand<'_> {
pub const LIST: &'static [(&'static str, char)] = &[
("...", '…'),
("-", '−'),
("*", '∗'),
("~", '∼'),
("!=", '≠'),
(":=", '≔'),
("::=", '⩴'),
("=:", '≕'),
("<<", '≪'),
("<<<", '⋘'),
(">>", '≫'),
(">>>", '⋙'),
("<=", '≤'),
(">=", '≥'),
("->", '→'),
("-->", '⟶'),
("|->", '↦'),
(">->", '↣'),
("->>", '↠'),
("<-", '←'),
("<--", '⟵'),
("<-<", '↢'),
("<<-", '↞'),
("<->", '↔'),
("<-->", '⟷'),
("~>", '⇝'),
("~~>", '⟿'),
("<~", '⇜'),
("<~~", '⬳'),
("=>", '⇒'),
("|=>", '⤇'),
("==>", '⟹'),
("<==", '⟸'),
("<=>", '⇔'),
("<==>", '⟺'),
("[|", '⟦'),
("|]", '⟧'),
("||", '‖'),
];
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! {
struct MathAlignPoint
}
node! {
struct MathDelimited
}
impl<'a> MathDelimited<'a> {
pub fn open(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn body(self) -> Math<'a> {
self.0.cast_first()
}
pub fn close(self) -> Expr<'a> {
self.0.cast_last()
}
}
node! {
struct MathAttach
}
impl<'a> MathAttach<'a> {
pub fn base(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn bottom(self) -> Option<Expr<'a>> {
self.0
.children()
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Underscore))
.find_map(SyntaxNode::cast)
}
pub fn top(self) -> Option<Expr<'a>> {
self.0
.children()
.skip_while(|node| !matches!(node.kind(), SyntaxKind::Hat))
.find_map(SyntaxNode::cast)
}
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! {
struct MathPrimes
}
impl MathPrimes<'_> {
pub fn count(self) -> usize {
self.0
.children()
.filter(|node| matches!(node.kind(), SyntaxKind::Prime))
.count()
}
}
node! {
struct MathFrac
}
impl<'a> MathFrac<'a> {
pub fn num(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn denom(self) -> Expr<'a> {
self.0.cast_last()
}
}
node! {
struct MathRoot
}
impl<'a> MathRoot<'a> {
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,
}
}
pub fn radicand(self) -> Expr<'a> {
self.0.cast_first()
}
}
node! {
struct Ident
}
impl<'a> Ident<'a> {
pub fn get(self) -> &'a EcoString {
self.0.text()
}
pub fn as_str(self) -> &'a str {
self.get()
}
}
impl Deref for Ident<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
node! {
struct None
}
node! {
struct Auto
}
node! {
struct Bool
}
impl Bool<'_> {
pub fn get(self) -> bool {
self.0.text() == "true"
}
}
node! {
struct Int
}
impl Int<'_> {
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! {
struct Float
}
impl Float<'_> {
pub fn get(self) -> f64 {
self.0.text().parse().unwrap_or_default()
}
}
node! {
struct Numeric
}
impl Numeric<'_> {
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)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Unit {
Pt,
Mm,
Cm,
In,
Rad,
Deg,
Em,
Fr,
Percent,
}
node! {
struct Str
}
impl Str<'_> {
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! {
struct CodeBlock
}
impl<'a> CodeBlock<'a> {
pub fn body(self) -> Code<'a> {
self.0.cast_first()
}
}
node! {
struct Code
}
impl<'a> Code<'a> {
pub fn exprs(self) -> impl DoubleEndedIterator<Item = Expr<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
}
node! {
struct ContentBlock
}
impl<'a> ContentBlock<'a> {
pub fn body(self) -> Markup<'a> {
self.0.cast_first()
}
}
node! {
struct Parenthesized
}
impl<'a> Parenthesized<'a> {
pub fn expr(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn pattern(self) -> Pattern<'a> {
self.0.cast_first()
}
}
node! {
struct Array
}
impl<'a> Array<'a> {
pub fn items(self) -> impl DoubleEndedIterator<Item = ArrayItem<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
}
#[derive(Debug, Copy, Clone, Hash)]
pub enum ArrayItem<'a> {
Pos(Expr<'a>),
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(),
}
}
}
node! {
struct Dict
}
impl<'a> Dict<'a> {
pub fn items(self) -> impl DoubleEndedIterator<Item = DictItem<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
}
#[derive(Debug, Copy, Clone, Hash)]
pub enum DictItem<'a> {
Named(Named<'a>),
Keyed(Keyed<'a>),
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(),
}
}
}
node! {
struct Named
}
impl<'a> Named<'a> {
pub fn name(self) -> Ident<'a> {
self.0.cast_first()
}
pub fn expr(self) -> Expr<'a> {
self.0.cast_last()
}
pub fn pattern(self) -> Pattern<'a> {
self.0.cast_last()
}
}
node! {
struct Keyed
}
impl<'a> Keyed<'a> {
pub fn key(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn expr(self) -> Expr<'a> {
self.0.cast_last()
}
}
node! {
struct Spread
}
impl<'a> Spread<'a> {
pub fn expr(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn sink_ident(self) -> Option<Ident<'a>> {
self.0.try_cast_first()
}
pub fn sink_expr(self) -> Option<Expr<'a>> {
self.0.try_cast_first()
}
}
node! {
struct Unary
}
impl<'a> Unary<'a> {
pub fn op(self) -> UnOp {
self.0
.children()
.find_map(|node| UnOp::from_kind(node.kind()))
.unwrap_or(UnOp::Pos)
}
pub fn expr(self) -> Expr<'a> {
self.0.cast_last()
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum UnOp {
Pos,
Neg,
Not,
}
impl UnOp {
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,
})
}
pub fn precedence(self) -> u8 {
match self {
Self::Pos | Self::Neg => 7,
Self::Not => 4,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Pos => "+",
Self::Neg => "-",
Self::Not => "not",
}
}
}
node! {
struct Binary
}
impl<'a> Binary<'a> {
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)
}
pub fn lhs(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn rhs(self) -> Expr<'a> {
self.0.cast_last()
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum BinOp {
Add,
Sub,
Mul,
Div,
And,
Or,
Eq,
Neq,
Lt,
Leq,
Gt,
Geq,
Assign,
In,
NotIn,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
}
impl BinOp {
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,
})
}
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,
}
}
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,
}
}
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 => "/=",
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Assoc {
Left,
Right,
}
node! {
struct FieldAccess
}
impl<'a> FieldAccess<'a> {
pub fn target(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn field(self) -> Ident<'a> {
self.0.cast_last()
}
}
node! {
struct FuncCall
}
impl<'a> FuncCall<'a> {
pub fn callee(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn args(self) -> Args<'a> {
self.0.cast_last()
}
}
node! {
struct Args
}
impl<'a> Args<'a> {
pub fn items(self) -> impl DoubleEndedIterator<Item = Arg<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
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)
}
}
#[derive(Debug, Copy, Clone, Hash)]
pub enum Arg<'a> {
Pos(Expr<'a>),
Named(Named<'a>),
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(),
}
}
}
node! {
struct Closure
}
impl<'a> Closure<'a> {
pub fn name(self) -> Option<Ident<'a>> {
self.0.children().next()?.cast()
}
pub fn params(self) -> Params<'a> {
self.0.cast_first()
}
pub fn body(self) -> Expr<'a> {
self.0.cast_last()
}
}
node! {
struct Params
}
impl<'a> Params<'a> {
pub fn children(self) -> impl DoubleEndedIterator<Item = Param<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
}
#[derive(Debug, Copy, Clone, Hash)]
pub enum Param<'a> {
Pos(Pattern<'a>),
Named(Named<'a>),
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(),
}
}
}
#[derive(Debug, Copy, Clone, Hash)]
pub enum Pattern<'a> {
Normal(Expr<'a>),
Placeholder(Underscore<'a>),
Parenthesized(Parenthesized<'a>),
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(),
}
}
}
impl<'a> Pattern<'a> {
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![],
}
}
}
impl Default for Pattern<'_> {
fn default() -> Self {
Self::Normal(Expr::default())
}
}
node! {
struct Underscore
}
node! {
struct Destructuring
}
impl<'a> Destructuring<'a> {
pub fn items(self) -> impl DoubleEndedIterator<Item = DestructuringItem<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
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()
}
}
#[derive(Debug, Copy, Clone, Hash)]
pub enum DestructuringItem<'a> {
Pattern(Pattern<'a>),
Named(Named<'a>),
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(),
}
}
}
node! {
struct LetBinding
}
#[derive(Debug)]
pub enum LetBindingKind<'a> {
Normal(Pattern<'a>),
Closure(Ident<'a>),
}
impl<'a> LetBindingKind<'a> {
pub fn bindings(self) -> Vec<Ident<'a>> {
match self {
LetBindingKind::Normal(pattern) => pattern.bindings(),
LetBindingKind::Closure(ident) => vec![ident],
}
}
}
impl<'a> LetBinding<'a> {
pub fn kind(self) -> LetBindingKind<'a> {
match self.0.cast_first() {
Pattern::Normal(Expr::Closure(closure)) => {
LetBindingKind::Closure(closure.name().unwrap_or_default())
}
pattern => LetBindingKind::Normal(pattern),
}
}
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! {
struct DestructAssignment
}
impl<'a> DestructAssignment<'a> {
pub fn pattern(self) -> Pattern<'a> {
self.0.cast_first()
}
pub fn value(self) -> Expr<'a> {
self.0.cast_last()
}
}
node! {
struct SetRule
}
impl<'a> SetRule<'a> {
pub fn target(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn args(self) -> Args<'a> {
self.0.cast_last()
}
pub fn condition(self) -> Option<Expr<'a>> {
self.0
.children()
.skip_while(|child| child.kind() != SyntaxKind::If)
.find_map(SyntaxNode::cast)
}
}
node! {
struct ShowRule
}
impl<'a> ShowRule<'a> {
pub fn selector(self) -> Option<Expr<'a>> {
self.0
.children()
.rev()
.skip_while(|child| child.kind() != SyntaxKind::Colon)
.find_map(SyntaxNode::cast)
}
pub fn transform(self) -> Expr<'a> {
self.0.cast_last()
}
}
node! {
struct Contextual
}
impl<'a> Contextual<'a> {
pub fn body(self) -> Expr<'a> {
self.0.cast_first()
}
}
node! {
struct Conditional
}
impl<'a> Conditional<'a> {
pub fn condition(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn if_body(self) -> Expr<'a> {
self.0
.children()
.filter_map(SyntaxNode::cast)
.nth(1)
.unwrap_or_default()
}
pub fn else_body(self) -> Option<Expr<'a>> {
self.0.children().filter_map(SyntaxNode::cast).nth(2)
}
}
node! {
struct WhileLoop
}
impl<'a> WhileLoop<'a> {
pub fn condition(self) -> Expr<'a> {
self.0.cast_first()
}
pub fn body(self) -> Expr<'a> {
self.0.cast_last()
}
}
node! {
struct ForLoop
}
impl<'a> ForLoop<'a> {
pub fn pattern(self) -> Pattern<'a> {
self.0.cast_first()
}
pub fn iterable(self) -> Expr<'a> {
self.0
.children()
.skip_while(|&c| c.kind() != SyntaxKind::In)
.find_map(SyntaxNode::cast)
.unwrap_or_default()
}
pub fn body(self) -> Expr<'a> {
self.0.cast_last()
}
}
node! {
struct ModuleImport
}
impl<'a> ModuleImport<'a> {
pub fn source(self) -> Expr<'a> {
self.0.cast_first()
}
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,
})
}
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),
}
}
pub fn new_name(self) -> Option<Ident<'a>> {
self.0
.children()
.skip_while(|child| child.kind() != SyntaxKind::As)
.find_map(SyntaxNode::cast)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum BareImportError {
Dynamic,
PathInvalid,
PackageInvalid,
}
#[derive(Debug, Copy, Clone, Hash)]
pub enum Imports<'a> {
Wildcard,
Items(ImportItems<'a>),
}
node! {
struct ImportItems
}
impl<'a> ImportItems<'a> {
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! {
struct ImportItemPath
}
impl<'a> ImportItemPath<'a> {
pub fn iter(self) -> impl DoubleEndedIterator<Item = Ident<'a>> {
self.0.children().filter_map(SyntaxNode::cast)
}
pub fn name(self) -> Ident<'a> {
self.0.cast_last()
}
}
#[derive(Debug, Copy, Clone, Hash)]
pub enum ImportItem<'a> {
Simple(ImportItemPath<'a>),
Renamed(RenamedImportItem<'a>),
}
impl<'a> ImportItem<'a> {
pub fn path(self) -> ImportItemPath<'a> {
match self {
Self::Simple(path) => path,
Self::Renamed(renamed_item) => renamed_item.path(),
}
}
pub fn original_name(self) -> Ident<'a> {
match self {
Self::Simple(path) => path.name(),
Self::Renamed(renamed_item) => renamed_item.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! {
struct RenamedImportItem
}
impl<'a> RenamedImportItem<'a> {
pub fn path(self) -> ImportItemPath<'a> {
self.0.cast_first()
}
pub fn original_name(self) -> Ident<'a> {
self.path().name()
}
pub fn new_name(self) -> Ident<'a> {
self.0.cast_last()
}
}
node! {
struct ModuleInclude
}
impl<'a> ModuleInclude<'a> {
pub fn source(self) -> Expr<'a> {
self.0.cast_last()
}
}
node! {
struct LoopBreak
}
node! {
struct LoopContinue
}
node! {
struct FuncReturn
}
impl<'a> FuncReturn<'a> {
pub fn body(self) -> Option<Expr<'a>> {
self.0.try_cast_last()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expr_default() {
assert!(Expr::default().to_untyped().cast::<Expr>().is_some());
}
}