use {
chumsky::prelude::*,
core::fmt,
};
use crate::{Span, SpanCache, chumsky::LaburnumSpanExt};
pub trait TriviaLexer<'src, I, E, X>:
chumsky::Parser<'src, I, Trivia, X>
+ Clone
+ 'src
where
I: chumsky::input::Input<'src, Token = char, Span = chumsky::span::SimpleSpan>
+ chumsky::input::StrInput<'src>,
E: chumsky::error::Error<'src, I>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<char>>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<()>>
+ 'src,
X: chumsky::extra::ParserExtra<'src, I>,
{
}
impl<'src, I, E, X, P> TriviaLexer<'src, I, E, X> for P
where
I: chumsky::input::Input<'src, Token = char, Span = chumsky::span::SimpleSpan>
+ chumsky::input::StrInput<'src>,
E: chumsky::error::Error<'src, I>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<char>>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<()>>
+ 'src,
X: chumsky::extra::ParserExtra<'src, I>,
P: chumsky::Parser<'src, I, Trivia, X>
+ Clone
+ 'src,
{
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Trivia {
Newline(Span),
Space(Span),
Tab(Span),
Many(Vec<Trivia>, Span),
}
impl Trivia {
pub fn span(&self) -> Span {
match self {
| Trivia::Newline(span) => *span,
| Trivia::Space(span) => *span,
| Trivia::Tab(span) => *span,
| Trivia::Many(_, span) => *span,
}
}
pub fn as_str(&self) -> String {
match self {
| Trivia::Newline(_span) => "\\n".to_string(),
| Trivia::Space(_span) => " ".to_string(),
| Trivia::Tab(_span) => "\\t".to_string(),
| Trivia::Many(t, _span) => {
let mut s = String::new();
for t in t {
s.push_str(t.as_str().as_str());
}
s
},
}
}
pub fn as_span_ident(self) -> (Span, crate::Ident) {
match self {
| Trivia::Newline(span) => (span, crate::Ident::new("\n")),
| Trivia::Space(span) => (span, crate::Ident::new(" ")),
| Trivia::Tab(span) => (span, crate::Ident::new("\t")),
| Trivia::Many(t, span) => (
span,
crate::Ident::new(
&t.into_iter()
.map(|t| t.as_str())
.collect::<Vec<_>>()
.join(""),
),
),
}
}
pub fn contains_new_line(&self) -> bool {
match self {
| Trivia::Newline(_) => true,
| Trivia::Space(_) => false,
| Trivia::Tab(_) => false,
| Trivia::Many(trivia, _) => trivia.iter().any(|t| t.contains_new_line()),
}
}
}
impl fmt::Debug for Trivia {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
| Trivia::Newline(span) => f
.debug_struct("Trivia::Newline")
.field("span", span)
.finish(),
| Trivia::Space(span) => {
f.debug_struct("Trivia::Space").field("span", span).finish()
},
| Trivia::Tab(span) => {
f.debug_struct("Trivia::Tab").field("span", span).finish()
},
| Trivia::Many(vals, _) => f.debug_list().entries(vals).finish(),
}
}
}
impl std::fmt::Display for Trivia {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
| Self::Newline(_) => write!(f, "\\n"),
| Self::Space(_) => write!(f, "' '"),
| Self::Tab(_) => write!(f, "\\t"),
| Self::Many(trivia, _) => {
for trivia in trivia {
write!(f, "{trivia}")?;
}
Ok(())
},
}
}
}
impl bluegum::Bluegum for Trivia {
fn node(&self, b: &mut bluegum::Builder) {
b.name("Trivia").field("kind", "").alt(format!("{self:?}"));
}
}
pub fn space<'src, I, E, X>() -> impl TriviaLexer<'src, I, E, X>
where
I: chumsky::input::Input<'src, Token = char, Span = chumsky::span::SimpleSpan>
+ chumsky::input::StrInput<'src>,
E: chumsky::error::Error<'src, I>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<char>>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<()>>
+ 'src,
X: chumsky::extra::ParserExtra<'src, I, Error = E> + 'src,
X::State: crate::chumsky::SpanCreator,
{
just(' ')
.ignored()
.repeated()
.at_least(1)
.count()
.map_with(|_, e| Trivia::Space(e.create_span()))
.boxed()
}
pub fn tab<'src, I, E, X>() -> impl TriviaLexer<'src, I, E, X>
where
I: chumsky::input::Input<'src, Token = char, Span = chumsky::span::SimpleSpan>
+ chumsky::input::StrInput<'src>,
E: chumsky::error::Error<'src, I>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<char>>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<()>>
+ 'src,
X: chumsky::extra::ParserExtra<'src, I, Error = E> + 'src,
X::State: crate::chumsky::SpanCreator,
{
just('\t')
.ignored()
.repeated()
.at_least(1)
.count()
.map_with(|_, e| Trivia::Tab(e.create_span()))
.boxed()
}
pub fn newlines<'src, I, E, X>() -> impl TriviaLexer<'src, I, E, X>
where
I: chumsky::input::Input<'src, Token = char, Span = chumsky::span::SimpleSpan>
+ chumsky::input::StrInput<'src>,
E: chumsky::error::Error<'src, I>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<char>>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<()>>
+ 'src,
X: chumsky::extra::ParserExtra<'src, I, Error = E> + 'src,
X::State: crate::chumsky::SpanCreator,
{
chumsky::text::newline()
.ignored()
.repeated()
.at_least(1)
.map_with(|_, e| Trivia::Newline(e.create_span()))
.boxed()
}
pub fn newline<'src, I, E, X>() -> impl TriviaLexer<'src, I, E, X>
where
I: chumsky::input::Input<'src, Token = char, Span = chumsky::span::SimpleSpan>
+ chumsky::input::StrInput<'src>,
E: chumsky::error::Error<'src, I>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<char>>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<()>>
+ 'src,
X: chumsky::extra::ParserExtra<'src, I, Error = E> + 'src,
X::State: crate::chumsky::SpanCreator,
{
chumsky::text::newline()
.ignored()
.map_with(|_, e| Trivia::Newline(e.create_span()))
.boxed()
}
pub fn leading<'src, I, E, X>() -> impl TriviaLexer<'src, I, E, X>
where
I: chumsky::input::Input<'src, Token = char, Span = chumsky::span::SimpleSpan>
+ chumsky::input::StrInput<'src>,
E: chumsky::error::Error<'src, I>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<char>>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<()>>
+ 'src,
X: chumsky::extra::ParserExtra<'src, I, Error = E> + 'src,
X::State: crate::chumsky::SpanCreator,
{
choice((space(), tab(), newline()))
.repeated()
.at_least(1)
.collect::<Vec<_>>()
.map_with(|t, e| Trivia::Many(t, e.create_span()))
.boxed()
}
pub fn trailing<'src, I, E, X>() -> impl TriviaLexer<'src, I, E, X>
where
I: chumsky::input::Input<'src, Token = char, Span = chumsky::span::SimpleSpan>
+ chumsky::input::StrInput<'src>,
E: chumsky::error::Error<'src, I>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<char>>
+ chumsky::error::LabelError<'src, I, chumsky::text::TextExpected<()>>
+ 'src,
X: chumsky::extra::ParserExtra<'src, I, Error = E> + 'src,
X::State: crate::chumsky::SpanCreator,
{
choice((tab(), space(), newline()))
.repeated()
.at_least(1)
.collect::<Vec<_>>()
.then(newline().or_not())
.map_with(|(mut t, ln), e| {
if let Some(ln) = ln {
t.push(ln)
}
Trivia::Many(t, e.create_span())
})
.boxed()
}
pub fn print_trivia_bluegum(
t: &Trivia,
b: &mut bluegum::Builder,
prefix: &str, span_cache: Option<&SpanCache>,
) {
match t {
| Trivia::Newline(span) => {
let field = b
.field(&format!("{prefix}NewLn"), "")
.alt(format!("{t}"))
.debug("span", format!("{:?}", span));
if let Some(cache) = span_cache {
let len = span.data(cache).map(|d| d.len).unwrap_or(0);
field.debug("count", format!("{}", len));
}
},
| Trivia::Space(span) => {
let field = b
.field(&format!("{prefix}Space"), "")
.alt(format!("{t}"))
.debug("span", format!("{:?}", span));
if let Some(cache) = span_cache {
let len = span.data(cache).map(|d| d.len).unwrap_or(0);
field.debug("count", format!("{}", len));
}
},
| Trivia::Tab(span) => {
let field = b
.field(&format!("{prefix}Tab"), "")
.alt(format!("{t}"))
.debug("span", format!("{:?}", span));
if let Some(cache) = span_cache {
let len = span.data(cache).map(|d| d.len).unwrap_or(0);
field.debug("count", format!("{}", len));
}
},
| Trivia::Many(vals, _) => {
for t in vals.iter() {
print_trivia_bluegum(t, b, prefix, span_cache);
}
},
};
}
#[macro_export]
macro_rules! wrap {
(
$token_parser:block -> $map:expr
) => {
$crate::chumsky::lexer::trivia::leading().or_not()
.then($token_parser)
.then($crate::chumsky::lexer::trivia::trailing().or_not())
.map_with($map)
.spanned()
.boxed()
};
}
pub use wrap;