lexington 0.3.0

A very simple library for lexing / parsing
Documentation
use lexington::{Any,Lexer,Match,Matcher,Scanner,Token,Within};
use lexington::{ShiftReduceParser,ShiftReduceRule};

/// A simple definition of the components of an S-expression.
#[derive(Copy,Clone,Debug,PartialEq)]    
enum Kind {
    WhiteSpace,
    LeftBrace,
    RightBrace,
    Symbol
}

/// A simple definition of an S-expression.
#[derive(Clone,Debug,PartialEq)]    
enum SExp<'a> {
    Symbol(&'a str),
    List(Vec<SExp<'a>>)
}

/// Parse an input string into an S-expression, or produce an error.
fn parse<'a>(input: &'a str) -> Result<SExp,()> {    
    // [ \n\t]+
    let whitespace = Any([' ','\n','\t']).one_or_more();
    // [0..9a..zA..Z_]+
    let symbol = Within('0'..='9').or(Within('a'..='z'))
        .or(Within('A'..='Z')).or('_').one_or_more();
    // Construct scanner
    let scanner = Match(whitespace,Kind::WhiteSpace)
        .and_match(symbol,Kind::Symbol)
        .and_match('(',Kind::LeftBrace)
        .and_match(')',Kind::RightBrace);
    // Construct lexer.
    let lexer = Lexer::new(input,scanner);
    // Rule for combining s-expressions
    let reduction_rule = |mut l:SExp<'a>,r:SExp<'a>| {
        match &mut l {
            SExp::List(vs) => { vs.push(r); Ok(l) }
            _ => Err(())
        }
    };
    // Rule for parsing strings into numbers
    ShiftReduceParser::new()
        .apply(reduction_rule)
        .terminate(Kind::Symbol,|tok| SExp::Symbol(&input[tok.range()]))
        .skip(Kind::WhiteSpace)
        .open(Kind::LeftBrace, SExp::List(Vec::new()))
        .close(Kind::RightBrace)
        .parse(lexer)    
}

///
macro_rules! list{
    ( $($a:expr),* ) => {
        {
            let mut v = Vec::new();
            $( v.push(SExp::Symbol($a)); )*
            SExp::List(v)
        }
    }
}

/// Identifiers differents kinds of error.
#[derive(Debug,PartialEq)]    
enum ParseError {
    UnexpectedEndOfFile,
    UnexpectedToken(Kind)
}

fn check_ok(input: &str, expected: SExp) {
    let actual = parse(input).unwrap();
    // Check go what was expected
    assert_eq!(actual,expected);
}

fn check_err(input: &str, _expecting: ParseError) {
    let actual = parse(input).unwrap_err();    
    // Check what we got
    // assert_eq!(actual,expecting);
    assert_eq!(actual,());    
}

use SExp::*;

#[test]
fn lisp_01() {
    check_ok("x",Symbol("x"));
}

#[test]
fn lisp_02() {
    check_ok("901",Symbol("901"));
}

#[test]
fn lisp_03() {
    check_ok("0x01",Symbol("0x01"));
}

#[test]
fn lisp_04() {
    check_ok("_hello",Symbol("_hello"));
}

#[test]
fn lisp_05() {
    check_ok(" xyz",Symbol("xyz"));
}

#[test]
fn lisp_06() {
    check_ok("()",list![]);
}

#[test]
fn lisp_07() {
    check_ok("(x)",list!["x"]);
}

#[test]
fn lisp_08() {
    check_ok("( x)",list!["x"]);
}

#[test]
fn lisp_09() {
    check_ok("(x )",list!["x"]);
}

#[test]
fn lisp_10() {
    check_ok("(x y)",list!["x","y"]);
}

#[test]
fn lisp_11() {
    check_ok("(())",List(vec![List(vec![])]));
}

#[test]
fn lisp_12() {
    check_ok("((x))",List(vec![list!["x"]]));
}

#[test]
fn lisp_13() {
    check_ok("(x (y))",List(vec![Symbol("x"),List(vec![Symbol("y")])]));
}

#[test]
fn lisp_14() {
    check_ok("((x) y)",List(vec![List(vec![Symbol("x")]),Symbol("y")]));
}

#[test]
fn lisp_15() {
    check_err("",ParseError::UnexpectedEndOfFile);
}

#[test]
fn lisp_16() {
    check_err(" ",ParseError::UnexpectedEndOfFile);
}

#[test]
fn lisp_17() {
    check_err("(",ParseError::UnexpectedEndOfFile);
}

#[test]
fn lisp_18() {
    check_err("(",ParseError::UnexpectedEndOfFile);
}

#[test]
fn lisp_19() {
    check_err("(()",ParseError::UnexpectedEndOfFile);
}

#[test]
fn lisp_20() {
    check_err(")",ParseError::UnexpectedToken(Kind::RightBrace));
}