use crate::diagnostics::Span;
use super::types::*;
use std::str::Chars;
use std::iter::Peekable;
#[derive(Debug, Clone)]
pub struct ParseState<'a> {
pub full_input: Input<'a>,
pub current_input: Input<'a>,
pub position: usize,
pub line: usize,
pub column: usize,
pub context_stack: Vec<String>,
}
impl<'a> ParseState<'a> {
pub fn new(input: Input<'a>) -> Self {
Self {
full_input: input,
current_input: input,
position: 0,
line: 1,
column: 1,
context_stack: Vec::new(),
}
}
pub fn current_span(&self, length: usize) -> Span {
Span::new(self.position, self.position + length)
}
pub fn advance(&mut self, chars: usize) -> Result<(), Box<ParseError>> {
let mut char_iter = self.current_input.chars();
let mut byte_offset = 0;
for _ in 0..chars {
match char_iter.next() {
Some(ch) => {
byte_offset += ch.len_utf8();
if ch == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
}
None => {
return Err(Box::new(ParseError::new(
"Unexpected end of input".to_string(),
self.current_span(0)
)));
}
}
}
self.position += byte_offset;
self.current_input = &self.current_input[byte_offset..];
Ok(())
}
pub fn is_at_end(&self) -> bool {
self.current_input.is_empty()
}
pub fn push_context(&mut self, context: String) {
self.context_stack.push(context);
}
pub fn pop_context(&mut self) -> Option<String> {
self.context_stack.pop()
}
}
pub struct Primitives;
impl Primitives {
pub fn tag<'a>(expected: &'static str) -> impl Fn(Input<'a>) -> ParseResult<'a, &'a str> {
move |input: Input<'a>| {
if let Some(remaining) = input.strip_prefix(expected) {
Ok((remaining, &input[..expected.len()]))
} else {
let actual_len = input.chars().take(expected.len()).count();
let actual: String = input.chars().take(expected.len()).collect();
Err(Box::new(ParseError::new(
format!("Expected '{expected}'"),
Span::new(0, actual_len)
).with_expected(expected.to_string())
.with_actual(actual)))
}
}
}
pub fn char<'a>(expected: char) -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
move |input: Input<'a>| {
let mut chars = input.chars();
match chars.next() {
Some(ch) if ch == expected => {
let remaining = chars.as_str();
Ok((remaining, ch))
}
Some(ch) => Err(Box::new(ParseError::new(
format!("Expected character '{expected}'"),
Span::new(0, ch.len_utf8())
).with_expected(expected.to_string())
.with_actual(ch.to_string()))),
None => Err(Box::new(ParseError::new(
"Unexpected end of input".to_string(),
Span::new(0, 0)
).with_expected(expected.to_string()))),
}
}
}
pub fn any_char<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
|input: Input<'a>| {
let mut chars = input.chars();
match chars.next() {
Some(ch) => {
let remaining = chars.as_str();
Ok((remaining, ch))
}
None => Err(Box::new(ParseError::new(
"Unexpected end of input".to_string(),
Span::new(0, 0)
))),
}
}
}
pub fn satisfy<'a, P>(predicate: P) -> impl Fn(Input<'a>) -> ParseResult<'a, char>
where
P: Fn(char) -> bool,
{
move |input: Input<'a>| {
let mut chars = input.chars();
match chars.next() {
Some(ch) if predicate(ch) => {
let remaining = chars.as_str();
Ok((remaining, ch))
}
Some(ch) => Err(Box::new(ParseError::new(
"Character does not satisfy predicate".to_string(),
Span::new(0, ch.len_utf8())
).with_actual(ch.to_string()))),
None => Err(Box::new(ParseError::new(
"Unexpected end of input".to_string(),
Span::new(0, 0)
))),
}
}
}
pub fn char_range<'a>(start: char, end: char) -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
move |input: Input<'a>| {
Self::satisfy(|ch| ch >= start && ch <= end)(input)
}
}
pub fn digit<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Self::satisfy(|ch| ch.is_ascii_digit())
}
pub fn alpha<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Self::satisfy(|ch| ch.is_alphabetic())
}
pub fn alphanumeric<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Self::satisfy(|ch| ch.is_alphanumeric())
}
pub fn whitespace<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Self::satisfy(|ch| ch.is_whitespace())
}
pub fn newline<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Self::char('\n')
}
pub fn tab<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Self::char('\t')
}
pub fn space<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Self::satisfy(|ch| ch == ' ' || ch == '\t')
}
pub fn hex_digit<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Self::satisfy(|ch| ch.is_ascii_hexdigit())
}
pub fn oct_digit<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Self::satisfy(|ch| ('0'..='7').contains(&ch))
}
pub fn eof<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, ()> {
|input: Input<'a>| {
if input.is_empty() {
Ok((input, ()))
} else {
Err(Box::new(ParseError::new(
"Expected end of input".to_string(),
Span::new(0, 1)
).with_expected("end of input".to_string())
.with_actual(input.chars().next().unwrap().to_string())))
}
}
}
pub fn empty<'a, T>(value: T) -> impl Fn(Input<'a>) -> ParseResult<'a, T>
where
T: Clone,
{
move |input: Input<'a>| Ok((input, value.clone()))
}
pub fn fail<'a, T>(message: &'static str) -> impl Fn(Input<'a>) -> ParseResult<'a, T> {
move |_input: Input<'a>| {
Err(Box::new(ParseError::new(
message.to_string(),
Span::new(0, 0)
)))
}
}
}
pub struct SchemePreimitives;
impl SchemePreimitives {
pub fn identifier_start<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Primitives::satisfy(|ch| {
ch.is_alphabetic() ||
"!$%&*+-./:<=>?@^_~".contains(ch)
})
}
pub fn identifier_continue<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Primitives::satisfy(|ch| {
ch.is_alphanumeric() ||
"!$%&*+-./:<=>?@^_~".contains(ch)
})
}
pub fn left_paren<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Primitives::satisfy(|ch| ch == '(' || ch == '[' || ch == '{')
}
pub fn right_paren<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Primitives::satisfy(|ch| ch == ')' || ch == ']' || ch == '}')
}
pub fn scheme_delimiter<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Primitives::satisfy(|ch| {
ch.is_whitespace() ||
"()[]{}\"';#|\\".contains(ch)
})
}
pub fn string_escape<'a>() -> impl Fn(Input<'a>) -> ParseResult<'a, char> {
Primitives::satisfy(|ch| "\\\"nrt".contains(ch))
}
}
pub struct ErrorUtils;
impl ErrorUtils {
pub fn with_context<'a, T, P>(
parser: P,
context: &'static str,
) -> impl Fn(Input<'a>) -> ParseResult<'a, T>
where
P: Fn(Input<'a>) -> ParseResult<'a, T>,
{
move |input: Input<'a>| {
parser(input).map_err(|err| Box::new(err.as_ref().clone().with_context(context.to_string())))
}
}
pub fn custom_error<'a, T>(
message: String,
span: Span,
) -> ParseResult<'a, T> {
Err(Box::new(ParseError::new(message, span)))
}
pub fn expected_error<'a, T>(
expected: String,
actual: String,
span: Span,
) -> ParseResult<'a, T> {
Err(Box::new(ParseError::new(
format!("Expected {expected}, found {actual}"),
span,
).with_expected(expected)
.with_actual(actual)))
}
pub fn detailed_error<'a, T>(
message: String,
span: Span,
expected: Vec<String>,
actual: String,
context: Vec<String>,
) -> ParseResult<'a, T> {
let mut error = ParseError::new(message, span).with_actual(actual);
for exp in expected {
error = error.with_expected(exp);
}
for ctx in context {
error = error.with_context(ctx);
}
Err(Box::new(error))
}
pub fn eof_error<'a, T>(expected: String) -> ParseResult<'a, T> {
Err(Box::new(ParseError::new(
"Unexpected end of input".to_string(),
Span::new(0, 0)
).with_expected(expected)))
}
pub fn invalid_char_error<'a, T>(
ch: char,
position: usize,
expected_description: String,
) -> ParseResult<'a, T> {
Err(Box::new(ParseError::new(
format!("Invalid character '{ch}'"),
Span::new(position, position + ch.len_utf8())
).with_expected(expected_description)
.with_actual(ch.to_string())))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tag_success() {
let parser = Primitives::tag("hello");
let result = parser("hello world");
assert!(result.is_ok());
let (remaining, matched) = result.unwrap();
assert_eq!(remaining, " world");
assert_eq!(matched, "hello");
}
#[test]
fn test_tag_failure() {
let parser = Primitives::tag("hello");
let result = parser("hi world");
assert!(result.is_err());
}
#[test]
fn test_char_success() {
let parser = Primitives::char('a');
let result = parser("abc");
assert!(result.is_ok());
let (remaining, matched) = result.unwrap();
assert_eq!(remaining, "bc");
assert_eq!(matched, 'a');
}
#[test]
fn test_satisfy() {
let parser = Primitives::satisfy(|ch| ch.is_ascii_digit());
let result = parser("123");
assert!(result.is_ok());
let (remaining, matched) = result.unwrap();
assert_eq!(remaining, "23");
assert_eq!(matched, '1');
}
#[test]
fn test_scheme_identifier_start() {
let parser = SchemePreimitives::identifier_start();
assert!(parser("abc").is_ok());
assert!(parser("+123").is_ok());
assert!(parser("$var").is_ok());
assert!(parser("123").is_err());
assert!(parser("(").is_err());
}
#[test]
fn test_parse_state() {
let mut state = ParseState::new("hello\nworld");
assert_eq!(state.line, 1);
assert_eq!(state.column, 1);
assert_eq!(state.position, 0);
state.advance(6).unwrap(); assert_eq!(state.line, 2);
assert_eq!(state.column, 1);
assert_eq!(state.position, 6);
let span = state.current_span(5);
assert_eq!(span.start, 6);
assert_eq!(span.end, 11);
}
}