//! This example demonstrates a very simple way to parse numbers in an expression-like format. #![allow(dead_code)] use alkale::{ format_notification, map_single_char_token, notification::{NotificationBuilder, NotificationSeverity}, span::Spanned, token::Token, FinalizedLexerResult, LexerResult, SourceCodeScanner, }; use criterion::{criterion_group, BenchmarkId, Criterion}; /// These are the tokens that will be present in the resulting tokens. #[derive(Debug, Clone)] pub enum ExprToken<'a> { OpenParen, // ( CloseParen, // ) Add, // + Sub, // - Mul, // * Div, // / Variable(&'a str), // abc | name | ... Number(f64), // 2.3 | 1 | 5e-2 | ... } /// Convert an expression string into a [LexerResult] containing tokens /// and notifications. fn expression_lexer(source: &str) -> FinalizedLexerResult { // Create a LexerContext from our source code. let context = SourceCodeScanner::new(source); let mut result = LexerResult::new(); while context.has_next() { // If the next character matches any of these, push the token and its span to the context, // consume the character from the source code, and skip the rest of this lexer iteration. map_single_char_token!(&context, &mut result, '(' => ExprToken::OpenParen, ')' => ExprToken::CloseParen, '+' => ExprToken::Add, '-' => ExprToken::Sub, '*' => ExprToken::Mul, '/' => ExprToken::Div ); // If an identifier exists here in the source code, consume it and push it // as a token. if let Some(Spanned { span, data }) = context.try_consume_standard_identifier() { result.push_token(Token::new(ExprToken::Variable(data), span)); continue; } // Attempt to parse a number. If one was found and it was valid, push // a token for it, otherwise report an error notification. if let Some(Spanned { data, span }) = context.try_parse_float() { if let Ok(number) = data { // The returned number is valid, push a token. result.push_token(Token::new(ExprToken::Number(number), span)); } else { // The returned number failed to parse— report a notification. NotificationBuilder::new("Floating-point number is malformed") .severity(NotificationSeverity::Error) .span(span) .report(&mut result); } continue; } // If more characters exist, take one and throw an error if it's not whitespace. if let Some(Spanned { data, span }) = context.next_span() { if !data.is_whitespace() { format_notification!("Unrecognized character '{data}'") .severity(NotificationSeverity::Error) .span(span) .report(&mut result); } } } result.finalize() } fn bench(criterion: &mut Criterion) { static CASES: &str = include_str!("expression_cases.txt"); let mut group = criterion.benchmark_group("expression"); for (index, case) in CASES.lines().enumerate() { group.bench_with_input(BenchmarkId::from_parameter(index), case, |b, c| { b.iter(|| expression_lexer(c)); }); } } criterion_group!(benches, bench);