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
+569
@@ -0,0 +1,569 @@
|
||||
use crate::combinator::empty;
|
||||
use crate::combinator::fail;
|
||||
use crate::combinator::opt;
|
||||
use crate::combinator::trace;
|
||||
use crate::error::ParserError;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::StreamIsPartial;
|
||||
use crate::Parser;
|
||||
use crate::Result;
|
||||
|
||||
/// Parses an expression based on operator precedence.
|
||||
///
|
||||
/// It uses a Pratt parsing algorithm, where operators are
|
||||
/// associated with a binding power. The higher the power,
|
||||
/// the more tightly an operator will bind to its operands.
|
||||
///
|
||||
/// This method returns an [`Expression`], which configures
|
||||
/// the Pratt parser.
|
||||
///
|
||||
/// Each operator type is configured with [`Prefix`], [`Postfix`],
|
||||
/// and [`Infix`]. These describe the operator's binding power,
|
||||
/// a function that applies the operator to its operand, and the
|
||||
/// operator's associativity (infix only).
|
||||
///
|
||||
/// For a more full-featured example, look at the [C-style Expression][crate::_topic::arithmetic#c-style-expression]
|
||||
/// topic.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Parsing a simple arithmetic expression without parenthesis.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use winnow::prelude::*;
|
||||
/// # use winnow::error::ContextError;
|
||||
/// # use winnow::ascii::digit1;
|
||||
/// # use winnow::combinator::{dispatch, fail};
|
||||
/// # use winnow::token::any;
|
||||
/// use winnow::combinator::expression;
|
||||
/// use winnow::combinator::{Prefix, Postfix, Infix};
|
||||
///
|
||||
/// fn parser<'i>() -> impl Parser<&'i str, i32, ContextError> {
|
||||
/// move |i: &mut &str| {
|
||||
/// use Infix::*;
|
||||
/// expression(digit1.parse_to::<i32>()) // operands are 32-bit integers
|
||||
/// .prefix(dispatch! {any;
|
||||
/// '-' => Prefix(12, |_, a: i32| Ok(-a)),
|
||||
/// _ => fail,
|
||||
/// })
|
||||
/// .infix(dispatch! {any;
|
||||
/// '+' => Left(5, |_, a, b| Ok(a + b)),
|
||||
/// '-' => Left(5, |_, a, b| Ok(a - b)),
|
||||
/// '*' => Left(7, |_, a, b| Ok(a * b)),
|
||||
/// '/' => Left(7, |_, a: i32, b| Ok(a.checked_div(b).unwrap_or_default())),
|
||||
/// _ => fail,
|
||||
/// })
|
||||
/// .postfix(dispatch! {any;
|
||||
/// '!' => Postfix(15, |_, a| if a < 1 { Ok(1) } else { Ok((1..=a).fold(1, |acc, a| acc*a)) }),
|
||||
/// _ => fail,
|
||||
/// })
|
||||
/// .parse_next(i)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(parser().parse("1+1"), Ok(2));
|
||||
/// assert_eq!(parser().parse("0!"), Ok(1));
|
||||
/// assert_eq!(parser().parse("-1*5*2*10+30/3!"), Ok(-95));
|
||||
/// ```
|
||||
#[doc(alias = "pratt")]
|
||||
#[doc(alias = "separated")]
|
||||
#[doc(alias = "shunting_yard")]
|
||||
#[doc(alias = "precedence_climbing")]
|
||||
#[inline(always)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn expression<I, ParseOperand, O, E>(
|
||||
parse_operand: ParseOperand,
|
||||
) -> Expression<
|
||||
I,
|
||||
O,
|
||||
ParseOperand,
|
||||
impl Parser<I, Prefix<I, O, E>, E>,
|
||||
impl Parser<I, Postfix<I, O, E>, E>,
|
||||
impl Parser<I, Infix<I, O, E>, E>,
|
||||
E,
|
||||
>
|
||||
where
|
||||
I: Stream + StreamIsPartial,
|
||||
ParseOperand: Parser<I, O, E>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
Expression {
|
||||
precedence_level: 0,
|
||||
parse_operand,
|
||||
parse_prefix: fail,
|
||||
parse_postfix: fail,
|
||||
parse_infix: fail,
|
||||
i: Default::default(),
|
||||
o: Default::default(),
|
||||
e: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper struct for [`expression()`].
|
||||
///
|
||||
/// Holds the configuration for the Pratt parser, including
|
||||
/// the operator and operand parsers. A precedence level can
|
||||
/// also be set, which is useful to disambiguate parse trees
|
||||
/// based on the parent operator's precedence.
|
||||
///
|
||||
/// Implements [`Parser`]. When parsing an input, it applies
|
||||
/// the Pratt parser.
|
||||
pub struct Expression<I, O, ParseOperand, Pre, Post, Pix, E>
|
||||
where
|
||||
I: Stream + StreamIsPartial,
|
||||
ParseOperand: Parser<I, O, E>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
precedence_level: i64,
|
||||
parse_operand: ParseOperand,
|
||||
parse_prefix: Pre,
|
||||
parse_postfix: Post,
|
||||
parse_infix: Pix,
|
||||
i: core::marker::PhantomData<I>,
|
||||
o: core::marker::PhantomData<O>,
|
||||
e: core::marker::PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<I, O, ParseOperand, Pre, Post, Pix, E> Expression<I, O, ParseOperand, Pre, Post, Pix, E>
|
||||
where
|
||||
ParseOperand: Parser<I, O, E>,
|
||||
I: Stream + StreamIsPartial,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
/// Sets the prefix operator parser.
|
||||
///
|
||||
/// The parser should parse the input to a [`Prefix`],
|
||||
/// which contains the operator's binding power and
|
||||
/// a fold function which applies the operator to its
|
||||
/// operands.
|
||||
#[inline(always)]
|
||||
pub fn prefix<NewParsePrefix>(
|
||||
self,
|
||||
parser: NewParsePrefix,
|
||||
) -> Expression<I, O, ParseOperand, NewParsePrefix, Post, Pix, E>
|
||||
where
|
||||
NewParsePrefix: Parser<I, Prefix<I, O, E>, E>,
|
||||
{
|
||||
Expression {
|
||||
precedence_level: self.precedence_level,
|
||||
parse_operand: self.parse_operand,
|
||||
parse_prefix: parser,
|
||||
parse_postfix: self.parse_postfix,
|
||||
parse_infix: self.parse_infix,
|
||||
i: Default::default(),
|
||||
o: Default::default(),
|
||||
e: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the postfix operator parser.
|
||||
///
|
||||
/// The parser should parse the input to a [`Postfix`],
|
||||
/// which contains the operator's binding power and
|
||||
/// a fold function which applies the operator to its
|
||||
/// operands.
|
||||
#[inline(always)]
|
||||
pub fn postfix<NewParsePostfix>(
|
||||
self,
|
||||
parser: NewParsePostfix,
|
||||
) -> Expression<I, O, ParseOperand, Pre, NewParsePostfix, Pix, E>
|
||||
where
|
||||
NewParsePostfix: Parser<I, Postfix<I, O, E>, E>,
|
||||
{
|
||||
Expression {
|
||||
precedence_level: self.precedence_level,
|
||||
parse_operand: self.parse_operand,
|
||||
parse_prefix: self.parse_prefix,
|
||||
parse_postfix: parser,
|
||||
parse_infix: self.parse_infix,
|
||||
i: Default::default(),
|
||||
o: Default::default(),
|
||||
e: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the infix operator parser.
|
||||
///
|
||||
/// The parser should parse the input to a [`Infix`],
|
||||
/// which contains the operator's binding power and
|
||||
/// a fold function which applies the operator to its
|
||||
/// operands.
|
||||
#[inline(always)]
|
||||
pub fn infix<NewParseInfix>(
|
||||
self,
|
||||
parser: NewParseInfix,
|
||||
) -> Expression<I, O, ParseOperand, Pre, Post, NewParseInfix, E>
|
||||
where
|
||||
NewParseInfix: Parser<I, Infix<I, O, E>, E>,
|
||||
{
|
||||
Expression {
|
||||
precedence_level: self.precedence_level,
|
||||
parse_operand: self.parse_operand,
|
||||
parse_prefix: self.parse_prefix,
|
||||
parse_postfix: self.parse_postfix,
|
||||
parse_infix: parser,
|
||||
i: Default::default(),
|
||||
o: Default::default(),
|
||||
e: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the precedence level for the current instance of the parser.
|
||||
///
|
||||
/// It defaults to 0, which is traditionally treated as the "lowest"
|
||||
/// possible precedence when parsing an expression.
|
||||
///
|
||||
/// This is useful to disambiguate grammars based on the parent operator's
|
||||
/// precedence. This comes up primarily when parsing recursive expressions.
|
||||
///
|
||||
/// The parsing machinery underpinning [`Expression`] assumes that a "more
|
||||
/// tightly binding" operator is numerically large, while a "more loosely
|
||||
/// binding" operator is numerically small. For example, `13` is a higher
|
||||
/// precedence level than `1` because `13 > 1`.
|
||||
///
|
||||
/// Other ways of describing this relationship:
|
||||
/// - `13` has a higher precedence compared to `1`
|
||||
/// - `13` has a higher binding power compared to `1`
|
||||
///
|
||||
/// Note: Binding power and precedence both refer to the same concept and
|
||||
/// may be used interchangeably.
|
||||
///
|
||||
/// # Motivation
|
||||
///
|
||||
/// If you don't understand why this is useful to have, this section tries
|
||||
/// to explain in more detail.
|
||||
///
|
||||
/// The [C-style Expressions][crate::_topic::arithmetic#c-style-expression]
|
||||
/// example has source code for parsing the expression described below, and
|
||||
/// can provide a clearer usage example.
|
||||
///
|
||||
/// Consider the following expression in the C language:
|
||||
///
|
||||
/// ```c
|
||||
/// int x = (1 == 1 ? 0 : 1, -123); // <-- let's parse this
|
||||
/// printf("%d\n", x); // -123
|
||||
/// ```
|
||||
///
|
||||
/// Let's look at the right-hand side of the expression on the first line,
|
||||
/// and replace some of the sub-expressions with symbols:
|
||||
///
|
||||
/// ```text
|
||||
/// (1 == 1 ? 0 : 1, -123) // rhs
|
||||
/// (a ? b : c, d ) // symbolic
|
||||
/// (a ? b : c, d) // remove whitespace
|
||||
/// (, (? a b c) d) // prefix notation
|
||||
/// ```
|
||||
///
|
||||
/// Written symbolically:
|
||||
/// - `a` is the condition, like `1 == 1`
|
||||
/// - `b` is the value when the condition is true
|
||||
/// - `c` is the value when the condition is false
|
||||
/// - `d` is a secondary expression unrelated to the ternary
|
||||
///
|
||||
/// In prefix notation, it's easier to see the specific operators and what
|
||||
/// they bind to:
|
||||
/// - COMMA (`,`) binds to `(? a b c)` and `d`
|
||||
/// - TERNARY (`?`) binds to `a`, `b`, and `c`
|
||||
///
|
||||
/// ## Parsing `c` and `d`
|
||||
///
|
||||
/// Let's focus on parsing the sub-expressions `c` and `d`, as that
|
||||
/// motivates why a parser precedence level is necessary.
|
||||
///
|
||||
/// To parse `c`, we would really like to re-use the parser produced by
|
||||
/// [`expression()`], because `c` is really *any* valid expression that
|
||||
/// can be parsed by `expression()` already.
|
||||
///
|
||||
/// However, we can't re-use the parser naively. When parsing `c`, we need
|
||||
/// to "escape" from the inner parser when encountering the comma separating
|
||||
/// `c` from `d`.
|
||||
///
|
||||
/// The reason we have to "escape" is because of how operator precedence is
|
||||
/// defined in the C language: the comma operator has the lowest precedence
|
||||
/// among all the operators. When we're parsing `c`, we're in the context of
|
||||
/// the ternary operator. We don't want to parse any valid expression! Just
|
||||
/// what the ternary operator captures.
|
||||
///
|
||||
/// That's where the precedence level comes in: you specify the minimum
|
||||
/// precedence this parser is willing to accept. If you come across an
|
||||
/// expression in the top-level with a lower binding power than the starting
|
||||
/// precedence, you know to stop parsing.
|
||||
///
|
||||
/// The parsing machinery inside of [`Expression`] handles most of this for
|
||||
/// you, but it can't determine what the precedence level should be for a
|
||||
/// given expression. That's a language-specific detail, and it depends on
|
||||
/// what you want to parse.
|
||||
#[inline(always)]
|
||||
pub fn current_precedence_level(
|
||||
mut self,
|
||||
level: i64,
|
||||
) -> Expression<I, O, ParseOperand, Pre, Post, Pix, E> {
|
||||
self.precedence_level = level;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, Pop, Pre, Post, Pix, E> Parser<I, O, E> for Expression<I, O, Pop, Pre, Post, Pix, E>
|
||||
where
|
||||
I: Stream + StreamIsPartial,
|
||||
Pop: Parser<I, O, E>,
|
||||
Pix: Parser<I, Infix<I, O, E>, E>,
|
||||
Pre: Parser<I, Prefix<I, O, E>, E>,
|
||||
Post: Parser<I, Postfix<I, O, E>, E>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<O, E> {
|
||||
trace("expression", move |i: &mut I| {
|
||||
expression_impl(
|
||||
i,
|
||||
&mut self.parse_operand,
|
||||
&mut self.parse_prefix,
|
||||
&mut self.parse_postfix,
|
||||
&mut self.parse_infix,
|
||||
self.precedence_level,
|
||||
)
|
||||
})
|
||||
.parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque implementation of the Pratt parser.
|
||||
fn expression_impl<I, O, Pop, Pre, Post, Pix, E>(
|
||||
i: &mut I,
|
||||
parse_operand: &mut Pop,
|
||||
prefix: &mut Pre,
|
||||
postfix: &mut Post,
|
||||
infix: &mut Pix,
|
||||
min_power: i64,
|
||||
) -> Result<O, E>
|
||||
where
|
||||
I: Stream + StreamIsPartial,
|
||||
Pop: Parser<I, O, E>,
|
||||
Pix: Parser<I, Infix<I, O, E>, E>,
|
||||
Pre: Parser<I, Prefix<I, O, E>, E>,
|
||||
Post: Parser<I, Postfix<I, O, E>, E>,
|
||||
E: ParserError<I>,
|
||||
{
|
||||
let operand = opt(trace("operand", parse_operand.by_ref())).parse_next(i)?;
|
||||
let mut operand = if let Some(operand) = operand {
|
||||
operand
|
||||
} else {
|
||||
// Prefix unary operators
|
||||
let len = i.eof_offset();
|
||||
let Prefix(power, fold_prefix) = trace("prefix", prefix.by_ref()).parse_next(i)?;
|
||||
// infinite loop check: the parser must always consume
|
||||
if i.eof_offset() == len {
|
||||
return Err(E::assert(i, "`prefix` parsers must always consume"));
|
||||
}
|
||||
let operand = expression_impl(i, parse_operand, prefix, postfix, infix, power)?;
|
||||
fold_prefix(i, operand)?
|
||||
};
|
||||
|
||||
// A variable to stop the `'parse` loop when `Assoc::Neither` with the same
|
||||
// precedence is encountered e.g. `a == b == c`. `Assoc::Neither` has similar
|
||||
// associativity rules as `Assoc::Left`, but we stop parsing when the next operator
|
||||
// is the same as the current one.
|
||||
let mut prev_op_is_neither = None;
|
||||
'parse: while i.eof_offset() > 0 {
|
||||
// Postfix unary operators
|
||||
let start = i.checkpoint();
|
||||
if let Some(Postfix(power, fold_postfix)) =
|
||||
opt(trace("postfix", postfix.by_ref())).parse_next(i)?
|
||||
{
|
||||
// control precedence over the prefix e.g.:
|
||||
// `--(i++)` or `(--i)++`
|
||||
if power < min_power {
|
||||
i.reset(&start);
|
||||
break 'parse;
|
||||
}
|
||||
operand = fold_postfix(i, operand)?;
|
||||
|
||||
continue 'parse;
|
||||
}
|
||||
|
||||
// Infix binary operators
|
||||
let start = i.checkpoint();
|
||||
let parse_result = opt(trace("infix", infix.by_ref())).parse_next(i)?;
|
||||
if let Some(infix_op) = parse_result {
|
||||
let mut is_neither = None;
|
||||
let (lpower, rpower, fold_infix) = match infix_op {
|
||||
Infix::Right(p, f) => (p, p - 1, f),
|
||||
Infix::Left(p, f) => (p, p + 1, f),
|
||||
Infix::Neither(p, f) => {
|
||||
is_neither = Some(p);
|
||||
(p, p + 1, f)
|
||||
}
|
||||
};
|
||||
if lpower < min_power
|
||||
// MSRV: `is_some_and`
|
||||
|| match prev_op_is_neither {
|
||||
None => false,
|
||||
Some(p) => lpower == p,
|
||||
}
|
||||
{
|
||||
i.reset(&start);
|
||||
break 'parse;
|
||||
}
|
||||
prev_op_is_neither = is_neither;
|
||||
let rhs = expression_impl(i, parse_operand, prefix, postfix, infix, rpower)?;
|
||||
operand = fold_infix(i, operand, rhs)?;
|
||||
|
||||
continue 'parse;
|
||||
}
|
||||
|
||||
break 'parse;
|
||||
}
|
||||
|
||||
Ok(operand)
|
||||
}
|
||||
|
||||
/// Define an [`expression()`]'s prefix operator
|
||||
///
|
||||
/// It requires an operator binding power, as well as a
|
||||
/// fold function which applies the operator.
|
||||
pub struct Prefix<I, O, E>(
|
||||
/// Binding power
|
||||
pub i64,
|
||||
/// Unary operator
|
||||
pub fn(&mut I, O) -> Result<O, E>,
|
||||
);
|
||||
|
||||
impl<I, O, E> Clone for Prefix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn clone(&self) -> Self {
|
||||
Prefix(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream, O, E: ParserError<I>> Parser<I, Prefix<I, O, E>, E> for Prefix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<Prefix<I, O, E>, E> {
|
||||
empty.value(self.clone()).parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Define an [`expression()`]'s postfix operator
|
||||
///
|
||||
/// It requires an operator binding power, as well as a
|
||||
/// fold function which applies the operator.
|
||||
pub struct Postfix<I, O, E>(
|
||||
/// Binding power
|
||||
pub i64,
|
||||
/// Unary operator
|
||||
pub fn(&mut I, O) -> Result<O, E>,
|
||||
);
|
||||
|
||||
impl<I, O, E> Clone for Postfix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn clone(&self) -> Self {
|
||||
Postfix(self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream, O, E: ParserError<I>> Parser<I, Postfix<I, O, E>, E> for Postfix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<Postfix<I, O, E>, E> {
|
||||
empty.value(self.clone()).parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
/// Define an [`expression()`]'s infix operator
|
||||
///
|
||||
/// It requires an operator binding power, as well as a
|
||||
/// fold function which applies the operator.
|
||||
pub enum Infix<I, O, E> {
|
||||
/// Left-associative operator
|
||||
///
|
||||
/// The operators will bind more tightly to their rightmost operands.
|
||||
///
|
||||
/// e.g `A op B op C` -> `(A op B) op C`
|
||||
Left(
|
||||
/// Binding power
|
||||
i64,
|
||||
/// Binary operator
|
||||
fn(&mut I, O, O) -> Result<O, E>,
|
||||
),
|
||||
/// Right-associative operator
|
||||
///
|
||||
/// The operators will bind more tightly to their leftmost operands.
|
||||
///
|
||||
/// e.g `A op B op C` -> `A op (B op C)`
|
||||
Right(
|
||||
/// Binding power
|
||||
i64,
|
||||
/// Binary operator
|
||||
fn(&mut I, O, O) -> Result<O, E>,
|
||||
),
|
||||
/// Neither left or right associative
|
||||
///
|
||||
/// `Infix::Neither` has similar associativity rules as `Assoc::Left`, but we stop
|
||||
/// parsing when the next operator is the same as the current one.
|
||||
///
|
||||
/// e.g. `a == b == c` -> `(a == b)`, fail: `(== c)`
|
||||
Neither(
|
||||
/// Binding power
|
||||
i64,
|
||||
/// Binary operator
|
||||
fn(&mut I, O, O) -> Result<O, E>,
|
||||
),
|
||||
}
|
||||
|
||||
impl<I, O, E> Clone for Infix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Infix::Left(p, f) => Infix::Left(*p, *f),
|
||||
Infix::Right(p, f) => Infix::Right(*p, *f),
|
||||
Infix::Neither(p, f) => Infix::Neither(*p, *f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Stream, O, E: ParserError<I>> Parser<I, Infix<I, O, E>, E> for Infix<I, O, E> {
|
||||
#[inline(always)]
|
||||
fn parse_next(&mut self, input: &mut I) -> Result<Infix<I, O, E>, E> {
|
||||
empty.value(self.clone()).parse_next(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ascii::digit1;
|
||||
use crate::combinator::fail;
|
||||
use crate::dispatch;
|
||||
use crate::error::ContextError;
|
||||
use crate::token::any;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn parser<'i>() -> impl Parser<&'i str, i32, ContextError> {
|
||||
move |i: &mut &str| {
|
||||
use Infix::*;
|
||||
expression(digit1.parse_to::<i32>())
|
||||
.current_precedence_level(0)
|
||||
.prefix(dispatch! {any;
|
||||
'+' => Prefix(12, |_, a| Ok(a)),
|
||||
'-' => Prefix(12, |_, a: i32| Ok(-a)),
|
||||
_ => fail
|
||||
})
|
||||
.infix(dispatch! {any;
|
||||
'+' => Left(5, |_, a, b| Ok(a + b)),
|
||||
'-' => Left(5, |_, a, b| Ok(a - b)),
|
||||
'*' => Left(7, |_, a, b| Ok(a * b)),
|
||||
'/' => Left(7, |_, a, b| Ok(a / b)),
|
||||
'%' => Left(7, |_, a, b| Ok(a % b)),
|
||||
'^' => Left(9, |_, a, b| Ok(a ^ b)),
|
||||
_ => fail
|
||||
})
|
||||
.parse_next(i)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expression() {
|
||||
assert_eq!(parser().parse("-3+-3*4"), Ok(-15));
|
||||
assert_eq!(parser().parse("+2+3*4"), Ok(14));
|
||||
assert_eq!(parser().parse("2*3+4"), Ok(10));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user