impl parser, ast, prettyprinter
This commit is contained in:
parent
4ff52a2bd3
commit
e3648ad8e6
4 changed files with 581 additions and 0 deletions
569
src/format_debug_output.rs
Normal file
569
src/format_debug_output.rs
Normal file
|
|
@ -0,0 +1,569 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
use winnow::{
|
||||
Parser,
|
||||
ascii::dec_uint,
|
||||
combinator::impls::ByRef,
|
||||
error::{FromExternalError, ParserError},
|
||||
stream,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Separator {
|
||||
Eq,
|
||||
Colon,
|
||||
}
|
||||
|
||||
impl Separator {
|
||||
fn parser<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> Result<Self, E> {
|
||||
use winnow::{combinator::*, prelude::*, token::*};
|
||||
|
||||
alt((
|
||||
literal('=').value(Self::Eq),
|
||||
literal(':').value(Self::Colon),
|
||||
))
|
||||
.parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Separator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Separator::Eq => write!(f, "="),
|
||||
Separator::Colon => write!(f, ":"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum QuoteType {
|
||||
Single,
|
||||
Double,
|
||||
Backtick,
|
||||
}
|
||||
|
||||
impl QuoteType {
|
||||
fn parse<'a, E: ParserError<&'a str>>(input: &mut &'a str) -> Result<Self, E> {
|
||||
use winnow::{combinator::*, prelude::*, token::*};
|
||||
|
||||
alt((
|
||||
literal('\'').value(Self::Single),
|
||||
literal('\"').value(Self::Double),
|
||||
literal('`').value(Self::Backtick),
|
||||
))
|
||||
.parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for QuoteType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
QuoteType::Single => write!(f, "\""),
|
||||
QuoteType::Double => write!(f, "\'"),
|
||||
QuoteType::Backtick => write!(f, "`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Delimiter {
|
||||
Paren,
|
||||
Bracket,
|
||||
Brace,
|
||||
Angle,
|
||||
}
|
||||
|
||||
impl Delimiter {
|
||||
fn fmt_start(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Delimiter::Paren => write!(f, "("),
|
||||
Delimiter::Bracket => write!(f, "["),
|
||||
Delimiter::Brace => write!(f, "{{"),
|
||||
Delimiter::Angle => write!(f, "<"),
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_end(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Delimiter::Paren => write!(f, ")"),
|
||||
Delimiter::Bracket => write!(f, "]"),
|
||||
Delimiter::Brace => write!(f, "}}"),
|
||||
Delimiter::Angle => write!(f, ">"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyString<'a> {
|
||||
prefix: Cow<'a, str>,
|
||||
ty: QuoteType,
|
||||
contents: Cow<'a, str>,
|
||||
num_hashtags: usize,
|
||||
suffix: Cow<'a, str>,
|
||||
}
|
||||
|
||||
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),
|
||||
)),
|
||||
);
|
||||
|
||||
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> Display for AnyString<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.prefix)?;
|
||||
for _ in 0..self.num_hashtags {
|
||||
write!(f, "#")?;
|
||||
}
|
||||
write!(f, "{}", self.ty)?;
|
||||
write!(f, "{}", self.contents)?;
|
||||
|
||||
for _ in 0..self.num_hashtags {
|
||||
write!(f, "#")?;
|
||||
}
|
||||
write!(f, "{}", self.suffix)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Space<'a>(Cow<'a, str>);
|
||||
|
||||
impl<'a> Space<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{prelude::*, token::*};
|
||||
|
||||
take_while(0.., |b: char| b.is_whitespace()).map(|i: &'a str| Self(i.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Space<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum PathSep {
|
||||
Slash,
|
||||
Backslash,
|
||||
}
|
||||
|
||||
impl PathSep {
|
||||
fn parse<'a, E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*};
|
||||
|
||||
alt(('/'.value(Self::Slash), '\\'.value(Self::Backslash)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PathSep {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PathSep::Slash => write!(f, "/"),
|
||||
PathSep::Backslash => write!(f, "\\"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PathSegment<'a> {
|
||||
leading_separator: PathSep,
|
||||
segment: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> Display for PathSegment<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.leading_separator)?;
|
||||
write!(f, "{}", self.segment)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileLocation<'a> {
|
||||
line: Cow<'a, str>,
|
||||
offset: Option<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
impl<'a> Display for FileLocation<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.line)?;
|
||||
if let Some(offset) = &self.offset {
|
||||
write!(f, "{offset}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileName<'a> {
|
||||
leading_separator: PathSep,
|
||||
segment: Cow<'a, str>,
|
||||
ext_excluding_dot: Option<Cow<'a, str>>,
|
||||
location: Option<FileLocation<'a>>,
|
||||
}
|
||||
|
||||
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> Display for FileName<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.leading_separator)?;
|
||||
write!(f, "{}", self.segment)?;
|
||||
if let Some(ext) = &self.ext_excluding_dot {
|
||||
write!(f, ".{ext}")?;
|
||||
}
|
||||
if let Some(loc) = &self.location {
|
||||
write!(f, "{loc}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Path<'a> {
|
||||
drive_excluding_colon: Option<char>,
|
||||
segments: Vec<PathSegment<'a>>,
|
||||
filename: FileName<'a>,
|
||||
}
|
||||
|
||||
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()),
|
||||
PathSep::parse(),
|
||||
)
|
||||
.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,
|
||||
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))
|
||||
}),
|
||||
);
|
||||
|
||||
drive_and_segments.map(|(drive, (segments, filename))| Self {
|
||||
drive_excluding_colon: drive,
|
||||
segments,
|
||||
filename,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Path<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(drive) = &self.drive_excluding_colon {
|
||||
write!(f, "{drive}:")?;
|
||||
}
|
||||
for segment in &self.segments {
|
||||
write!(f, "{segment}:")?;
|
||||
}
|
||||
write!(f, "{}", self.filename)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Number<'a>(Cow<'a, str>);
|
||||
|
||||
impl<'a> Number<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{ascii::*, combinator::*, prelude::*};
|
||||
|
||||
alt((float::<_, f64, _>.take(), dec_int::<_, i64, _>.take())).map(|i: &str| Self(i.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Number<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Anything that doesn't contain spaces, and that can be a prefix of `Delimited`.
|
||||
/// i.e. an english word, or rust `::`-separated Path
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Atom<'a> {
|
||||
Text(Cow<'a, str>),
|
||||
}
|
||||
|
||||
impl<'a> Display for Atom<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Atom::Text(text) => write!(f, "{text}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Atom<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*, token::*};
|
||||
|
||||
alt((repeat(0.., any.verify(|i: &char| !(*i).is_whitespace())).map(Self::Text),))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Token<'a> {
|
||||
True,
|
||||
False,
|
||||
None,
|
||||
|
||||
Path(Path<'a>),
|
||||
String(AnyString<'a>),
|
||||
Number(Number<'a>),
|
||||
|
||||
// TODO: RustPath
|
||||
Separated {
|
||||
before: Box<Token<'a>>,
|
||||
space_before: Space<'a>,
|
||||
separator: Separator,
|
||||
after: Box<Segment<'a>>,
|
||||
},
|
||||
Delimited(Delimited<'a>),
|
||||
|
||||
Atom(Atom<'a>),
|
||||
}
|
||||
|
||||
impl<'a> Token<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*};
|
||||
|
||||
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),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Token<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Token::Number(number) => write!(f, "{number}"),
|
||||
Token::True => write!(f, "true"),
|
||||
Token::False => write!(f, "false"),
|
||||
Token::None => write!(f, "None"),
|
||||
Token::Atom(atom) => write!(f, "{atom}"),
|
||||
Token::Path(path) => write!(f, "{path}"),
|
||||
Token::Separated {
|
||||
before,
|
||||
space_before,
|
||||
separator,
|
||||
after,
|
||||
} => write!(f, "{before}{space_before}{separator}{after}"),
|
||||
Token::Delimited(delimited) => write!(f, "{delimited}"),
|
||||
Token::String(s) => write!(f, "{s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Delimited<'a> {
|
||||
prefix: Atom<'a>,
|
||||
delimiter: Delimiter,
|
||||
contents: Segments<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Delimited<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::{combinator::*, prelude::*, token::*};
|
||||
|
||||
(
|
||||
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> Display for Delimited<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.prefix)?;
|
||||
self.delimiter.fmt_start(f)?;
|
||||
write!(f, "{}", self.contents)?;
|
||||
self.delimiter.fmt_end(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Segment<'a> {
|
||||
leading_space: Space<'a>,
|
||||
token: Token<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Segment<'a> {
|
||||
fn parse<E: ParserError<&'a str>>() -> impl Parser<&'a str, Self, E> {
|
||||
use winnow::prelude::*;
|
||||
|
||||
(Space::parse(), Token::parse()).map(|(leading_space, token)| Self {
|
||||
leading_space,
|
||||
token,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Segment<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.leading_space)?;
|
||||
write!(f, "{}", self.token)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Segments<'a> {
|
||||
segments: Vec<Segment<'a>>,
|
||||
trailing_space: Space<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Segments<'a> {
|
||||
fn parse<E: ParserError<&'a str>, End: 'a>(
|
||||
end: impl Parser<&'a str, End, E>,
|
||||
) -> impl Parser<&'a str, (Self, End), E> {
|
||||
use winnow::{combinator::*, prelude::*};
|
||||
|
||||
repeat_till(0.., Segment::parse(), (Space::parse(), end)).map(
|
||||
|(segments, (trailing_space, end)): (Vec<_>, _)| {
|
||||
(
|
||||
Self {
|
||||
segments,
|
||||
trailing_space,
|
||||
},
|
||||
end,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for Segments<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for segment in &self.segments {
|
||||
write!(f, "{segment}")?;
|
||||
}
|
||||
write!(f, "{}", self.trailing_space)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ use std::{
|
|||
str::FromStr,
|
||||
};
|
||||
|
||||
mod format_debug_output;
|
||||
mod tui;
|
||||
|
||||
use clap::{Parser, Subcommand, ValueEnum, builder::PossibleValue};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue