move and turn into spans

This commit is contained in:
Jana Dönszelmann 2026-04-01 14:01:08 +02:00
parent bedaa49754
commit 9f401bda53
No known key found for this signature in database
13 changed files with 1262 additions and 774 deletions

View file

@ -0,0 +1,320 @@
use std::borrow::Cow;
use super::ast::*;
use winnow::{Parser, combinator::trace, error::ParserError};
impl<'a> AnyString<'a> {
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*, token::*};
// let (prefix, num_hashtags, quote) =
let preamble = (
take_while(0.., |b: char| !b.is_whitespace()),
take_while(0.., |c| c == '#').map(|i: &'a str| i.len()),
alt((
'`'.value(QuoteType::Backtick),
'\''.value(QuoteType::Single),
'\"'.value(QuoteType::Double),
)),
);
trace(
"string",
preamble.flat_map(
|(prefix, num_hashtags, quote): (&'a str, usize, QuoteType)| {
let end = (
match quote {
QuoteType::Single => '\'',
QuoteType::Double => '\"',
QuoteType::Backtick => '`',
},
repeat::<_, _, Cow<'a, str>, _, _>(
num_hashtags..=num_hashtags,
literal("#"),
),
);
let contents = repeat_till(0.., any, end).map(|(contents, _)| contents);
let suffix = take_while(0.., |b: char| !b.is_whitespace());
(contents, suffix).map(move |(contents, suffix): (Cow<'a, str>, &'a str)| {
Self {
prefix: prefix.into(),
ty: quote,
contents,
num_hashtags,
suffix: suffix.into(),
}
})
},
),
)
}
}
impl<'a> Space<'a> {
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{prelude::*, token::*};
trace(
"space",
take_while(0.., |b: char| b.is_whitespace()).map(|i: &'a str| Self(i.into())),
)
}
}
impl PathSep {
fn parse<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*};
trace(
"pathsep",
alt(('/'.value(Self::Slash), '\\'.value(Self::Backslash))),
)
}
}
impl Separator {
fn parser<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*, token::*};
trace(
"separator",
alt((
literal('=').value(Self::Eq),
literal(':').value(Self::Colon),
)),
)
}
}
impl<'a> FileName<'a> {
fn parse(segment: PathSegment<'a>) -> Self {
fn rsplit<'a>(
input: Cow<'a, str>,
delimiter: char,
) -> Option<(Cow<'a, str>, Cow<'a, str>)> {
match input {
Cow::Borrowed(s) => s
.rsplit_once(delimiter)
.map(|(a, b)| (Cow::Borrowed(a), Cow::Borrowed(b))),
Cow::Owned(s) => s
.rsplit_once(delimiter)
.map(|(a, b)| (Cow::Owned(a.to_string()), Cow::Owned(b.to_string()))),
}
}
let (rest, location) = if let Some((before, offset)) = rsplit(segment.segment.clone(), ':')
{
if let Some((before, line)) = rsplit(before.clone(), ':') {
(
before,
Some(FileLocation {
line,
offset: Some(offset),
}),
)
} else {
(
before,
Some(FileLocation {
line: offset,
offset: None,
}),
)
}
} else {
(segment.segment, None)
};
let (new_segment, ext_excluding_dot) =
if let Some((segment, ext_excluding_dot)) = rsplit(rest.clone(), '.') {
(segment, Some(ext_excluding_dot))
} else {
(rest, None)
};
Self {
leading_separator: segment.leading_separator,
segment: new_segment,
ext_excluding_dot,
location,
}
}
}
impl<'a> Path<'a> {
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*, token::*};
let till_next_sep = repeat_till(
0..,
any::<&'a str, E>.verify(|i: &char| !(*i).is_whitespace()),
peek(alt((
PathSep::parse().value(()),
any::<&'a str, E>
.verify(|i: &char| (*i).is_whitespace())
.value(()),
))),
)
.map(|(segment, _)| segment);
let sep_and_next =
(PathSep::parse(), till_next_sep).map(|(leading_separator, segment)| PathSegment {
leading_separator,
segment,
});
let drive = opt((
any::<&'a str, E>.verify(|x: &char| matches!(*x, 'A'..='Z')),
':',
))
.map(|i| i.map(|(letter, _): (char, char)| letter));
let drive_and_segments = (
drive,
repeat_till(
1..,
sep_and_next,
peek(any.verify(|i: &char| (*i).is_whitespace())),
)
.map(|(segments, _): (Vec<PathSegment>, _)| {
let (rest, last) = {
let mut segments = segments;
let last = segments.pop().unwrap();
(segments, last)
};
(rest, FileName::parse(last))
}),
);
trace(
"path",
drive_and_segments.map(|(drive, (segments, filename))| Self {
drive_excluding_colon: drive,
segments,
filename,
}),
)
}
}
impl<'a> Atom<'a> {
fn parse<E: ParserError<&'a str>>(
except_chars: &'static [char],
) -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*, token::*};
trace(
"atom",
alt((repeat(
1..,
any.verify(move |i: &char| !(*i).is_whitespace() && !except_chars.contains(i)),
)
.map(Self::Text),)),
)
}
}
impl<'a> Token<'a> {
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*};
let delimited: Box<dyn Parser<&'a str, Self, E>> =
Box::new(Delimited::parse().map(Self::Delimited));
trace(
"token",
alt((
"true".value(Self::True),
"false".value(Self::False),
"None".value(Self::None),
Path::parse().map(Self::Path),
AnyString::parse().map(Self::String),
Number::parse().map(Self::Number),
delimited,
Atom::parse(&[]).map(Self::Atom),
)),
)
}
}
impl<'a> Delimited<'a> {
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
use winnow::{combinator::*, prelude::*, token::*};
trace(
"delimited",
(
Atom::parse(&['(', '[', '{']),
alt((
literal('(').map(|_| literal(')').value(Delimiter::Paren)),
literal('[').map(|_| literal(']').value(Delimiter::Bracket)),
literal('{').map(|_| literal('}').value(Delimiter::Brace)),
))
.flat_map(|end| Segments::parse(end)),
)
.map(|(prefix, (contents, delimiter))| Self {
prefix,
delimiter,
contents,
}),
)
}
}
impl<'a> Segments<'a> {
fn parse<E: ParserError<&'a str> + 'a, End: 'a>(
end: impl Parser<&'a str, End, E>,
) -> impl Parser<&'a str, (Self, End), E> {
use winnow::{combinator::*, prelude::*};
trace(
"segments",
repeat_till(0.., Segment::parse(), (Space::parse(), end)).map(
|(segments, (trailing_space, end)): (Vec<_>, _)| {
(
Self {
segments,
trailing_space,
},
end,
)
},
),
)
}
}
impl<'a> Number<'a> {
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
use winnow::{ascii::*, combinator::*, prelude::*};
trace(
"number",
alt((float::<_, f64, _>.take(), dec_int::<_, i64, _>.take()))
.map(|i: &str| Self(i.into())),
)
}
}
impl<'a> Segment<'a> {
fn parse<E: ParserError<&'a str> + 'a>() -> impl Parser<&'a str, Self, E> {
use winnow::prelude::*;
trace(
"segment",
(Space::parse(), Token::parse()).map(|(leading_space, token)| Self {
leading_space,
token,
}),
)
}
}
pub fn parse_input<'a>(i: &'a str) -> Result<Segments<'a>, String> {
use winnow::combinator::eof;
Segments::parse(eof::<&str, winnow::error::EmptyError>)
.map(|(segments, _)| segments)
.parse(i)
.map_err(|e| e.to_string())
}