#programming-language #language #syntax #token #parser

ketchup

A parser that can *ketch-up* with your programming language

9 releases (5 stable)

1.1.1 Sep 11, 2024
1.1.0 Aug 20, 2024
0.1.2 Aug 11, 2024
0.0.0 Mar 15, 2024

#2318 in Parser implementations

Download history 71/week @ 2024-08-10 496/week @ 2024-08-17 25/week @ 2024-08-24 2/week @ 2024-08-31 129/week @ 2024-09-07 30/week @ 2024-09-14 33/week @ 2024-09-21 21/week @ 2024-09-28 2/week @ 2024-10-05 2/week @ 2024-10-12

562 downloads per month

MIT/Apache

24KB
290 lines

ketchup


A parser that can ketch - up with your programming language.

Example


for a full implementation/example check the examples directory

use ketchup::{error::KError, node::Node, parser::Parser, OperInfo, Space, Span};

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum Error {
    #[default]
    UnexpectedCharacter,
    EmptyParentheses,
    UnclosedParentheses,
    UnexpectedToken,
}

/// A simple logos lexer
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
    Number(u32),
    Plus,
    Minus,
    Star,
    Slash,
}

/// The operations / nodes that will be used
#[derive(Debug, Clone)]
pub enum Oper {
    Num(u32),
    Add,
    Sub,
    Mul,
    Div,
    Neg,
    Pos,
}

fn oper_generator(token: Token, tokens: &mut impl Iterator<Item = (Result<Token, Error>, Span)>, double_space: bool) -> Result<Option<(OperInfo<Oper>, Option<(Result<Token, Error>, Span)>)>, Vec<KError<Error>>> {
    use Token as T;
    use Oper as O;

    // precedence determines the order of operations, lower the precedence the 'smaller' it is
    // space determines how much many input nodes it takes, eg `Space::None` is `x`, `Space::Single` is `x input`, `Space::Double` is `input1 x input2`
    // oper is just the kind of operation it is, like a number, addition, etc
    let (precedence, space, oper) = match (token, double_space) {
        // no space
        (T::Number(x), _) => (0, Space::None, O::Num(x)),

        // single space
        (T::Plus, false) => (1, Space::Single, O::Pos),
        (T::Minus, false) => (1, Space::Single, O::Neg),

        // double space
        (T::Plus, true) => (3, Space::Double, O::Add),
        (T::Minus, true) => (3, Space::Double, O::Sub),
        (T::Star, _) => (2, Space::Double, O::Mul),
        (T::Slash, _) => (2, Space::Double, O::Div),
    };

    Ok(Some((OperInfo {
        oper,
        span: 0..0, // should be used with logos to get the actual span
        space,
        precedence,
    }, tokens.next())))
}

fn throw(error: KError<Error>) {
    println!("err: {error:?}");
}

fn main() {
    let mut tokens = [(Ok(Token::Number(1)), 0..1), (Ok(Token::Plus), 1..2), (Ok(Token::Number(2)), 2..3), (Ok(Token::Star), 3..4), (Ok(Token::Number(3)), 4..5)].into_iter();
    let first_tok = tokens.next();
    let parser = Parser::<'_, Token, Oper, _, Vec<Node<Oper>>, _, Error>::new(&mut tokens, oper_generator);

    // handle errors
    let (asa, trailing_tok) = match parser.parse(first_tok) {
        Ok(asa) => asa,
        Err(errs) => {
            for err in errs {
                throw(err);
            } panic!("an error occured");
        },
    };

    // make sure that there aren't any tokens that haven't been consumed
    if let Some((_, span)) = trailing_tok {
        throw(KError::Other(span, Error::UnexpectedToken));
        panic!("an error occured");
    }

    // print abstract syntax array
    println!("{asa:?}");
}

No runtime deps