use gramatika::{DebugLisp, Parse, ParseStreamer, Span, Spanned, SpannedError, Substr, Token as _};
use crate::{token::comment_start, ParseStream, Text, Token, TokenKind};
#[derive(DebugLisp)]
pub enum Comment {
Line(LineComment),
Block(BlockComment),
}
#[derive(DebugLisp)]
pub struct LineComment {
pub start: Token,
pub text: Text,
}
#[derive(DebugLisp)]
pub struct BlockComment {
pub start: Token,
pub children: Vec<BlockCommentChild>,
pub end: Token,
}
#[derive(DebugLisp)]
pub enum BlockCommentChild {
Text(Text),
Comment(BlockComment),
}
impl Parse for Comment {
type Stream = ParseStream;
fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
use TokenKind::*;
match input.peek() {
Some(token) => match token.as_matchable() {
(CommentStart, "//", _) => Ok(Comment::Line(input.parse()?)),
(CommentStart, "/*", _) => Ok(Comment::Block(input.parse()?)),
(_, _, span) => Err(SpannedError {
message: "Expected `//` or `/*`".into(),
source: input.source(),
span: Some(span),
}),
},
None => Err(SpannedError {
message: "Unexpected end of input".into(),
source: input.source(),
span: input.prev().map(|token| token.span()),
}),
}
}
}
impl Parse for LineComment {
type Stream = ParseStream;
fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
let start = input.consume(comment_start!["//"])?;
let start_span = start.span();
let text = match text_until(input, |peeked| {
peeked.span().start.line > start_span.start.line
}) {
Some(text) => text,
None => {
let content = Substr::new();
let span = Span {
start: start_span.end,
end: start_span.end,
};
Text(content, span)
}
};
Ok(Self { start, text })
}
}
impl Parse for BlockComment {
type Stream = ParseStream;
fn parse(input: &mut Self::Stream) -> gramatika::Result<Self> {
let start = input.consume(comment_start!["/*"])?;
let start_span = start.span();
let mut children: Vec<BlockCommentChild> = vec![];
loop {
use TokenKind::*;
match input.peek().map(|peeked| peeked.as_matchable()) {
Some((CommentStart, "/*", _)) => {
children.push(BlockCommentChild::Comment(input.parse()?));
}
Some((CommentEnd, "*/", _)) => {
let end = input.next().unwrap();
break Ok(Self {
start,
children,
end,
});
}
Some(_) => {
let text = match text_until(input, |peeked| {
matches!(
peeked.as_matchable(),
(CommentStart, "/*", _) | (CommentEnd, _, _)
)
}) {
Some(text) => text,
None => {
let content = Substr::new();
let span = Span {
start: start_span.end,
end: start_span.end,
};
Text(content, span)
}
};
children.push(BlockCommentChild::Text(text));
}
None => {
return Err(SpannedError {
message: "Unexpected end of input".into(),
source: input.source(),
span: input.prev().map(|token| token.span()),
});
}
}
}
}
}
impl Spanned for Comment {
fn span(&self) -> Span {
match self {
Comment::Line(inner) => inner.span(),
Comment::Block(inner) => inner.span(),
}
}
}
impl Spanned for BlockComment {
fn span(&self) -> Span {
self.start.span().through(self.end.span())
}
}
impl Spanned for LineComment {
fn span(&self) -> Span {
self.start.span().through(self.text.span())
}
}
fn text_until<F>(input: &mut ParseStream, end_condition: F) -> Option<Text>
where F: Fn(&Token) -> bool {
let mut result: Option<Text> = None;
loop {
match input.peek() {
Some(token) if !end_condition(token) => {
let kind = token.kind();
let token = input.consume_as(kind, Token::plain).unwrap();
result = match result.take() {
Some(text) => Some(text.concat(token)),
None => Some(Text::from(token)),
}
}
_ => {
break result;
}
}
}
}