Math module
Yes this is a full math parser and interpreter
This commit is contained in:
parent
6774bef64c
commit
93131165ee
8 changed files with 536 additions and 4 deletions
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -20,6 +20,7 @@ dependencies = [
|
|||
"nucleo-matcher",
|
||||
"regex",
|
||||
"shlex",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -698,6 +699,26 @@ version = "0.13.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
|
|
|
@ -9,3 +9,4 @@ gtk4-layer-shell = "0.5.0"
|
|||
nucleo-matcher = "0.3.1"
|
||||
regex = "1.11.1"
|
||||
shlex = "1.3.0"
|
||||
thiserror = "2.0.12"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod math;
|
||||
pub mod modules;
|
||||
pub mod widget;
|
||||
|
||||
|
|
65
src/math/eval.rs
Normal file
65
src/math/eval.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::math::{BinMathOp, MathExpr, Number, UnMathOp};
|
||||
|
||||
pub fn eval_math_expr(expr: MathExpr) -> Result<Number, MathEvalError> {
|
||||
match expr {
|
||||
MathExpr::Number(n) => Ok(n),
|
||||
// TODO: Non floating point arithmetic please
|
||||
MathExpr::Sym("pi") => Ok(std::f64::consts::PI),
|
||||
MathExpr::Sym("e") => Ok(std::f64::consts::E),
|
||||
MathExpr::Sym(s) => Err(MathEvalError::UnkownSymbol(s)),
|
||||
MathExpr::UnOp(UnMathOp::Minus, expr) => Ok(-eval_math_expr(*expr)?),
|
||||
MathExpr::BinOp(BinMathOp::Add, lhs, rhs) => {
|
||||
Ok(eval_math_expr(*lhs)? + eval_math_expr(*rhs)?)
|
||||
}
|
||||
MathExpr::BinOp(BinMathOp::Sub, lhs, rhs) => {
|
||||
Ok(eval_math_expr(*lhs)? - eval_math_expr(*rhs)?)
|
||||
}
|
||||
MathExpr::BinOp(BinMathOp::Mult, lhs, rhs) => {
|
||||
Ok(eval_math_expr(*lhs)? * eval_math_expr(*rhs)?)
|
||||
}
|
||||
MathExpr::BinOp(BinMathOp::Div, lhs, rhs) => {
|
||||
Ok(eval_math_expr(*lhs)? / eval_math_expr(*rhs)?)
|
||||
}
|
||||
MathExpr::BinOp(BinMathOp::Pow, lhs, rhs) => {
|
||||
Ok(eval_math_expr(*lhs)?.powf(eval_math_expr(*rhs)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MathEvalError<'a> {
|
||||
#[error("Encountered unkown symbol while evaluating: {0}")]
|
||||
UnkownSymbol(&'a str),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::math::{
|
||||
Number,
|
||||
eval::{MathEvalError, eval_math_expr},
|
||||
parser::parse_expr_from_tokens,
|
||||
tokenizer::tokenize,
|
||||
};
|
||||
|
||||
fn eval_str(input: &str) -> Result<Number, MathEvalError> {
|
||||
eval_math_expr(parse_expr_from_tokens(tokenize(input).unwrap()).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_expressions() {
|
||||
assert_eq!(eval_str("1+1").unwrap(), 2.0);
|
||||
assert_eq!(eval_str("(1+1) * 6").unwrap(), 12.0);
|
||||
assert_eq!(eval_str("1/2").unwrap(), 0.5);
|
||||
assert_eq!(eval_str("2+2*3").unwrap(), 8.0);
|
||||
assert_eq!(eval_str("2+2*3**2").unwrap(), 20.0);
|
||||
assert_eq!(eval_str("pi").unwrap(), std::f64::consts::PI);
|
||||
assert_eq!(eval_str("e").unwrap(), std::f64::consts::E);
|
||||
assert_eq!(eval_str("2*pi").unwrap(), 2.0 * std::f64::consts::PI);
|
||||
assert_eq!(
|
||||
eval_str("pi * e").unwrap(),
|
||||
std::f64::consts::PI * std::f64::consts::E
|
||||
);
|
||||
}
|
||||
}
|
112
src/math/mod.rs
Normal file
112
src/math/mod.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
// TODO: This should be its own crate
|
||||
use std::fmt::Display;
|
||||
|
||||
pub mod eval;
|
||||
pub mod parser;
|
||||
pub mod tokenizer;
|
||||
|
||||
// TODO: Non floating point arithmetic please
|
||||
pub type Number = f64;
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
||||
pub enum MathExpr<'a> {
|
||||
Number(Number),
|
||||
Sym(&'a str),
|
||||
UnOp(UnMathOp, Box<MathExpr<'a>>),
|
||||
BinOp(BinMathOp, Box<MathExpr<'a>>, Box<MathExpr<'a>>),
|
||||
}
|
||||
|
||||
impl<'a> Display for MathExpr<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MathExpr::Number(n) => write!(f, "{n}"),
|
||||
MathExpr::Sym(s) => write!(f, "{s}"),
|
||||
MathExpr::UnOp(op, expr) => write!(f, "{op}{expr}"),
|
||||
MathExpr::BinOp(op, lhs, rhs) => write!(f, "({lhs}{op}{rhs})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
||||
pub enum UnMathOp {
|
||||
Minus,
|
||||
}
|
||||
|
||||
impl Display for UnMathOp {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UnMathOp::Minus => f.write_str("-"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
||||
pub enum BinMathOp {
|
||||
Add,
|
||||
Sub,
|
||||
Mult,
|
||||
Div,
|
||||
// TODO: Integer division
|
||||
// IntegerDiv,
|
||||
Pow,
|
||||
}
|
||||
|
||||
impl Display for BinMathOp {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BinMathOp::Add => f.write_str("+"),
|
||||
BinMathOp::Sub => f.write_str("-"),
|
||||
BinMathOp::Mult => f.write_str("*"),
|
||||
BinMathOp::Div => f.write_str("/"),
|
||||
BinMathOp::Pow => f.write_str("^"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
|
||||
pub enum MathToken<'a> {
|
||||
Number(Number),
|
||||
Sym(&'a str),
|
||||
LParen,
|
||||
RParen,
|
||||
Plus,
|
||||
Minus,
|
||||
Slash,
|
||||
Star,
|
||||
DoubleStar,
|
||||
Caret,
|
||||
}
|
||||
|
||||
impl<'a> Display for MathToken<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MathToken::Number(n) => write!(f, "{n}"),
|
||||
MathToken::Sym(s) => write!(f, "{s}"),
|
||||
MathToken::LParen => f.write_str("("),
|
||||
MathToken::RParen => f.write_str(")"),
|
||||
MathToken::Plus => f.write_str("+"),
|
||||
MathToken::Minus => f.write_str("-"),
|
||||
MathToken::Slash => f.write_str("/"),
|
||||
MathToken::Star => f.write_str("*"),
|
||||
MathToken::DoubleStar => f.write_str("**"),
|
||||
MathToken::Caret => f.write_str("^"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MathToken<'a> {
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
MathToken::Number(_) => 0,
|
||||
MathToken::Sym(_) => 0,
|
||||
MathToken::LParen => 0,
|
||||
MathToken::RParen => 0,
|
||||
MathToken::Plus => 1,
|
||||
MathToken::Minus => 1,
|
||||
MathToken::Slash => 2,
|
||||
MathToken::Star => 2,
|
||||
MathToken::DoubleStar => 3,
|
||||
MathToken::Caret => 3,
|
||||
}
|
||||
}
|
||||
}
|
210
src/math/parser.rs
Normal file
210
src/math/parser.rs
Normal file
|
@ -0,0 +1,210 @@
|
|||
use std::iter::Peekable;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::math::{BinMathOp, MathExpr, MathToken, UnMathOp};
|
||||
|
||||
// New thing on stack -> [what to consume, other thing to consume]
|
||||
// Done -> BinOp <eof>
|
||||
|
||||
// Grammer for LR parser
|
||||
// <> is a terminal
|
||||
//
|
||||
// <unOp> := '-'
|
||||
// <binOp1> := '+' | '-'
|
||||
// <binOp2> := '/' | '*'
|
||||
// <binOp3> := '**' | '^'
|
||||
// <function> := 'sqrt' | 'sin' | 'cos' | etc
|
||||
//
|
||||
//
|
||||
// F :=
|
||||
// | <number>
|
||||
// | <sym>
|
||||
// | '(' binOp1 ')'
|
||||
// | <unOp> F
|
||||
// | <function> '(' binOp1 ')'
|
||||
//
|
||||
//
|
||||
// binOp3 :=
|
||||
// | binOp3 <binOp3> F
|
||||
// | F
|
||||
//
|
||||
// binOp2 :=
|
||||
// | binOp2 <binOp2> binOp3
|
||||
// | binOp2 '(' expr ')' [Implicit multiplication] // TODO:
|
||||
// | binOp3
|
||||
//
|
||||
// binOp1 :=
|
||||
// | binOp1 <binOp1> binOp2
|
||||
// | binOp2
|
||||
//
|
||||
// goal := binOp1 <eof>
|
||||
|
||||
pub fn parse_expr_from_tokens(tokens: Vec<MathToken>) -> Result<MathExpr, MathParserError> {
|
||||
let mut tokens = tokens.into_iter().peekable();
|
||||
parse_expr(&mut tokens)
|
||||
}
|
||||
|
||||
fn parse_primary<'a>(
|
||||
tokens: &mut Peekable<impl Iterator<Item = MathToken<'a>>>,
|
||||
) -> Result<MathExpr<'a>, MathParserError<'a>> {
|
||||
match tokens.next() {
|
||||
Some(MathToken::Number(n)) => Ok(MathExpr::Number(n)),
|
||||
Some(MathToken::Sym(s)) => Ok(MathExpr::Sym(s)),
|
||||
Some(MathToken::LParen) => {
|
||||
let expr = parse_expr(tokens)?;
|
||||
match tokens.next() {
|
||||
Some(MathToken::RParen) => Ok(expr),
|
||||
Some(t) => Err(MathParserError::UnexpectedToken(t)),
|
||||
None => Err(MathParserError::EndOfExpression),
|
||||
}
|
||||
}
|
||||
Some(MathToken::Minus) => Ok(MathExpr::UnOp(
|
||||
UnMathOp::Minus,
|
||||
Box::new(parse_expr(tokens)?),
|
||||
)),
|
||||
// TODO: Function calls such as sqrt, sin, etc
|
||||
Some(t) => Err(MathParserError::UnexpectedToken(t)),
|
||||
None => Err(MathParserError::EndOfExpression),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr<'a>(
|
||||
tokens: &mut Peekable<impl Iterator<Item = MathToken<'a>>>,
|
||||
) -> Result<MathExpr<'a>, MathParserError<'a>> {
|
||||
parse_expr_prime(parse_primary(tokens)?, tokens, 0)
|
||||
}
|
||||
|
||||
fn parse_expr_prime<'a>(
|
||||
mut lhs: MathExpr<'a>,
|
||||
tokens: &mut Peekable<impl Iterator<Item = MathToken<'a>>>,
|
||||
min_precedence: u8,
|
||||
) -> Result<MathExpr<'a>, MathParserError<'a>> {
|
||||
loop {
|
||||
match tokens.peek() {
|
||||
// If the token has zero precedence it is not an operator and should stop expression
|
||||
// parsing.
|
||||
Some(t) if t.precedence() == 0 => return Ok(lhs),
|
||||
// If the token has a precedence lower then the minimum we don't parse it right now.
|
||||
Some(t) if t.precedence() < min_precedence => return Ok(lhs),
|
||||
// If the token has a precedence of at least min_precedence we parse it.
|
||||
Some(token) => {
|
||||
let op_precedence = token.precedence();
|
||||
let op = parse_bin_op_from_token(*token)?;
|
||||
// Advance iterator
|
||||
let _ = tokens.next();
|
||||
let mut rhs = parse_primary(tokens)?;
|
||||
loop {
|
||||
match tokens.peek() {
|
||||
Some(t) if t.precedence() > op_precedence => {
|
||||
rhs = parse_expr_prime(rhs, tokens, op_precedence + 1)?;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
lhs = MathExpr::BinOp(op, Box::new(lhs), Box::new(rhs));
|
||||
}
|
||||
None => return Ok(lhs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_bin_op_from_token(token: MathToken) -> Result<BinMathOp, MathParserError> {
|
||||
match token {
|
||||
MathToken::Plus => Ok(BinMathOp::Add),
|
||||
MathToken::Minus => Ok(BinMathOp::Sub),
|
||||
MathToken::Slash => Ok(BinMathOp::Div),
|
||||
MathToken::Star => Ok(BinMathOp::Mult),
|
||||
MathToken::DoubleStar => Ok(BinMathOp::Pow),
|
||||
MathToken::Caret => Ok(BinMathOp::Pow),
|
||||
t => Err(MathParserError::UnexpectedToken(t)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub enum MathParserError<'a> {
|
||||
#[error("Unexpected token {0}")]
|
||||
UnexpectedToken(MathToken<'a>),
|
||||
#[error("Reached the end of the expression")]
|
||||
EndOfExpression,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::math::parser::{MathParserError, parse_expr_from_tokens};
|
||||
use crate::math::tokenizer::tokenize;
|
||||
use crate::math::{BinMathOp, MathExpr, MathToken::*};
|
||||
|
||||
#[test]
|
||||
fn basic_examples() {
|
||||
assert_eq!(
|
||||
parse_expr_from_tokens(vec![]).err().unwrap(),
|
||||
MathParserError::EndOfExpression
|
||||
);
|
||||
assert_eq!(
|
||||
parse_expr_from_tokens(vec![Number(1.)]).unwrap(),
|
||||
MathExpr::Number(1.)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_expr_from_tokens(vec![Number(1.), Plus, Number(1.)]).unwrap(),
|
||||
MathExpr::BinOp(
|
||||
BinMathOp::Add,
|
||||
Box::new(MathExpr::Number(1.)),
|
||||
Box::new(MathExpr::Number(1.))
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_expr_from_tokens(vec![LParen, Number(1.), Plus, Number(1.), RParen]).unwrap(),
|
||||
MathExpr::BinOp(
|
||||
BinMathOp::Add,
|
||||
Box::new(MathExpr::Number(1.)),
|
||||
Box::new(MathExpr::Number(1.))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advanced_examples() {
|
||||
assert_eq!(
|
||||
parse_expr_from_tokens(tokenize("1+2*3-5**10").unwrap()).unwrap(),
|
||||
MathExpr::BinOp(
|
||||
BinMathOp::Sub,
|
||||
Box::new(MathExpr::BinOp(
|
||||
BinMathOp::Add,
|
||||
Box::new(MathExpr::Number(1.)),
|
||||
Box::new(MathExpr::BinOp(
|
||||
BinMathOp::Mult,
|
||||
Box::new(MathExpr::Number(2.)),
|
||||
Box::new(MathExpr::Number(3.))
|
||||
))
|
||||
)),
|
||||
Box::new(MathExpr::BinOp(
|
||||
BinMathOp::Pow,
|
||||
Box::new(MathExpr::Number(5.)),
|
||||
Box::new(MathExpr::Number(10.))
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_expr_from_tokens(tokenize("(1+(2*3))-(5**10)").unwrap()).unwrap(),
|
||||
MathExpr::BinOp(
|
||||
BinMathOp::Sub,
|
||||
Box::new(MathExpr::BinOp(
|
||||
BinMathOp::Add,
|
||||
Box::new(MathExpr::Number(1.)),
|
||||
Box::new(MathExpr::BinOp(
|
||||
BinMathOp::Mult,
|
||||
Box::new(MathExpr::Number(2.)),
|
||||
Box::new(MathExpr::Number(3.))
|
||||
))
|
||||
)),
|
||||
Box::new(MathExpr::BinOp(
|
||||
BinMathOp::Pow,
|
||||
Box::new(MathExpr::Number(5.)),
|
||||
Box::new(MathExpr::Number(10.))
|
||||
))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
125
src/math/tokenizer.rs
Normal file
125
src/math/tokenizer.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use std::iter::Peekable;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::math::{MathToken, Number};
|
||||
|
||||
pub fn tokenize(input: &str) -> Result<Vec<MathToken>, MathTokenizerError> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut chars = input.char_indices().peekable();
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some((_, ' ')) => { /* Spaces can be ignored in the general case. */ }
|
||||
Some((_, '(')) => tokens.push(MathToken::LParen),
|
||||
Some((_, ')')) => tokens.push(MathToken::RParen),
|
||||
Some((_, '+')) => tokens.push(MathToken::Plus),
|
||||
Some((_, '-')) => tokens.push(MathToken::Minus),
|
||||
// TODO: Integer division with double //
|
||||
Some((_, '/')) => tokens.push(MathToken::Slash),
|
||||
Some((_, '*')) => match chars.peek() {
|
||||
Some((_, '*')) => {
|
||||
chars.next();
|
||||
tokens.push(MathToken::DoubleStar)
|
||||
}
|
||||
_ => tokens.push(MathToken::Star),
|
||||
},
|
||||
Some((_, '^')) => tokens.push(MathToken::Caret),
|
||||
Some((start_idx, c)) if c.is_alphabetic() => {
|
||||
// This is an yet unknown symbol, call the helper to find its end and return the
|
||||
// end index. Then take a slice of that symbol and store it.
|
||||
let end_idx = tokenize_sym(&mut chars, input.len());
|
||||
tokens.push(MathToken::Sym(&input[start_idx..end_idx]));
|
||||
}
|
||||
Some((start_idx, c)) if c.is_ascii_digit() || c == '.' => {
|
||||
let end_idx = tokenize_number(c == '.', &mut chars, input.len());
|
||||
let number: Number = input[start_idx..end_idx]
|
||||
.parse()
|
||||
.map_err(|_| MathTokenizerError::InvalidNumber(&input[start_idx..end_idx]))?;
|
||||
tokens.push(MathToken::Number(number));
|
||||
}
|
||||
Some((_, c)) => {
|
||||
return Err(MathTokenizerError::InvalidChar(c));
|
||||
}
|
||||
|
||||
// No more chars, so we are done
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn tokenize_sym(
|
||||
chars: &mut Peekable<impl Iterator<Item = (usize, char)>>,
|
||||
input_end_idx: usize,
|
||||
) -> usize {
|
||||
loop {
|
||||
match chars.peek() {
|
||||
// Alphabetic chars can be symbols, so continue on.
|
||||
Some((_, c)) if c.is_alphabetic() => {
|
||||
chars.next();
|
||||
}
|
||||
// Anything other then alphabetic chars means end of symbol
|
||||
Some((end_idx, _)) => return *end_idx,
|
||||
// If we reach the end of the string, that is the end of the symbol
|
||||
None => return input_end_idx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tokenize_number<'a>(
|
||||
first_is_dot: bool,
|
||||
chars: &mut Peekable<impl Iterator<Item = (usize, char)>>,
|
||||
input_end_idx: usize,
|
||||
) -> usize {
|
||||
let mut has_seen_dot = first_is_dot;
|
||||
loop {
|
||||
match chars.peek() {
|
||||
Some((_, '.')) if !has_seen_dot => {
|
||||
has_seen_dot = true;
|
||||
chars.next();
|
||||
}
|
||||
Some((_, c)) if c.is_ascii_digit() => {
|
||||
chars.next();
|
||||
}
|
||||
Some((end_idx, _)) => return *end_idx,
|
||||
None => return input_end_idx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MathTokenizerError<'a> {
|
||||
#[error("Invalid character '{0}' in input")]
|
||||
InvalidChar(char),
|
||||
#[error("Invalid number \"{0}\" in input")]
|
||||
InvalidNumber(&'a str),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::math::MathToken::*;
|
||||
use crate::math::tokenizer::tokenize;
|
||||
|
||||
#[test]
|
||||
fn basic_examples() {
|
||||
assert_eq!(tokenize("").unwrap(), vec![]);
|
||||
assert_eq!(tokenize("1").unwrap(), vec![Number(1.)]);
|
||||
assert_eq!(tokenize("1.0").unwrap(), vec![Number(1.)]);
|
||||
assert_eq!(tokenize(".1").unwrap(), vec![Number(0.1)]);
|
||||
assert_eq!(tokenize("1+1").unwrap(), vec![Number(1.), Plus, Number(1.)]);
|
||||
assert_eq!(
|
||||
tokenize("1 + 1").unwrap(),
|
||||
vec![Number(1.), Plus, Number(1.)]
|
||||
);
|
||||
assert_eq!(
|
||||
tokenize(" 1 + 1 ").unwrap(),
|
||||
vec![Number(1.), Plus, Number(1.)]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
tokenize("(1+1)").unwrap(),
|
||||
vec![LParen, Number(1.), Plus, Number(1.), RParen]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,10 +4,7 @@ use std::{
|
|||
};
|
||||
|
||||
use gtk::{
|
||||
gio::{
|
||||
AppInfo, DesktopAppInfo, Icon,
|
||||
prelude::{AppInfoExt, DesktopAppInfoExtManual},
|
||||
},
|
||||
gio::{AppInfo, DesktopAppInfo, Icon, prelude::AppInfoExt},
|
||||
prelude::BoxExt,
|
||||
};
|
||||
use nucleo_matcher::{
|
||||
|
|
Loading…
Add table
Reference in a new issue