use crate::ast::Token;
use crate::error::{Error, Result, Span};
use crate::lexer::JsonLexer;
use logos::Logos;
pub struct LogosLexer<'a> {
lexer: logos::Lexer<'a, Token>,
input: &'a str,
peeked: Option<(Token, Span)>,
line: usize,
column: usize,
last_newline_pos: usize,
}
impl<'a> LogosLexer<'a> {
pub fn new(input: &'a str) -> Self {
LogosLexer {
lexer: Token::lexer(input),
input,
peeked: None,
line: 1,
column: 1,
last_newline_pos: 0,
}
}
fn update_position(&mut self, span: &Span) {
let text = &self.input[self.last_newline_pos..span.start];
for ch in text.chars() {
if ch == '\n' {
self.line += 1;
self.column = 1;
self.last_newline_pos = span.start;
} else {
self.column += 1;
}
}
}
pub fn next_token_with_span(&mut self) -> Result<(Token, Span)> {
self.next_token()
}
pub fn peek_with_span(&mut self) -> Result<&(Token, Span)> {
self.peek_token()
}
pub fn span(&self) -> logos::Span {
self.lexer.span()
}
}
impl<'a> JsonLexer for LogosLexer<'a> {
fn position(&self) -> usize {
self.lexer.span().start
}
fn next_token(&mut self) -> Result<(Token, Span)> {
if let Some(peeked) = self.peeked.take() {
return Ok(peeked);
}
match self.lexer.next() {
Some(token_result) => {
let logos_span = self.lexer.span();
let span = Span::new(logos_span.start, logos_span.end);
self.update_position(&span);
match token_result {
Ok(token) => Ok((token, span)),
Err(_) => Err(Error::UnexpectedChar(
self.lexer.slice().chars().next().unwrap_or(' '),
span.start,
)),
}
}
None => {
let pos = self.lexer.span().start;
Ok((Token::Eof, Span::new(pos, pos)))
}
}
}
fn peek_token(&mut self) -> Result<&(Token, Span)> {
if self.peeked.is_none() {
self.peeked = Some(self.next_token()?);
}
Ok(self.peeked.as_ref().unwrap())
}
fn span_text(&self, span: &Span) -> &str {
&self.input[span.start..span.end]
}
fn line_col(&self) -> (usize, usize) {
(self.line, self.column)
}
fn is_eof(&self) -> bool {
self.peeked
.as_ref()
.map(|(t, _)| *t == Token::Eof)
.unwrap_or(false)
|| self.lexer.remainder().is_empty()
}
}
pub type Lexer<'a> = LogosLexer<'a>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_logos_lexer() {
let input = r#"{"key": "value"}"#;
let mut lexer = LogosLexer::new(input);
let (token, span) = lexer.next_token().unwrap();
assert_eq!(token, Token::LeftBrace);
assert_eq!(span.start, 0);
assert_eq!(span.end, 1);
let peeked_token = lexer.peek_token().unwrap().0;
assert_eq!(peeked_token, Token::String);
let peeked_token2 = lexer.peek_token().unwrap().0;
assert_eq!(peeked_token, peeked_token2);
let (token, _) = lexer.next_token().unwrap();
assert_eq!(token, Token::String);
}
#[test]
fn test_line_col_tracking() {
let input = "{\n \"key\": \"value\"\n}";
let mut lexer = LogosLexer::new(input);
lexer.next_token().unwrap();
assert_eq!(lexer.line_col(), (1, 1));
lexer.next_token().unwrap(); lexer.next_token().unwrap(); let (line, _col) = lexer.line_col();
assert_eq!(line, 2);
}
}