use super::lexing::{skip_ws, take_lexeme, Lexeme};
use super::{Token, UnbalancedParenthesis};
use crate::error::{ParseError, ParseFailure};
use crate::{CompletionResult, ParseResult};
#[derive(Debug, Clone, Copy)]
pub struct TokenStream<'a> {
remaining: &'a str,
all_consumed: bool,
next_lexeme: Option<Lexeme<'a>>,
}
impl<'a> TokenStream<'a> {
pub fn new(input: &'a str) -> Self {
let (next_lexeme, remaining) = take_lexeme(skip_ws(input));
TokenStream {
all_consumed: input.is_empty(),
remaining,
next_lexeme,
}
}
pub fn is_empty(&self) -> bool {
self.peek().is_none()
}
pub fn is_all_consumed(&self) -> bool {
self.all_consumed
}
fn advance(&self) -> TokenStream<'a> {
let (next_lexeme, remaining) = take_lexeme(skip_ws(self.remaining));
TokenStream {
all_consumed: self.remaining.is_empty(),
remaining,
next_lexeme,
}
}
pub fn peek(&self) -> Option<Result<Token<'a>, UnbalancedParenthesis>> {
match self.next_lexeme {
Some(Lexeme::ClosingParen) => None,
Some(lexeme) => Some(Token::from_lexeme(lexeme)),
None => None,
}
}
pub fn take(&self) -> Option<Result<(Token<'a>, TokenStream<'a>), UnbalancedParenthesis>> {
match self.peek() {
Some(Ok(token)) => Some(Ok((token, self.advance()))),
Some(Err(error)) => Some(Err(error)),
None => None,
}
}
pub fn with_nested<R, F: FnOnce(TokenStream<'a>) -> ParseResult<'a, R>>(
&self,
callback: F,
) -> ParseResult<'a, R> {
let (has_parens, stream) = self.enter_nested();
let (result, stream) = match callback(stream) {
Ok(sucess) => sucess,
Err(error) if !has_parens => return Err(error),
Err(error @ ParseFailure::Error(_)) => return Err(error),
Err(ParseFailure::Unrecognized(unrecognized)) => {
return Err(unrecognized.into_error().into())
}
};
match stream.exit_nested(has_parens) {
Ok(remaining) => Ok((result, remaining)),
Err(Ok(token)) => Err(ParseError::unknown(token).into()),
Err(Err(error)) => Err(error.into()),
}
}
pub fn complete_nested<F: FnOnce(TokenStream<'a>) -> CompletionResult<'a>>(
&self,
callback: F,
) -> CompletionResult<'a> {
let (has_parens, stream) = self.enter_nested();
if has_parens {
let mut end_stream = stream;
let mut paren_depth = 1;
while paren_depth > 0 {
match end_stream.next_lexeme {
Some(Lexeme::OpeningParen) => paren_depth += 1,
Some(Lexeme::ClosingParen) => paren_depth -= 1,
Some(_) => (),
None => break,
}
end_stream = end_stream.advance();
}
if paren_depth == 0 {
return CompletionResult::new(end_stream, true);
}
}
callback(stream)
}
fn enter_nested(&self) -> (bool, TokenStream<'a>) {
if let Some(Lexeme::OpeningParen) = self.next_lexeme {
(true, self.advance())
} else {
(false, *self)
}
}
fn exit_nested(
&self,
has_parens: bool,
) -> Result<TokenStream<'a>, Result<Token<'a>, UnbalancedParenthesis>> {
if !has_parens {
return Ok(*self);
};
match self.next_lexeme {
Some(Lexeme::ClosingParen) => Ok(self.advance()),
Some(_) => Err(self.peek().unwrap()),
None => Ok(*self),
}
}
}
#[cfg(test)]
mod tests {
use super::TokenStream;
use crate::testing::token;
use crate::tokens::{Token, UnbalancedParenthesis};
use crate::CompletionResult;
use std::collections::BTreeSet;
fn assert_takes<'a>(stream: TokenStream<'a>, expected: Token<'a>) -> TokenStream<'a> {
let peeked = stream.peek().unwrap().unwrap();
let (taken, stream) = stream.take().unwrap().unwrap();
assert_eq!(peeked, taken);
assert_eq!(peeked, expected);
stream
}
#[test]
fn taking_tokens() {
let stream = TokenStream::new("first --second third");
let stream = assert_takes(stream, token!("first"));
assert!(!stream.is_all_consumed());
let stream = assert_takes(stream, token!(--"second"));
assert!(!stream.is_all_consumed());
let stream = assert_takes(stream, token!("third"));
assert!(stream.is_all_consumed());
assert!(stream.peek().is_none());
assert!(stream.take().is_none());
}
#[test]
fn takes_nested_structures() {
let stream = TokenStream::new("first (--second third) fourth");
let stream = assert_takes(stream, token!("first"));
assert!(matches!(stream.peek(), Some(Err(UnbalancedParenthesis))));
assert!(matches!(stream.take(), Some(Err(UnbalancedParenthesis))));
let (result, stream) = stream
.with_nested(|stream| {
let stream = assert_takes(stream, token!(--"second"));
let stream = assert_takes(stream, token!("third"));
assert!(matches!(stream.peek(), None));
assert!(matches!(stream.take(), None));
Ok((true, stream))
})
.unwrap();
assert!(result);
assert_takes(stream, token!("fourth"));
}
#[test]
fn takes_nested_structures_no_parens() {
let stream = TokenStream::new("first --second third fourth");
let stream = assert_takes(stream, token!("first"));
let (_, stream) = stream
.with_nested(|stream| {
let stream = assert_takes(stream, token!(--"second"));
let stream = assert_takes(stream, token!("third"));
Ok(((), stream))
})
.unwrap();
assert_takes(stream, token!("fourth"));
}
#[test]
fn nested_struct_remaining_tokens() {
let stream = TokenStream::new("(first second) third");
assert!(matches!(stream.peek(), Some(Err(UnbalancedParenthesis))));
assert!(matches!(stream.take(), Some(Err(UnbalancedParenthesis))));
let error = stream
.with_nested(|stream| {
let stream = assert_takes(stream, token!("first"));
Ok(((), stream))
})
.unwrap_err();
assert_eq!(
error.as_error().unwrap().to_string(),
"unrecognized token: \"second\""
);
}
#[test]
fn nested_struct_remaining_paren() {
let stream = TokenStream::new("(first (second)) third");
let error = stream
.with_nested(|stream| Ok(((), assert_takes(stream, token!("first")))))
.unwrap_err();
assert_eq!(
error.as_error().unwrap().to_string(),
"unbalanced parenthesis"
);
}
#[test]
fn completes_nested_skips_if_closed() {
let stream = TokenStream::new("(first (()(second))) third");
let result = stream.complete_nested(|_input| panic!("should not be called"));
assert!(result.value_consumed);
assert!(result.suggestions.is_empty());
assert_takes(result.remaining.unwrap(), token!("third"));
}
#[test]
fn completes_nested_calls_parser_if_not_closed() {
let stream = TokenStream::new("(first (()(second) third");
let result = stream.complete_nested(|input| {
assert_takes(input, token!("first"));
CompletionResult::new_final(true).add_suggestions(["abc".into()])
});
assert!(result.value_consumed);
assert_eq!(result.suggestions, BTreeSet::from(["abc".into()]));
assert!(result.remaining.is_none());
}
}