Skip to content

Commit 50be352

Browse files
committed
add lexing stage, make variables and functions prefixed to prepare for
implicit multiplication
1 parent ad72bfc commit 50be352

File tree

5 files changed

+274
-124
lines changed

5 files changed

+274
-124
lines changed

libraries/math-parser/src/ast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl Unit {
3737
}
3838
}
3939

40-
#[derive(Debug, PartialEq)]
40+
#[derive(Debug, Clone, PartialEq)]
4141
pub enum Literal {
4242
Float(f64),
4343
Complex(Complex),

libraries/math-parser/src/constants.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ lazy_static! {
99
pub static ref DEFAULT_FUNCTIONS: HashMap<&'static str, FunctionImplementation> = {
1010
let mut map: HashMap<&'static str, FunctionImplementation> = HashMap::new();
1111

12+
map.insert(
13+
"sqrt",
14+
Box::new(|values| match values{
15+
[Value::Number(Number::Real(real))] => Some(Value::Number(Number::Real(real.sqrt()))),
16+
[Value::Number(Number::Complex(complex))] => Some(Value::Number(Number::Complex(complex.sqrt()))),
17+
_ => None,
18+
})
19+
);
1220
map.insert(
1321
"sin",
1422
Box::new(|values| match values {

libraries/math-parser/src/lexer.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// ── lexer.rs ───────────────────────────────────────────────────────────
2+
use crate::ast::Literal;
3+
use chumsky::input::{Input, ValueInput};
4+
use chumsky::prelude::*;
5+
use chumsky::span::SimpleSpan;
6+
use chumsky::text::{ident, int};
7+
use num_complex::Complex64;
8+
use std::ops::Range;
9+
10+
pub type Span = SimpleSpan;
11+
12+
#[derive(Clone, Debug, PartialEq)]
13+
pub enum Token<'src> {
14+
// literals ----------------------------------------------------------------
15+
Const(Literal), // numeric or complex constants recognised at lex‑time
16+
Var(&'src str), // #identifier (variables)
17+
Call(&'src str),
18+
// punctuation -------------------------------------------------------------
19+
LParen,
20+
RParen,
21+
Comma,
22+
Plus,
23+
Minus,
24+
Star,
25+
Slash,
26+
Caret,
27+
// comparison --------------------------------------------------------------
28+
Lt,
29+
Le,
30+
Gt,
31+
Ge,
32+
EqEq,
33+
// keywords ----------------------------------------------------------------
34+
If,
35+
}
36+
37+
pub fn lexer<'src>() -> impl Parser<'src, &'src str, Vec<(Token<'src>, Span)>, extra::Err<Rich<'src, char>>> {
38+
// ── numbers ────────────────────────────────────────────────────────────
39+
let num = int(10)
40+
.then(just('.').then(int(10)).or_not())
41+
.then(just('e').or(just('E')).then(one_of("+-").or_not()).then(int(10)).or_not())
42+
.map(|((int_part, frac), exp): ((&str, _), _)| {
43+
let mut s = int_part.to_string();
44+
if let Some((_, frac)) = frac {
45+
s.push('.');
46+
s.push_str(frac);
47+
}
48+
if let Some(((e, sign), exp)) = exp {
49+
s.push(e);
50+
if let Some(sign) = sign {
51+
s.push(sign);
52+
}
53+
s.push_str(exp);
54+
}
55+
Token::Const(Literal::Float(s.parse::<f64>().unwrap()))
56+
});
57+
58+
// ── single‑char symbols ────────────────────────────────────────────────
59+
let sym = choice((
60+
just('(').to(Token::LParen),
61+
just(')').to(Token::RParen),
62+
just(',').to(Token::Comma),
63+
just('+').to(Token::Plus),
64+
just('-').to(Token::Minus),
65+
just('*').to(Token::Star),
66+
just('/').to(Token::Slash),
67+
just('^').to(Token::Caret),
68+
));
69+
70+
// ── comparison operators ───────────────────────────────────────────────
71+
let cmp = choice((
72+
just("<=").to(Token::Le),
73+
just(">=").to(Token::Ge),
74+
just("==").to(Token::EqEq),
75+
just('<').to(Token::Lt),
76+
just('>').to(Token::Gt),
77+
));
78+
79+
let kw_token = |w, t| just(w).padded().to(t);
80+
81+
let kw_lit = |w, lit: Literal| just(w).padded().to(lit);
82+
83+
let const_token = choice((
84+
kw_lit("pi", Literal::Float(std::f64::consts::PI)),
85+
kw_lit("π", Literal::Float(std::f64::consts::PI)),
86+
kw_lit("tau", Literal::Float(std::f64::consts::TAU)),
87+
kw_lit("τ", Literal::Float(std::f64::consts::TAU)),
88+
kw_lit("e", Literal::Float(std::f64::consts::E)),
89+
kw_lit("phi", Literal::Float(1.618_033_988_75)),
90+
kw_lit("φ", Literal::Float(1.618_033_988_75)),
91+
kw_lit("inf", Literal::Float(f64::INFINITY)),
92+
kw_lit("∞", Literal::Float(f64::INFINITY)),
93+
kw_lit("i", Literal::Complex(Complex64::new(0.0, 1.0))),
94+
kw_lit("G", Literal::Float(9.80665)),
95+
))
96+
.map(Token::Const);
97+
98+
let var_token = just('#').ignore_then(ident()).map(Token::Var);
99+
let call_token = just('@').ignore_then(ident()).map(Token::Call);
100+
101+
choice((num, kw_token("if", Token::If), const_token, cmp, sym, var_token, call_token))
102+
.map_with(|t, e| (t, e.span()))
103+
.padded()
104+
.repeated()
105+
.collect()
106+
}
107+
108+
#[derive(Debug)]
109+
pub struct TokenStream<'src> {
110+
tokens: Vec<(Token<'src>, Span)>,
111+
}
112+
113+
impl<'src> TokenStream<'src> {
114+
pub fn new(tokens: Vec<(Token<'src>, Span)>) -> Self {
115+
TokenStream { tokens }
116+
}
117+
}
118+
119+
impl<'src> Input<'src> for TokenStream<'src> {
120+
type Token = (Token<'src>, Span);
121+
type Span = Span;
122+
type Cursor = usize;
123+
type MaybeToken = (Token<'src>, Span);
124+
type Cache = Self;
125+
126+
fn begin(self) -> (Self::Cursor, Self::Cache) {
127+
(0, self)
128+
}
129+
130+
fn cursor_location(cursor: &Self::Cursor) -> usize {
131+
*cursor
132+
}
133+
134+
#[inline(always)]
135+
unsafe fn next_maybe(this: &mut Self::Cache, cursor: &mut Self::Cursor) -> Option<Self::MaybeToken> {
136+
if let Some(tok) = this.tokens.get(*cursor) {
137+
*cursor += 1;
138+
Some(tok.clone())
139+
} else {
140+
None
141+
}
142+
}
143+
144+
#[inline(always)]
145+
unsafe fn span(_this: &mut Self::Cache, range: Range<&Self::Cursor>) -> Self::Span {
146+
(*range.start..*range.end).into()
147+
}
148+
}
149+
150+
impl<'src> ValueInput<'src> for TokenStream<'src> {
151+
unsafe fn next(this: &mut Self::Cache, cursor: &mut Self::Cursor) -> Option<Self::Token> {
152+
if let Some(tok) = this.tokens.get(*cursor) {
153+
*cursor += 1;
154+
Some(tok.clone())
155+
} else {
156+
None
157+
}
158+
}
159+
}

libraries/math-parser/src/lib.rs

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod ast;
44
mod constants;
55
pub mod context;
66
pub mod executer;
7+
pub mod lexer;
78
pub mod parser;
89
pub mod value;
910

@@ -95,22 +96,22 @@ mod tests {
9596
order_of_operations_negative_prefix: "-10 + 5" => -5.,
9697
order_of_operations_add_multiply: "5+1*1+5" => 11.,
9798
order_of_operations_add_negative_multiply: "5+(-1)*1+5" => 9.,
98-
order_of_operations_sqrt: "sqrt25 + 11" => 16.,
99-
order_of_operations_sqrt_expression: "sqrt(25+11)" => 6.,
99+
order_of_operations_sqrt: "@sqrt(25) + 11" => 16.,
100+
order_of_operations_sqrt_expression: "@sqrt(25+11)" => 6.,
100101

101102
// Parentheses and nested expressions
102103
parentheses_nested_multiply: "(5 + 3) * (2 + 6)" => 64.,
103104
parentheses_mixed_operations: "2 * (3 + 5 * (2 + 1))" => 36.,
104105
parentheses_divide_add_multiply: "10 / (2 + 3) + (7 * 2)" => 16.,
105106

106107
// Square root and nested square root
107-
sqrt_chain_operations: "sqrt(16) + sqrt(9) * sqrt(4)" => 10.,
108-
sqrt_nested: "sqrt(sqrt(81))" => 3.,
109-
sqrt_divide_expression: "sqrt((25 + 11) / 9)" => 2.,
108+
sqrt_chain_operations: "@sqrt(16) + @sqrt(9) * @sqrt(4)" => 10.,
109+
sqrt_nested: "@sqrt(@sqrt(81))" => 3.,
110+
sqrt_divide_expression: "@sqrt((25 + 11) / 9)" => 2.,
110111

111112
// Mixed square root and units
112-
sqrt_add_multiply: "sqrt(49) - 1 + 2 * 3" => 12.,
113-
sqrt_addition_multiply: "(sqrt(36) + 2) * 2" => 16.,
113+
sqrt_add_multiply: "@sqrt(49) - 1 + 2 * 3" => 12.,
114+
sqrt_addition_multiply: "(@sqrt(36) + 2) * 2" => 16.,
114115

115116
// Exponentiation
116117
exponent_single: "2^3" => 8.,
@@ -119,10 +120,10 @@ mod tests {
119120

120121
// Operations with negative values
121122
negative_nested_parentheses: "-(5 + 3 * (2 - 1))" => -8.,
122-
negative_sqrt_addition: "-(sqrt(16) + sqrt(9))" => -7.,
123-
multiply_sqrt_subtract: "5 * 2 + sqrt(16) / 2 - 3" => 9.,
124-
add_multiply_subtract_sqrt: "4 + 3 * (2 + 1) - sqrt(25)" => 8.,
125-
add_sqrt_subtract_nested_multiply: "10 + sqrt(64) - (5 * (2 + 1))" => 3.,
123+
negative_sqrt_addition: "-(@sqrt(16) + @sqrt(9))" => -7.,
124+
multiply_sqrt_subtract: "5 * 2 + @sqrt(16) / 2 - 3" => 9.,
125+
add_multiply_subtract_sqrt: "4 + 3 * (2 + 1) - @sqrt(25)" => 8.,
126+
add_sqrt_subtract_nested_multiply: "10 + @sqrt(64) - (5 * (2 + 1))" => 3.,
126127

127128
// Mathematical constants
128129
constant_pi: "pi" => std::f64::consts::PI,
@@ -138,47 +139,44 @@ mod tests {
138139
infinity_subtract_large_number: "inf - 1000" => f64::INFINITY,
139140

140141
// Trigonometric functions
141-
trig_sin_pi: "sin(pi)" => 0.0,
142-
trig_cos_zero: "cos(0)" => 1.0,
143-
trig_tan_pi_div_four: "tan(pi/4)" => 1.0,
144-
trig_sin_tau: "sin(tau)" => 0.0,
145-
trig_cos_tau_div_two: "cos(tau/2)" => -1.0,
142+
trig_sin_pi: "@sin(pi)" => 0.0,
143+
trig_cos_zero: "@cos(0)" => 1.0,
144+
trig_tan_pi_div_four: "@tan(pi/4)" => 1.0,
145+
trig_sin_tau: "@sin(tau)" => 0.0,
146+
trig_cos_tau_div_two: "@cos(tau/2)" => -1.0,
146147

147148
// Basic if statements
148-
if_true_condition: "if(1){5} else {3}" => 5.,
149-
if_false_condition: "if(0){5} else {3}" => 3.,
149+
if_true_condition: "if(1,5,3)" => 5.,
150+
if_false_condition: "if(0, 5, 3)" => 3.,
150151

151152
// Arithmetic conditions
152-
if_arithmetic_true: "if(2+2-4){1} else {0}" => 0.,
153-
if_arithmetic_false: "if(3*2-5){1} else {0}" => 1.,
153+
if_arithmetic_true: "if(2+2-4, 1 , 0)" => 0.,
154+
if_arithmetic_false: "if(3*2-5, 1, 0)" => 1.,
154155

155156
// Nested arithmetic
156-
if_complex_arithmetic: "if((5+3)*(2-1)){10} else {20}" => 10.,
157-
if_with_division: "if(8/4-2 == 0){15} else {25}" => 15.,
157+
if_complex_arithmetic: "if((5+3)*(2-1), 10, 20)" => 10.,
158+
if_with_division: "if(8/4-2 == 0,15, 25)" => 15.,
158159

159160
// Constants in conditions
160-
if_with_pi: "if(pi > 3){1} else {0}" => 1.,
161-
if_with_e: "if(e < 3){1} else {0}" => 1.,
161+
if_with_pi: "if(pi > 3, 1, 0)" => 1.,
162+
if_with_e: "if(e < 3, 1, 0)" => 1.,
162163

163164
// Functions in conditions
164-
if_with_sqrt: "if(sqrt(16) == 4){1} else {0}" => 1.,
165-
if_with_sin: "if(sin(pi) == 0.0){1} else {0}" => 0.,
165+
if_with_sqrt: "if(@sqrt(16) == 4, 1, 0)" => 1.,
166+
if_with_sin: "if(@sin(pi) == 0.0, 1, 0)" => 0.,
166167

167168
// Nested if statements
168-
nested_if: "if(1){if(0){1} else {2}} else {3}" => 2.,
169-
nested_if_complex: "if(2-2 == 0){if(1){5} else {6}} else {if(1){7} else {8}}" => 5.,
169+
nested_if: "if(1, if(0, 1, 2), 3)" => 2.,
170+
nested_if_complex: "if(2-2 == 0, if(1, 5, 6), if(1, 7, 8))" => 5.,
170171

171172
// Mixed operations in conditions and blocks
172-
if_complex_condition: "if(sqrt(16) + sin(pi) < 5){2*pi} else {3*e}" => 2. * std::f64::consts::PI,
173-
if_complex_blocks: "if(1){2*sqrt(16) + sin(pi/2)} else {3*cos(0) + 4}" => 9.,
173+
if_complex_condition: "if(@sqrt(16) + @sin(pi) < 5, 2*pi, 3*e)" => 2. * std::f64::consts::PI,
174+
if_complex_blocks: "if(1, 2*@sqrt(16) + @sin(pi/2), 3*@cos(0) + 4)" => 9.,
174175

175176
// Edge cases
176-
if_zero: "if(0.0){1} else {2}" => 2.,
177-
if_negative: "if(-1){1} else {2}" => 1.,
178-
if_infinity: "if(inf){1} else {2}" => 1.,
179-
177+
if_zero: "if(0.0, 1, 2)" => 2.,
180178

181179
// Complex nested expressions
182-
if_nested_expr: "if((sqrt(16) + 2) * (sin(pi) + 1)){3 + 4 * 2} else {5 - 2 / 1}" => 11.,
180+
if_nested_expr: "if((@sqrt(16) + 2) * (@sin(pi) + 1), 3 + 4 * 2, 5 - 2 / 1)" => 11.,
183181
}
184182
}

0 commit comments

Comments
 (0)