flexar 1.2.6

An extremely flexible lexer/parser (get it?) for writing your own programming language
Documentation
use flexar::{lext::Lext, flext::Flext, token_node::Token};

flexar::compiler_error! {
    [[Define] CompileErrors]
    (E001) "invalid character": "`", "` is an invalid character";
    (E002) "string not closed": "expected `\"` to close string";
}

flexar::lexer! {
    [[TokenType] lext, current, 'cycle]
    else flexar::compiler_error!((E001, lext.position()) current).throw();

    token_types {
        Slash => "/";
        Plus => "+";
        LParen => "(";
        RParen => ")";
        EE => "==";
        EEE => "===";
        EQ => "=";
        Dot => ".";
        Colon => ":";
        Str(val: String) => val;
        Int(val: u32) => val;
        Float(val: f32) => val;
    }

    Slash: /;
    Plus: +;
    LParen: '(';
    RParen: ')';
    Dot: .;
    Colon: :;
    [" \n\t"] >> ({ lext.advance(); lext = lext.spawn(); continue 'cycle; });

    // `=` stuff
    EEE: (= = =);
    EE: (= =);
    EQ: =;
    '"' child {
        { child.advance() };
        set string { String::new() };
        rsome current {
            ck (current, '"') {
                { child.advance() };
                done Str(string);
            };
            { string.push(current) };
        };
        throw E002(child.spawn().position());
    };
    ["0123456789"] child {
        set number { String::new() };
        set dot false;
        rsome (current, 'number) {
            set matched false;
            ck (current, ["0123456789"]) {
                mut matched true;
                { number.push(current) };
            };
            ck (current, '.') {
                if (dot) {
                    done Float(number.parse().unwrap());
                };
                mut matched true;
                mut dot true;
                { number.push(current) };
            };
            {if !matched {break 'number}};
        };
        { println!("{number}") };
        if (dot) { done Float(number.parse().unwrap()); };
        done Int(number.parse().unwrap());
    };
}

#[test]
fn test_single() {
    let contents = "+  /\n(  .:) /";
    let tokens = TokenType::tokenize(Lext::new(String::from("example"), contents));
    use TokenType as L;
    assert_tokens(&tokens, &[
        L::Plus,
        L::Slash,
        L::LParen,
        L::Dot,
        L::Colon,
        L::RParen,
        L::Slash,
    ]);
}

#[test]
fn test_multiple() {
    let contents = "=  ==\n=:  ====.==   =====";
    let tokens = TokenType::tokenize(Lext::new(String::from("example"), contents));
    use TokenType as L;
    assert_tokens(&tokens, &[
        L::EQ,
        L::EE,
        L::EQ,
        L::Colon,
        L::EEE,
        L::EQ,
        L::Dot,
        L::EE,
        L::EEE,
        L::EE,
    ]);
}

#[test]
fn test_string() {
    let contents = "+  /\n:( \"hello world?\"). /";
    let tokens = TokenType::tokenize(Lext::new(String::from("example"), contents));
    use TokenType as L;
    assert_tokens(&tokens, &[
        L::Plus,
        L::Slash,
        L::Colon,
        L::LParen,
        L::Str("hello world?".into()),
        L::RParen,
        L::Dot,
        L::Slash,
    ]);
}

#[test]
fn test_int() {
    let contents = "+  /\n:( 1234). /";
    let tokens = TokenType::tokenize(Lext::new(String::from("example"), contents));
    use TokenType as L;
    assert_tokens(&tokens, &[
        L::Plus,
        L::Slash,
        L::Colon,
        L::LParen,
        L::Int(1234),
        L::RParen,
        L::Dot,
        L::Slash,
    ]);
}

#[test]
fn test_float() {
    let contents = "+  /\n:( 12.34). /";
    let tokens = TokenType::tokenize(Lext::new(String::from("example"), contents));
    use TokenType as L;
    assert_tokens(&tokens, &[
        L::Plus,
        L::Slash,
        L::Colon,
        L::LParen,
        L::Float(12.34),
        L::RParen,
        L::Dot,
        L::Slash,
    ]);
}

fn assert_tokens(tokens: &[Token<TokenType>], expected: &[TokenType]) {
    tokens.iter()
        .enumerate()
        .for_each(|(i, x)| if x.token_type != expected[i] {
            panic!("Expected: {expected:?}\nGot: {:?}", tokens.iter().map(|x| &x.token_type).collect::<Box<[&TokenType]>>())
        });
}