lisp_expressions.rs raw

use std::io::Cursor;

use neotoma::{
    cache::ParsingCache,
    literal::Literal,
    optional::Optional,
    parser::{Parser, Source, parse},
    result::{Error, ParseResult},
    utf8class::Utf8Class,
};

// Lisp atom parser - handles numbers, symbols, and strings
#[derive(Clone)]
struct LispAtom;

impl<Ctx> Parser<Ctx> for LispAtom {
    type Output = String;

    fn read<S>(
        &self,
        source: &mut Source<S>,
        cache: &mut impl ParsingCache,
        _context: &mut Ctx,
    ) -> ParseResult<Self::Output>
    where
        S: neotoma::parser::Parsable,
    {
        // Try parsing a number first (require at least one digit)
        let number = Utf8Class::with_min("0123456789", 1);
        if let Ok(digits) = number.parse(source, cache, _context) {
            if !digits.is_empty() {
                return Ok(digits);
            }
        }

        // Try parsing a symbol (alphanumeric + some special chars, require at least one)
        let symbol_chars =
            Utf8Class::from_predicate_min(|c| c.is_alphanumeric() || "+-*/<>=!?".contains(c), 1);
        if let Ok(symbol) = symbol_chars.parse(source, cache, _context) {
            if !symbol.is_empty() {
                return Ok(symbol);
            }
        }

        Err(Error::NoMatch)
    }
}

// Lisp list parser - handles parenthesized expressions
#[derive(Clone)]
struct LispList;

impl<Ctx> Parser<Ctx> for LispList {
    type Output = Vec<LispExpr>;

    fn read<S>(
        &self,
        source: &mut Source<S>,
        cache: &mut impl ParsingCache,
        _context: &mut Ctx,
    ) -> ParseResult<Self::Output>
    where
        S: neotoma::parser::Parsable,
    {
        // Parse opening paren
        let open_paren = Literal::from_bytes_const(b"(");
        let _ = open_paren.parse(source, cache, _context)?;

        let mut expressions = Vec::new();

        // Parse expressions until closing paren
        loop {
            // Skip whitespace first
            let whitespace = Optional::new(Utf8Class::whitespace());
            let _ = whitespace.parse(source, cache, _context);

            // Try to parse closing paren - parse() handles backtracking automatically
            let close_paren = Literal::from_bytes_const(b")");
            if close_paren.parse(source, cache, _context).is_ok() {
                return Ok(expressions);
            }

            // Parse an expression - for now just atoms (no recursion yet)
            let atom = LispAtom;
            if let Ok(result) = atom.parse(source, cache, _context) {
                expressions.push(LispExpr::Atom(result));
            } else {
                // If we can't parse an atom and can't find closing paren, it's an error
                return Err(Error::NoMatch);
            }
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
enum LispExpr {
    Atom(String),
    List(Vec<LispExpr>),
}

// Main expression parser
#[derive(Clone)]
struct LispExpression;

impl<Ctx> Parser<Ctx> for LispExpression {
    type Output = LispExpr;

    fn read<S>(
        &self,
        source: &mut Source<S>,
        cache: &mut impl ParsingCache,
        _context: &mut Ctx,
    ) -> ParseResult<Self::Output>
    where
        S: neotoma::parser::Parsable,
    {
        // Try parsing an atom first
        let atom = LispAtom;
        if let Ok(result) = atom.parse(source, cache, _context) {
            return Ok(LispExpr::Atom(result));
        }

        // Try parsing a list
        let list = LispList;
        if let Ok(result) = list.parse(source, cache, _context) {
            return Ok(LispExpr::List(result));
        }

        Err(Error::NoMatch)
    }
}

#[test]
fn test_simple_lisp_atom() {
    let cursor = Cursor::new(b"42");
    let mut source = Source::new(cursor);

    let parser = LispExpression;
    let result = parse(parser, &mut source).unwrap();

    assert_eq!(result, LispExpr::Atom("42".to_string()));
}

#[test]
fn test_lisp_symbol() {
    let cursor = Cursor::new(b"+");
    let mut source = Source::new(cursor);

    let parser = LispExpression;
    let result = parse(parser, &mut source).unwrap();

    assert_eq!(result, LispExpr::Atom("+".to_string()));
}

#[test]
fn test_empty_lisp_list() {
    let cursor = Cursor::new(b"()");
    let mut source = Source::new(cursor);

    let parser = LispExpression;
    let result = parse(parser, &mut source).unwrap();

    assert_eq!(result, LispExpr::List(vec![]));
}

#[test]
fn test_simple_lisp_list() {
    let cursor = Cursor::new(b"(+ 1 2)");
    let mut source = Source::new(cursor);

    let parser = LispExpression;
    let result = parse(parser, &mut source).unwrap();

    assert_eq!(
        result,
        LispExpr::List(vec![
            LispExpr::Atom("+".to_string()),
            LispExpr::Atom("1".to_string()),
            LispExpr::Atom("2".to_string()),
        ])
    );
}

#[test]
fn lisp_parsing() {
    // Test various lisp expressions to demonstrate the parser capabilities
    let test_cases = vec![
        ("42", LispExpr::Atom("42".to_string())),
        ("hello", LispExpr::Atom("hello".to_string())),
        ("+", LispExpr::Atom("+".to_string())),
        ("()", LispExpr::List(vec![])),
        (
            "(+ 1 2)",
            LispExpr::List(vec![
                LispExpr::Atom("+".to_string()),
                LispExpr::Atom("1".to_string()),
                LispExpr::Atom("2".to_string()),
            ]),
        ),
        (
            "(hello world)",
            LispExpr::List(vec![
                LispExpr::Atom("hello".to_string()),
                LispExpr::Atom("world".to_string()),
            ]),
        ),
    ];

    for (input, expected) in test_cases {
        let cursor = Cursor::new(input.as_bytes());
        let mut source = Source::new(cursor);

        let parser = LispExpression;
        let result = parse(parser, &mut source).unwrap();

        assert_eq!(result, expected, "Failed to parse: {input}");
    }
}