feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+33
@@ -0,0 +1,33 @@
|
||||
mod parser;
|
||||
mod parser_ast;
|
||||
mod parser_lexer;
|
||||
|
||||
use winnow::prelude::*;
|
||||
|
||||
#[allow(clippy::eq_op, clippy::erasing_op)]
|
||||
fn arithmetic(c: &mut criterion::Criterion) {
|
||||
let data = " 2*2 / ( 5 - 1) + 3 / 4 * (2 - 7 + 567 *12 /2) + 3*(1+2*( 45 /2))";
|
||||
let expected = 2 * 2 / (5 - 1) + 3 * (1 + 2 * (45 / 2));
|
||||
|
||||
assert_eq!(parser::expr.parse(data), Ok(expected));
|
||||
assert_eq!(
|
||||
parser_ast::expr.parse(data).map(|ast| ast.eval()),
|
||||
Ok(expected)
|
||||
);
|
||||
assert_eq!(
|
||||
parser_lexer::expr2.parse(data).map(|ast| ast.eval()),
|
||||
Ok(expected)
|
||||
);
|
||||
c.bench_function("direct", |b| {
|
||||
b.iter(|| parser::expr.parse(data).unwrap());
|
||||
});
|
||||
c.bench_function("ast", |b| {
|
||||
b.iter(|| parser_ast::expr.parse(data).unwrap().eval());
|
||||
});
|
||||
c.bench_function("lexer", |b| {
|
||||
b.iter(|| parser_lexer::expr2.parse_peek(data).unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
criterion::criterion_group!(benches, arithmetic);
|
||||
criterion::criterion_main!(benches);
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
use winnow::prelude::*;
|
||||
|
||||
mod parser;
|
||||
mod parser_ast;
|
||||
mod parser_lexer;
|
||||
#[cfg(test)]
|
||||
mod test_parser;
|
||||
#[cfg(test)]
|
||||
mod test_parser_ast;
|
||||
#[cfg(test)]
|
||||
mod test_parser_lexer;
|
||||
|
||||
fn main() -> Result<(), lexopt::Error> {
|
||||
let args = Args::parse()?;
|
||||
|
||||
let input = args.input.as_deref().unwrap_or("1 + 1");
|
||||
if let Err(err) = calc(input, args.implementation) {
|
||||
println!("FAILED");
|
||||
println!("{err}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calc(
|
||||
input: &str,
|
||||
imp: Impl,
|
||||
) -> Result<(), winnow::error::ParseError<&str, winnow::error::ContextError>> {
|
||||
println!("{input} =");
|
||||
match imp {
|
||||
Impl::Eval => {
|
||||
let result = parser::expr.parse(input)?;
|
||||
println!(" {result}");
|
||||
}
|
||||
Impl::Ast => {
|
||||
let result = parser_ast::expr.parse(input)?;
|
||||
println!(" {:#?}={}", result, result.eval());
|
||||
}
|
||||
Impl::Lexer => {
|
||||
let tokens = parser_lexer::tokens.parse(input)?;
|
||||
println!(" {tokens:#?}");
|
||||
let tokens = parser_lexer::Tokens::new(&tokens);
|
||||
let result = parser_lexer::expr.parse(tokens).unwrap();
|
||||
println!(" {:#?}={}", result, result.eval());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Args {
|
||||
input: Option<String>,
|
||||
implementation: Impl,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum Impl {
|
||||
#[default]
|
||||
Eval,
|
||||
Ast,
|
||||
Lexer,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
fn parse() -> Result<Self, lexopt::Error> {
|
||||
use lexopt::prelude::*;
|
||||
|
||||
let mut res = Args::default();
|
||||
|
||||
let mut args = lexopt::Parser::from_env();
|
||||
while let Some(arg) = args.next()? {
|
||||
match arg {
|
||||
Long("impl") => {
|
||||
res.implementation = args.value()?.parse_with(|s| match s {
|
||||
"eval" => Ok(Impl::Eval),
|
||||
"ast" => Ok(Impl::Ast),
|
||||
"lexer" => Ok(Impl::Lexer),
|
||||
_ => Err("expected `eval`, `ast`"),
|
||||
})?;
|
||||
}
|
||||
Value(input) => {
|
||||
res.input = Some(input.string()?);
|
||||
}
|
||||
_ => return Err(arg.unexpected()),
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
use winnow::{
|
||||
ascii::{digit1 as digits, multispace0 as multispaces},
|
||||
combinator::alt,
|
||||
combinator::delimited,
|
||||
combinator::repeat,
|
||||
token::one_of,
|
||||
};
|
||||
|
||||
// Parser definition
|
||||
|
||||
pub(crate) fn expr(i: &mut &str) -> Result<i64> {
|
||||
let init = term.parse_next(i)?;
|
||||
|
||||
repeat(0.., (one_of(['+', '-']), term))
|
||||
.fold(
|
||||
move || init,
|
||||
|acc, (op, val): (char, i64)| {
|
||||
if op == '+' {
|
||||
acc + val
|
||||
} else {
|
||||
acc - val
|
||||
}
|
||||
},
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
// We read an initial factor and for each time we find
|
||||
// a * or / operator followed by another factor, we do
|
||||
// the math by folding everything
|
||||
pub(crate) fn term(i: &mut &str) -> Result<i64> {
|
||||
let init = factor.parse_next(i)?;
|
||||
|
||||
repeat(0.., (one_of(['*', '/']), factor))
|
||||
.fold(
|
||||
move || init,
|
||||
|acc, (op, val): (char, i64)| {
|
||||
if op == '*' {
|
||||
acc * val
|
||||
} else {
|
||||
acc / val
|
||||
}
|
||||
},
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
// We transform an integer string into a i64, ignoring surrounding whitespace
|
||||
// We look for a digit suite, and try to convert it.
|
||||
// If either str::from_utf8 or FromStr::from_str fail,
|
||||
// we fallback to the parens parser defined above
|
||||
pub(crate) fn factor(i: &mut &str) -> Result<i64> {
|
||||
delimited(
|
||||
multispaces,
|
||||
alt((digits.try_map(FromStr::from_str), parens)),
|
||||
multispaces,
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
// We parse any expr surrounded by parens, ignoring all whitespace around those
|
||||
fn parens(i: &mut &str) -> Result<i64> {
|
||||
delimited('(', expr, ')').parse_next(i)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
use winnow::{
|
||||
ascii::{digit1 as digits, multispace0 as multispaces},
|
||||
combinator::alt,
|
||||
combinator::delimited,
|
||||
combinator::repeat,
|
||||
token::one_of,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Expr {
|
||||
Value(i64),
|
||||
Add(Box<Expr>, Box<Expr>),
|
||||
Sub(Box<Expr>, Box<Expr>),
|
||||
Mul(Box<Expr>, Box<Expr>),
|
||||
Div(Box<Expr>, Box<Expr>),
|
||||
Paren(Box<Expr>),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub(crate) fn eval(&self) -> i64 {
|
||||
match self {
|
||||
Self::Value(v) => *v,
|
||||
Self::Add(lhs, rhs) => lhs.eval() + rhs.eval(),
|
||||
Self::Sub(lhs, rhs) => lhs.eval() - rhs.eval(),
|
||||
Self::Mul(lhs, rhs) => lhs.eval() * rhs.eval(),
|
||||
Self::Div(lhs, rhs) => lhs.eval() / rhs.eval(),
|
||||
Self::Paren(expr) => expr.eval(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Expr {
|
||||
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
|
||||
use Expr::{Add, Div, Mul, Paren, Sub, Value};
|
||||
match *self {
|
||||
Value(val) => write!(format, "{val}"),
|
||||
Add(ref left, ref right) => write!(format, "{left} + {right}"),
|
||||
Sub(ref left, ref right) => write!(format, "{left} - {right}"),
|
||||
Mul(ref left, ref right) => write!(format, "{left} * {right}"),
|
||||
Div(ref left, ref right) => write!(format, "{left} / {right}"),
|
||||
Paren(ref expr) => write!(format, "({expr})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expr(i: &mut &str) -> Result<Expr> {
|
||||
let init = term.parse_next(i)?;
|
||||
|
||||
repeat(0.., (one_of(['+', '-']), term))
|
||||
.fold(
|
||||
move || init.clone(),
|
||||
|acc, (op, val): (char, Expr)| {
|
||||
if op == '+' {
|
||||
Expr::Add(Box::new(acc), Box::new(val))
|
||||
} else {
|
||||
Expr::Sub(Box::new(acc), Box::new(val))
|
||||
}
|
||||
},
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
pub(crate) fn term(i: &mut &str) -> Result<Expr> {
|
||||
let init = factor.parse_next(i)?;
|
||||
|
||||
repeat(0.., (one_of(['*', '/']), factor))
|
||||
.fold(
|
||||
move || init.clone(),
|
||||
|acc, (op, val): (char, Expr)| {
|
||||
if op == '*' {
|
||||
Expr::Mul(Box::new(acc), Box::new(val))
|
||||
} else {
|
||||
Expr::Div(Box::new(acc), Box::new(val))
|
||||
}
|
||||
},
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
pub(crate) fn factor(i: &mut &str) -> Result<Expr> {
|
||||
delimited(
|
||||
multispaces,
|
||||
alt((digits.try_map(FromStr::from_str).map(Expr::Value), parens)),
|
||||
multispaces,
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn parens(i: &mut &str) -> Result<Expr> {
|
||||
delimited("(", expr, ")")
|
||||
.map(|e| Expr::Paren(Box::new(e)))
|
||||
.parse_next(i)
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
||||
use winnow::prelude::*;
|
||||
use winnow::Result;
|
||||
use winnow::{
|
||||
ascii::{digit1 as digits, multispace0 as multispaces},
|
||||
combinator::alt,
|
||||
combinator::dispatch,
|
||||
combinator::eof,
|
||||
combinator::fail,
|
||||
combinator::opt,
|
||||
combinator::peek,
|
||||
combinator::repeat,
|
||||
combinator::{delimited, preceded, terminated},
|
||||
error::ContextError,
|
||||
stream::TokenSlice,
|
||||
token::any,
|
||||
token::literal,
|
||||
token::one_of,
|
||||
token::take_till,
|
||||
};
|
||||
|
||||
/// Lex and parse
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn expr2(i: &mut &str) -> Result<Expr> {
|
||||
let tokens = tokens.parse_next(i)?;
|
||||
let mut tokens = Tokens::new(&tokens);
|
||||
expr.parse_next(&mut tokens)
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub(crate) struct Token<'s> {
|
||||
kind: TokenKind,
|
||||
raw: &'s str,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Token<'_> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Customized for brevity for a better `winnow/debug` experience
|
||||
match self.kind {
|
||||
TokenKind::Value => Debug::fmt(self.raw, fmt),
|
||||
TokenKind::Oper(oper) => Debug::fmt(&oper, fmt),
|
||||
TokenKind::OpenParen => fmt.write_str("OpenParen"),
|
||||
TokenKind::CloseParen => fmt.write_str("CloseParen"),
|
||||
TokenKind::Unknown => fmt.write_str("Unknown"),
|
||||
TokenKind::Eof => fmt.write_str("Eof"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<TokenKind> for Token<'_> {
|
||||
fn eq(&self, other: &TokenKind) -> bool {
|
||||
self.kind == *other
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum TokenKind {
|
||||
Value,
|
||||
Oper(Oper),
|
||||
OpenParen,
|
||||
CloseParen,
|
||||
Unknown,
|
||||
Eof,
|
||||
}
|
||||
|
||||
impl<'i> Parser<Tokens<'i>, &'i Token<'i>, ContextError> for TokenKind {
|
||||
fn parse_next(&mut self, input: &mut Tokens<'i>) -> Result<&'i Token<'i>> {
|
||||
literal(*self).parse_next(input).map(|t| &t[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Oper {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
|
||||
impl winnow::stream::ContainsToken<&'_ Token<'_>> for TokenKind {
|
||||
#[inline(always)]
|
||||
fn contains_token(&self, token: &'_ Token<'_>) -> bool {
|
||||
*self == token.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl winnow::stream::ContainsToken<&'_ Token<'_>> for &'_ [TokenKind] {
|
||||
#[inline]
|
||||
fn contains_token(&self, token: &'_ Token<'_>) -> bool {
|
||||
self.contains(&token.kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LEN: usize> winnow::stream::ContainsToken<&'_ Token<'_>> for &'_ [TokenKind; LEN] {
|
||||
#[inline]
|
||||
fn contains_token(&self, token: &'_ Token<'_>) -> bool {
|
||||
self.contains(&token.kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LEN: usize> winnow::stream::ContainsToken<&'_ Token<'_>> for [TokenKind; LEN] {
|
||||
#[inline]
|
||||
fn contains_token(&self, token: &'_ Token<'_>) -> bool {
|
||||
self.contains(&token.kind)
|
||||
}
|
||||
}
|
||||
|
||||
/// Lex tokens
|
||||
///
|
||||
/// See [`expr`] to parse the tokens
|
||||
pub(crate) fn tokens<'s>(i: &mut &'s str) -> Result<Vec<Token<'s>>> {
|
||||
let mut tokens: Vec<_> =
|
||||
preceded(multispaces, repeat(1.., terminated(token, multispaces))).parse_next(i)?;
|
||||
if let Some(eof) = opt(eof.map(|raw| Token {
|
||||
kind: TokenKind::Eof,
|
||||
raw,
|
||||
}))
|
||||
.parse_next(i)?
|
||||
{
|
||||
tokens.push(eof);
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn token<'s>(i: &mut &'s str) -> Result<Token<'s>> {
|
||||
dispatch! {peek(any);
|
||||
'0'..='9' => digits.value(TokenKind::Value),
|
||||
'(' => '('.value(TokenKind::OpenParen),
|
||||
')' => ')'.value(TokenKind::CloseParen),
|
||||
'+' => '+'.value(TokenKind::Oper(Oper::Add)),
|
||||
'-' => '-'.value(TokenKind::Oper(Oper::Sub)),
|
||||
'*' => '*'.value(TokenKind::Oper(Oper::Mul)),
|
||||
'/' => '/'.value(TokenKind::Oper(Oper::Div)),
|
||||
' '| '\t'| '\r'| '\n' => fail,
|
||||
_ => take_till(.., ('0'..='9', '(', ')', '+', '-', '*', '/')).value(TokenKind::Unknown),
|
||||
}
|
||||
.with_taken()
|
||||
.map(|(kind, raw)| Token { kind, raw })
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Expr {
|
||||
Value(i64),
|
||||
Add(Box<Expr>, Box<Expr>),
|
||||
Sub(Box<Expr>, Box<Expr>),
|
||||
Mul(Box<Expr>, Box<Expr>),
|
||||
Div(Box<Expr>, Box<Expr>),
|
||||
Paren(Box<Expr>),
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub(crate) fn eval(&self) -> i64 {
|
||||
match self {
|
||||
Self::Value(v) => *v,
|
||||
Self::Add(lhs, rhs) => lhs.eval() + rhs.eval(),
|
||||
Self::Sub(lhs, rhs) => lhs.eval() - rhs.eval(),
|
||||
Self::Mul(lhs, rhs) => lhs.eval() * rhs.eval(),
|
||||
Self::Div(lhs, rhs) => lhs.eval() / rhs.eval(),
|
||||
Self::Paren(expr) => expr.eval(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Expr {
|
||||
fn fmt(&self, format: &mut Formatter<'_>) -> fmt::Result {
|
||||
use Expr::{Add, Div, Mul, Paren, Sub, Value};
|
||||
match *self {
|
||||
Value(val) => write!(format, "{val}"),
|
||||
Add(ref left, ref right) => write!(format, "{left} + {right}"),
|
||||
Sub(ref left, ref right) => write!(format, "{left} - {right}"),
|
||||
Mul(ref left, ref right) => write!(format, "{left} * {right}"),
|
||||
Div(ref left, ref right) => write!(format, "{left} / {right}"),
|
||||
Paren(ref expr) => write!(format, "({expr})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type Tokens<'i> = TokenSlice<'i, Token<'i>>;
|
||||
|
||||
/// Parse the tokens lexed in [`tokens`]
|
||||
pub(crate) fn expr(i: &mut Tokens<'_>) -> Result<Expr> {
|
||||
let init = term.parse_next(i)?;
|
||||
|
||||
let expr = repeat(
|
||||
0..,
|
||||
(
|
||||
one_of([TokenKind::Oper(Oper::Add), TokenKind::Oper(Oper::Sub)]),
|
||||
term,
|
||||
),
|
||||
)
|
||||
.fold(
|
||||
move || init.clone(),
|
||||
|acc, (op, val): (&Token<'_>, Expr)| {
|
||||
if op.kind == TokenKind::Oper(Oper::Add) {
|
||||
Expr::Add(Box::new(acc), Box::new(val))
|
||||
} else {
|
||||
Expr::Sub(Box::new(acc), Box::new(val))
|
||||
}
|
||||
},
|
||||
)
|
||||
.parse_next(i)?;
|
||||
|
||||
opt(TokenKind::Eof).parse_next(i)?;
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
pub(crate) fn term(i: &mut Tokens<'_>) -> Result<Expr> {
|
||||
let init = factor.parse_next(i)?;
|
||||
|
||||
repeat(
|
||||
0..,
|
||||
(
|
||||
one_of([TokenKind::Oper(Oper::Mul), TokenKind::Oper(Oper::Div)]),
|
||||
factor,
|
||||
),
|
||||
)
|
||||
.fold(
|
||||
move || init.clone(),
|
||||
|acc, (op, val): (&Token<'_>, Expr)| {
|
||||
if op.kind == TokenKind::Oper(Oper::Mul) {
|
||||
Expr::Mul(Box::new(acc), Box::new(val))
|
||||
} else {
|
||||
Expr::Div(Box::new(acc), Box::new(val))
|
||||
}
|
||||
},
|
||||
)
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
pub(crate) fn factor(i: &mut Tokens<'_>) -> Result<Expr> {
|
||||
alt((
|
||||
TokenKind::Value.try_map(|t: &Token<'_>| t.raw.parse::<i64>().map(Expr::Value)),
|
||||
parens,
|
||||
))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn parens(i: &mut Tokens<'_>) -> Result<Expr> {
|
||||
delimited(TokenKind::OpenParen, expr, TokenKind::CloseParen)
|
||||
.map(|e| Expr::Paren(Box::new(e)))
|
||||
.parse_next(i)
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
use snapbox::assert_data_eq;
|
||||
use snapbox::prelude::*;
|
||||
use snapbox::str;
|
||||
use winnow::prelude::*;
|
||||
|
||||
use crate::parser::*;
|
||||
|
||||
#[test]
|
||||
fn factor_test() {
|
||||
let input = "3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
3,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
12,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = "537 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
537,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 24 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
24,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn term_test() {
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
8,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
8,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* 3 *2 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
8,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 48 / 3/2";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
8,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_test() {
|
||||
let input = " 1 + 2 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
3,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 + 6 - 4+ 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
17,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 1 + 2*3 + 4";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
11,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens_test() {
|
||||
let input = " ( 2 )";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
2,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* ( 3 + 4 ) ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
14,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2*2 / ( 5 - 1) + 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
4,
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
use snapbox::assert_data_eq;
|
||||
use snapbox::prelude::*;
|
||||
use snapbox::str;
|
||||
use winnow::prelude::*;
|
||||
|
||||
use crate::parser_ast::*;
|
||||
|
||||
#[test]
|
||||
fn factor_test() {
|
||||
let input = "3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = "537 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Value(
|
||||
537,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 24 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Value(
|
||||
24,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn term_test() {
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* 3 *2 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Div(
|
||||
Mul(
|
||||
Mul(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 48 / 3/2";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
Div(
|
||||
Div(
|
||||
Value(
|
||||
48,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_test() {
|
||||
let input = " 1 + 2 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 + 6 - 4+ 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Sub(
|
||||
Add(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
6,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 1 + 2*3 + 4";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Add(
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens_test() {
|
||||
let input = " ( 2 )";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Paren(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* ( 3 + 4 ) ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Paren(
|
||||
Add(
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 2*2 / ( 5 - 1) + 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Paren(
|
||||
Sub(
|
||||
Value(
|
||||
5,
|
||||
),
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
use snapbox::assert_data_eq;
|
||||
use snapbox::prelude::*;
|
||||
use snapbox::str;
|
||||
use winnow::prelude::*;
|
||||
|
||||
use crate::parser_lexer::*;
|
||||
|
||||
#[test]
|
||||
fn lex_test() {
|
||||
let input = "3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
[
|
||||
"3",
|
||||
Eof,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(tokens.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 24 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
[
|
||||
"24",
|
||||
Eof,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(tokens.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
[
|
||||
"12",
|
||||
Mul,
|
||||
"2",
|
||||
Div,
|
||||
"3",
|
||||
Eof,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(tokens.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2*2 / ( 5 - 1) + 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
"",
|
||||
[
|
||||
"2",
|
||||
Mul,
|
||||
"2",
|
||||
Div,
|
||||
OpenParen,
|
||||
"5",
|
||||
Sub,
|
||||
"1",
|
||||
CloseParen,
|
||||
Add,
|
||||
"3",
|
||||
Eof,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
assert_data_eq!(tokens.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factor_test() {
|
||||
let input = "3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = "537 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Value(
|
||||
537,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 24 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Value(
|
||||
24,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(factor.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn term_test() {
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* 3 *2 *2 / 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Div(
|
||||
Mul(
|
||||
Mul(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
|
||||
let input = " 48 / 3/2";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
(
|
||||
[
|
||||
Eof,
|
||||
],
|
||||
Div(
|
||||
Div(
|
||||
Value(
|
||||
48,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(term.parse_peek(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expr_test() {
|
||||
let input = " 1 + 2 ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 12 + 6 - 4+ 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Sub(
|
||||
Add(
|
||||
Value(
|
||||
12,
|
||||
),
|
||||
Value(
|
||||
6,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 1 + 2*3 + 4";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Add(
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parens_test() {
|
||||
let input = " ( 2 )";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Paren(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 2* ( 3 + 4 ) ";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Paren(
|
||||
Add(
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
Value(
|
||||
4,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
|
||||
let input = " 2*2 / ( 5 - 1) + 3";
|
||||
let expected = str![[r#"
|
||||
Ok(
|
||||
Add(
|
||||
Div(
|
||||
Mul(
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
Value(
|
||||
2,
|
||||
),
|
||||
),
|
||||
Paren(
|
||||
Sub(
|
||||
Value(
|
||||
5,
|
||||
),
|
||||
Value(
|
||||
1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Value(
|
||||
3,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
"#]];
|
||||
let input = tokens.parse(input).unwrap();
|
||||
let input = Tokens::new(&input);
|
||||
assert_data_eq!(expr.parse(input).to_debug(), expected);
|
||||
}
|
||||
Reference in new issue
Block a user