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}");
}
}