1use crate::model::Bar;
4use std::fmt;
5
6#[derive(Clone, Debug, PartialEq)]
37pub struct SymbolExpression {
38 root: Node,
39}
40
41#[derive(Clone, Debug, PartialEq)]
42enum Node {
43 Number(f64),
44 Symbol(String),
45 Negate(Box<Node>),
46 Binary(Operator, Box<Node>, Box<Node>),
47}
48
49#[derive(Clone, Copy, Debug, PartialEq)]
50enum Operator {
51 Add,
52 Subtract,
53 Multiply,
54 Divide,
55}
56
57impl Operator {
58 fn precedence(self) -> u8 {
59 match self {
60 Self::Add | Self::Subtract => 1,
61 Self::Multiply | Self::Divide => 2,
62 }
63 }
64
65 fn sign(self) -> char {
66 match self {
67 Self::Add => '+',
68 Self::Subtract => '-',
69 Self::Multiply => '*',
70 Self::Divide => '/',
71 }
72 }
73}
74
75#[derive(Clone, Debug, PartialEq)]
77#[non_exhaustive]
78pub enum ExpressionError {
79 UnexpectedCharacter(char),
81 MissingOperand,
83 MissingOperator(String),
85 UnbalancedParenthesis,
87 NoSymbol,
89 UnknownSymbol(String),
91 TooComplex,
93}
94
95impl fmt::Display for ExpressionError {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 Self::UnexpectedCharacter(found) => write!(f, "Unexpected character {found:?}"),
99 Self::MissingOperand => write!(f, "A symbol or number is missing"),
100 Self::MissingOperator(found) => write!(f, "An operator is missing before {found}"),
101 Self::UnbalancedParenthesis => write!(f, "Unbalanced parentheses"),
102 Self::NoSymbol => write!(f, "The expression has no symbol"),
103 Self::UnknownSymbol(symbol) => write!(f, "Unknown symbol {symbol}"),
104 Self::TooComplex => write!(f, "The expression exceeds 256 tokens"),
105 }
106 }
107}
108
109impl std::error::Error for ExpressionError {}
110
111#[derive(Clone, Debug, PartialEq)]
112enum Token {
113 Number(f64),
114 Symbol(String),
115 Operator(Operator),
116 Open,
117 Close,
118}
119
120fn tokens(text: &str) -> Result<Vec<Token>, ExpressionError> {
121 let symbol_char = |c: char| c.is_alphanumeric() || matches!(c, '.' | ':' | '_' | '!');
122 let mut tokens = Vec::new();
123 let mut chars = text.char_indices().peekable();
124 while let Some((start, c)) = chars.next() {
125 let token = match c {
126 _ if c.is_whitespace() => continue,
127 '+' => Token::Operator(Operator::Add),
128 '-' => Token::Operator(Operator::Subtract),
129 '*' => Token::Operator(Operator::Multiply),
130 '/' => Token::Operator(Operator::Divide),
131 '(' => Token::Open,
132 ')' => Token::Close,
133 _ if symbol_char(c) => {
134 let mut end = start + c.len_utf8();
135 while let Some(&(at, next)) = chars.peek()
136 && symbol_char(next)
137 {
138 end = at + next.len_utf8();
139 chars.next();
140 }
141 let word = &text[start..end];
142 let numeric = word.chars().all(|c| c.is_ascii_digit() || c == '.');
143 match word.parse::<f64>() {
144 Ok(number) if numeric && number.is_finite() => Token::Number(number),
145 _ => Token::Symbol(word.to_owned()),
146 }
147 }
148 _ => return Err(ExpressionError::UnexpectedCharacter(c)),
149 };
150 if tokens.len() == 256 {
151 return Err(ExpressionError::TooComplex);
152 }
153 tokens.push(token);
154 }
155 Ok(tokens)
156}
157
158struct Parser {
160 tokens: Vec<Token>,
161 at: usize,
162}
163
164impl Parser {
165 fn peek(&self) -> Option<&Token> {
166 self.tokens.get(self.at)
167 }
168
169 fn sum(&mut self) -> Result<Node, ExpressionError> {
170 let mut node = self.product()?;
171 while let Some(&Token::Operator(op @ (Operator::Add | Operator::Subtract))) = self.peek() {
172 self.at += 1;
173 node = Node::Binary(op, Box::new(node), Box::new(self.product()?));
174 }
175 Ok(node)
176 }
177
178 fn product(&mut self) -> Result<Node, ExpressionError> {
179 let mut node = self.factor()?;
180 while let Some(&Token::Operator(op @ (Operator::Multiply | Operator::Divide))) = self.peek()
181 {
182 self.at += 1;
183 node = Node::Binary(op, Box::new(node), Box::new(self.factor()?));
184 }
185 Ok(node)
186 }
187
188 fn factor(&mut self) -> Result<Node, ExpressionError> {
189 let token = self.peek().cloned();
190 self.at += 1;
191 match token {
192 Some(Token::Number(number)) => Ok(Node::Number(number)),
193 Some(Token::Symbol(symbol)) => Ok(Node::Symbol(symbol)),
194 Some(Token::Operator(Operator::Subtract)) => Ok(Node::Negate(Box::new(self.factor()?))),
195 Some(Token::Open) => {
196 let node = self.sum()?;
197 if self.peek() != Some(&Token::Close) {
198 return Err(match self.peek() {
199 None => ExpressionError::UnbalancedParenthesis,
200 Some(token) => ExpressionError::MissingOperator(describe(token)),
201 });
202 }
203 self.at += 1;
204 Ok(node)
205 }
206 Some(Token::Close) if self.at == 1 => Err(ExpressionError::UnbalancedParenthesis),
207 _ => Err(ExpressionError::MissingOperand),
208 }
209 }
210}
211
212fn describe(token: &Token) -> String {
213 match token {
214 Token::Number(number) => number.to_string(),
215 Token::Symbol(symbol) => symbol.clone(),
216 Token::Operator(op) => op.sign().to_string(),
217 Token::Open => "(".into(),
218 Token::Close => ")".into(),
219 }
220}
221
222impl SymbolExpression {
223 pub fn parse(text: &str) -> Result<Self, ExpressionError> {
225 let mut parser = Parser {
226 tokens: tokens(text)?,
227 at: 0,
228 };
229 let root = parser.sum()?;
230 if let Some(token) = parser.peek() {
231 return Err(if *token == Token::Close {
232 ExpressionError::UnbalancedParenthesis
233 } else {
234 ExpressionError::MissingOperator(describe(token))
235 });
236 }
237 let expression = Self { root };
238 if expression.symbols().is_empty() {
239 return Err(ExpressionError::NoSymbol);
240 }
241 Ok(expression)
242 }
243
244 #[cfg(feature = "widget")]
246 pub(crate) fn is_spread(&self) -> bool {
247 !matches!(self.root, Node::Symbol(_))
248 }
249
250 pub fn symbols(&self) -> Vec<&str> {
252 fn walk<'a>(node: &'a Node, found: &mut Vec<&'a str>) {
253 match node {
254 Node::Number(_) => {}
255 Node::Symbol(symbol) => {
256 if !found.contains(&symbol.as_str()) {
257 found.push(symbol);
258 }
259 }
260 Node::Negate(inner) => walk(inner, found),
261 Node::Binary(_, left, right) => {
262 walk(left, found);
263 walk(right, found);
264 }
265 }
266 }
267 let mut found = Vec::new();
268 walk(&self.root, &mut found);
269 found
270 }
271
272 #[cfg(feature = "ui")]
274 pub(crate) fn rename_symbols(&mut self, mut rename: impl FnMut(&str) -> String) {
275 fn walk(node: &mut Node, rename: &mut impl FnMut(&str) -> String) {
276 match node {
277 Node::Number(_) => {}
278 Node::Symbol(symbol) => *symbol = rename(symbol),
279 Node::Negate(inner) => walk(inner, rename),
280 Node::Binary(_, left, right) => {
281 walk(left, rename);
282 walk(right, rename);
283 }
284 }
285 }
286 walk(&mut self.root, &mut rename);
287 }
288
289 pub fn evaluate(&self, value: impl Fn(&str) -> Option<f64>) -> Option<f64> {
293 fn walk(node: &Node, value: &impl Fn(&str) -> Option<f64>) -> Option<f64> {
294 Some(match node {
295 Node::Number(number) => *number,
296 Node::Symbol(symbol) => value(symbol)?,
297 Node::Negate(inner) => -walk(inner, value)?,
298 Node::Binary(op, left, right) => {
299 let (left, right) = (walk(left, value)?, walk(right, value)?);
300 match op {
301 Operator::Add => left + right,
302 Operator::Subtract => left - right,
303 Operator::Multiply => left * right,
304 Operator::Divide => left / right,
305 }
306 }
307 })
308 .filter(|result| result.is_finite())
309 }
310 walk(&self.root, &value)
311 }
312
313 pub fn bars<'a>(
322 &self,
323 history: impl Fn(&str) -> Option<&'a [Bar]>,
324 ) -> Result<Vec<Bar>, ExpressionError> {
325 let components = self
326 .symbols()
327 .into_iter()
328 .map(|symbol| {
329 history(symbol)
330 .map(|bars| (symbol, bars))
331 .ok_or_else(|| ExpressionError::UnknownSymbol(symbol.to_owned()))
332 })
333 .collect::<Result<Vec<_>, _>>()?;
334 let timeline = components[0].1;
335 let mut bars = Vec::new();
336 for bar in timeline.iter() {
337 let Some(aligned) = components
338 .iter()
339 .map(|(symbol, bars)| {
340 let at = bars.binary_search_by_key(&bar.time, |bar| bar.time).ok()?;
341 Some((*symbol, &bars[at]))
342 })
343 .collect::<Option<Vec<_>>>()
344 else {
345 continue;
346 };
347 let field = |pick: fn(&Bar) -> f64| {
348 self.evaluate(|symbol| {
349 aligned
350 .iter()
351 .find(|(name, _)| *name == symbol)
352 .map(|(_, bar)| pick(bar))
353 })
354 };
355 let (Some(open), Some(high), Some(low), Some(close)) = (
356 field(|bar| bar.open),
357 field(|bar| bar.high),
358 field(|bar| bar.low),
359 field(|bar| bar.close),
360 ) else {
361 continue;
362 };
363 let values = [open, high, low, close];
364 bars.push(Bar::new(
365 bar.time,
366 open,
367 values.into_iter().fold(f64::MIN, f64::max),
368 values.into_iter().fold(f64::MAX, f64::min),
369 close,
370 0.0,
371 ));
372 }
373 Ok(bars)
374 }
375}
376
377impl fmt::Display for SymbolExpression {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 fn write(node: &Node, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381 match node {
382 Node::Number(number) => write!(f, "{number}"),
383 Node::Symbol(symbol) => f.write_str(symbol),
384 Node::Negate(inner) => {
385 f.write_str("-")?;
386 operand(inner, 3, f)
387 }
388 Node::Binary(op, left, right) => {
389 operand(left, op.precedence(), f)?;
390 write!(f, "{}", op.sign())?;
391 operand(right, op.precedence() + 1, f)
393 }
394 }
395 }
396 fn operand(node: &Node, min: u8, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397 let precedence = match node {
398 Node::Binary(op, ..) => op.precedence(),
399 _ => 3,
400 };
401 if precedence < min {
402 f.write_str("(")?;
403 write(node, f)?;
404 f.write_str(")")
405 } else {
406 write(node, f)
407 }
408 }
409 write(&self.root, f)
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use chrono::{TimeZone, Utc};
417
418 #[test]
421 fn expressions_parse_print_and_report_faults() {
422 for (text, expected) in [
423 ("AAPL - MSFT", Ok("AAPL-MSFT")),
424 ("(BTCUSD+ETHUSD)/2", Ok("(BTCUSD+ETHUSD)/2")),
425 ("a-(b-c)", Ok("a-(b-c)")),
426 ("a-b-c", Ok("a-b-c")),
427 ("a*b+c", Ok("a*b+c")),
428 ("a*(b+c)", Ok("a*(b+c)")),
429 ("-a+2.5*NASDAQ:B", Ok("-a+2.5*NASDAQ:B")),
430 ("ES1!/BRK.B", Ok("ES1!/BRK.B")),
431 ("", Err(ExpressionError::MissingOperand)),
432 ("AAPL -", Err(ExpressionError::MissingOperand)),
433 (
434 "AAPL MSFT",
435 Err(ExpressionError::MissingOperator("MSFT".into())),
436 ),
437 ("(AAPL-MSFT", Err(ExpressionError::UnbalancedParenthesis)),
438 ("AAPL-MSFT)", Err(ExpressionError::UnbalancedParenthesis)),
439 ("AAPL%2", Err(ExpressionError::UnexpectedCharacter('%'))),
440 ("2*3", Err(ExpressionError::NoSymbol)),
441 ] {
442 let parsed = SymbolExpression::parse(text).map(|e| e.to_string());
443 assert_eq!(parsed, expected.map(str::to_owned), "{text:?}");
444 }
445 for text in [
446 format!("{}A{}", "(".repeat(300), ")".repeat(300)),
447 ["A"; 300].join("+"),
448 format!("{}A", "-".repeat(300)),
449 ] {
450 assert!(
451 SymbolExpression::parse(&text).is_err(),
452 "unbounded recursion"
453 );
454 }
455 }
456
457 #[test]
461 fn spread_bars_align_components_by_time() {
462 let day = |d| Utc.with_ymd_and_hms(2024, 3, d, 0, 0, 0).unwrap();
463 let a = [
464 Bar::new(day(1), 10.0, 14.0, 9.0, 12.0, 100.0),
465 Bar::new(day(2), 12.0, 13.0, 11.0, 11.5, 100.0),
466 Bar::new(day(3), 11.0, 15.0, 10.0, 14.0, 100.0),
467 ];
468 let b = [
469 Bar::new(day(1), 4.0, 8.0, 3.5, 5.0, 50.0),
470 Bar::new(day(3), 2.0, 0.0, 1.0, 7.0, 50.0),
471 ];
472 let history = |symbol: &str| match symbol {
473 "A" => Some(&a[..]),
474 "B" => Some(&b[..]),
475 _ => None,
476 };
477 let ohlc = |text: &str| {
478 SymbolExpression::parse(text)
479 .unwrap()
480 .bars(history)
481 .map(|bars| {
482 assert!(bars.iter().all(crate::data::merge::valid));
483 bars.iter()
484 .map(|bar| (bar.time, bar.open, bar.high, bar.low, bar.close))
485 .collect::<Vec<_>>()
486 })
487 };
488 assert_eq!(
490 ohlc("A-B"),
491 Ok(vec![
492 (day(1), 6.0, 7.0, 5.5, 7.0),
493 (day(3), 9.0, 15.0, 7.0, 7.0)
494 ])
495 );
496 assert_eq!(
498 ohlc("A/B"),
499 Ok(vec![(day(1), 2.5, 2.5714285714285716, 1.75, 2.4)])
500 );
501 assert_eq!(ohlc("A-C"), Err(ExpressionError::UnknownSymbol("C".into())));
502 assert_eq!(ohlc("1/(A/0)"), Ok(vec![]));
504 }
505}