expression/
expression.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//! This example demonstrates a very simple way to parse numbers— using [TokenizerContext::try_parse_float].
//! This is a nice, simple way of parsing single-type numbers. [TokenizerContext::try_parse_integer] is another way to do this.
//!
//! For more complex languages, [TokenizerContext::try_parse_number] parses both integers and floats, or, if more control
//! is needed, [TokenizerContext::consume_standard_number] will return a [String] of the next number-like section of code for manual
//! parsing.
//!
//! Numbers are by-far the most time-consuming part of creating a custom parser, so using the built-in systems is recommended, at least
//! for prototyping.

#![allow(dead_code)]

use alkale::{common::error::ErrorNotification, token::Token, TokenizerContext};

/// These are the tokens that will be present in the result. [MathSymbol::Number] holds data while the
/// rest are more symbolic.
#[derive(Debug, Clone)]
enum MathSymbol {
    Plus,
    Minus,
    Times,
    Divide,
    Modulo,
    OpenParen,
    CloseParen,
    Number(f64),
}

fn main() {
    use MathSymbol::*;

    // The test program we're going to tokenize.
    let program = "23 * (012 - 3) / 1_2_3 + 5e-3";

    // The TokenizerContext for our example program.
    let mut context = TokenizerContext::new(program.chars());

    // While there are more characters in the source code...
    while context.has_next() {
        // If the next character is one of these, push its respective token.
        let single_char_pushed = context.map_single_char_token(|char| match char {
            '+' => Some(Plus),
            '-' => Some(Minus),
            '*' => Some(Times),
            '/' => Some(Divide),
            '%' => Some(Modulo),
            '(' => Some(OpenParen),
            ')' => Some(CloseParen),
            _ => None,
        });

        // If the above properly pushed a token, skip this iteration.
        if single_char_pushed {
            continue;
        }

        // Try to parse a floating point number (f64), throw an error or push the token as necessary.
        if let Some((result, span)) = context.try_parse_float() {
            if let Ok(value) = result {
                context.push_token(Token::new(Number(value), span));
            } else {
                context.report(ErrorNotification(String::from("Malformed number."), span));
            }

            continue;
        }

        // If the above didn't occur, skip the next character. Throw an error if it isn't whitespace.
        if let Some((char, span)) = context.next_span() {
            if !char.is_whitespace() {
                context.report(ErrorNotification(
                    format!("Unexpected character `{}` in expression.", char),
                    span,
                ));
            }
        }
    }

    // Print the result— this example should have no error notifications.
    println!("{:#?}", context.result());
}