1use std::collections::HashMap;
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::{Arc, Weak};
24
25use parking_lot::Mutex;
26
27use crate::{Inner, Listener, Pv, PvManager, PvName, PvState, Severity, Shared, Value};
28
29pub fn formula_inputs(text: &str) -> Vec<String> {
33 fn walk(e: &Expr, out: &mut Vec<String>) {
34 match e {
35 Expr::Num(_) | Expr::Str(_) => {}
36 Expr::Pv(name) => {
37 if !out.contains(name) {
38 out.push(name.clone());
39 }
40 }
41 Expr::DynPv(e) | Expr::Neg(e) | Expr::Not(e) => walk(e, out),
42 Expr::Binary(_, a, b) => [a, b].into_iter().for_each(|e| walk(e, out)),
43 Expr::Cond(c, a, b) => [c, a, b].into_iter().for_each(|e| walk(e, out)),
44 Expr::Call(_, args) => args.iter().for_each(|e| walk(e, out)),
45 }
46 }
47 let mut out = Vec::new();
48 if let Ok(e) = parse(text) {
49 walk(&e, &mut out);
50 }
51 out
52}
53
54#[derive(Clone, Debug, PartialEq)]
56enum Expr {
57 Num(f64),
58 Str(String),
59 Pv(String),
61 DynPv(Box<Expr>),
63 Neg(Box<Expr>),
64 Not(Box<Expr>),
65 Binary(Op, Box<Expr>, Box<Expr>),
66 Cond(Box<Expr>, Box<Expr>, Box<Expr>),
67 Call(String, Vec<Expr>),
68}
69
70#[derive(Clone, Copy, Debug, PartialEq)]
71enum Op {
72 Add,
73 Sub,
74 Mul,
75 Div,
76 Rem,
77 Pow,
78 Lt,
79 Le,
80 Gt,
81 Ge,
82 Eq,
83 Ne,
84 And,
85 Or,
86}
87
88#[derive(Clone, Debug, PartialEq)]
89enum Tok {
90 Num(f64),
91 Str(String),
92 Name(String),
94 Ident(String),
95 Sym(&'static str),
96}
97
98const SYMS: [&str; 20] = [
99 "&&", "||", "==", "!=", "<=", ">=", "<", ">", "+", "-", "*", "/", "%", "^", "!", "?", ":", "(",
100 ")", ",",
101];
102
103fn lex(text: &str) -> Result<Vec<Tok>, String> {
104 let mut toks = Vec::new();
105 let mut rest = text;
106 while let Some(c) = rest.chars().next() {
107 if c.is_whitespace() {
108 rest = &rest[c.len_utf8()..];
109 } else if c.is_ascii_digit()
110 || (c == '.' && rest[1..].starts_with(|d: char| d.is_ascii_digit()))
111 {
112 let mut end = 0;
113 let b = rest.as_bytes();
114 while end < b.len() && (b[end].is_ascii_digit() || b[end] == b'.') {
115 end += 1;
116 }
117 if end < b.len() && (b[end] == b'e' || b[end] == b'E') {
119 let mut e = end + 1;
120 if e < b.len() && (b[e] == b'+' || b[e] == b'-') {
121 e += 1;
122 }
123 if e < b.len() && b[e].is_ascii_digit() {
124 end = e;
125 while end < b.len() && b[end].is_ascii_digit() {
126 end += 1;
127 }
128 }
129 }
130 let n = rest[..end]
131 .parse()
132 .map_err(|_| format!("bad number '{}'", &rest[..end]))?;
133 toks.push(Tok::Num(n));
134 rest = &rest[end..];
135 } else if c == '"' {
136 let mut s = String::new();
137 let mut chars = rest[1..].char_indices();
138 let end = loop {
139 match chars.next() {
140 Some((i, '"')) => break i + 2,
141 Some((_, '\\')) => match chars.next() {
142 Some((_, 'n')) => s.push('\n'),
143 Some((_, 't')) => s.push('\t'),
144 Some((_, e)) => s.push(e),
145 None => return Err("unterminated string".into()),
146 },
147 Some((_, ch)) => s.push(ch),
148 None => return Err("unterminated string".into()),
149 }
150 };
151 toks.push(Tok::Str(s));
152 rest = &rest[end..];
153 } else if c == '\'' || c == '`' {
154 let end = rest[1..]
156 .find(c)
157 .ok_or_else(|| format!("unterminated PV name {rest}"))?;
158 toks.push(Tok::Name(rest[1..end + 1].trim().to_string()));
159 rest = &rest[end + 2..];
160 } else if c.is_ascii_alphabetic() || c == '_' {
161 let end = rest
162 .find(|ch: char| !(ch.is_ascii_alphanumeric() || "_:.".contains(ch)))
163 .unwrap_or(rest.len());
164 let word = &rest[..end];
165 rest = &rest[end..];
166 if rest.trim_start().starts_with('(') {
167 toks.push(Tok::Ident(word.to_string()));
168 } else {
169 toks.push(Tok::Name(word.to_string()));
170 }
171 } else if let Some(sym) = SYMS.iter().find(|s| rest.starts_with(**s)) {
172 toks.push(Tok::Sym(sym));
173 rest = &rest[sym.len()..];
174 } else {
175 return Err(format!("unexpected '{c}'"));
176 }
177 }
178 Ok(toks)
179}
180
181struct Parser {
183 toks: Vec<Tok>,
184 at: usize,
185 depth: usize,
188}
189
190const MAX_DEPTH: usize = 64;
194const MAX_TOKENS: usize = 1024;
197
198impl Parser {
199 fn nested(
201 &mut self,
202 f: impl FnOnce(&mut Self) -> Result<Expr, String>,
203 ) -> Result<Expr, String> {
204 if self.depth >= MAX_DEPTH {
205 return Err("formula nests too deeply".into());
206 }
207 self.depth += 1;
208 let e = f(self);
209 self.depth -= 1;
210 e
211 }
212
213 fn peek(&self) -> Option<&Tok> {
214 self.toks.get(self.at)
215 }
216
217 fn eat(&mut self, sym: &str) -> bool {
218 let found = matches!(self.peek(), Some(Tok::Sym(s)) if *s == sym);
219 if found {
220 self.at += 1;
221 }
222 found
223 }
224
225 fn expect(&mut self, sym: &str) -> Result<(), String> {
226 if self.eat(sym) {
227 Ok(())
228 } else {
229 Err(format!("expected '{sym}'"))
230 }
231 }
232
233 fn cond(&mut self) -> Result<Expr, String> {
234 self.nested(Self::cond_inner)
235 }
236
237 fn cond_inner(&mut self) -> Result<Expr, String> {
238 let c = self.binary(0)?;
239 if !self.eat("?") {
240 return Ok(c);
241 }
242 let a = self.cond()?;
243 self.expect(":")?;
244 let b = self.cond()?;
245 Ok(Expr::Cond(Box::new(c), Box::new(a), Box::new(b)))
246 }
247
248 fn binary(&mut self, level: usize) -> Result<Expr, String> {
250 const LEVELS: [&[(&str, Op)]; 6] = [
251 &[("||", Op::Or)],
252 &[("&&", Op::And)],
253 &[("==", Op::Eq), ("!=", Op::Ne)],
254 &[("<=", Op::Le), (">=", Op::Ge), ("<", Op::Lt), (">", Op::Gt)],
255 &[("+", Op::Add), ("-", Op::Sub)],
256 &[("*", Op::Mul), ("/", Op::Div), ("%", Op::Rem)],
257 ];
258 let Some(ops) = LEVELS.get(level) else {
259 return self.unary();
260 };
261 let mut left = self.binary(level + 1)?;
262 'more: loop {
263 for (sym, op) in ops.iter() {
264 if self.eat(sym) {
265 let right = self.binary(level + 1)?;
266 left = Expr::Binary(*op, Box::new(left), Box::new(right));
267 continue 'more;
268 }
269 }
270 return Ok(left);
271 }
272 }
273
274 fn unary(&mut self) -> Result<Expr, String> {
275 if self.eat("-") {
276 Ok(Expr::Neg(Box::new(self.nested(Self::unary)?)))
277 } else if self.eat("+") {
278 self.nested(Self::unary)
279 } else if self.eat("!") {
280 Ok(Expr::Not(Box::new(self.nested(Self::unary)?)))
281 } else {
282 let base = self.primary()?;
283 if self.eat("^") {
284 let exp = self.nested(Self::unary)?;
285 return Ok(Expr::Binary(Op::Pow, Box::new(base), Box::new(exp)));
286 }
287 Ok(base)
288 }
289 }
290
291 fn primary(&mut self) -> Result<Expr, String> {
292 let tok = self.peek().cloned().ok_or("unexpected end")?;
293 self.at += 1;
294 match tok {
295 Tok::Num(n) => Ok(Expr::Num(n)),
296 Tok::Str(s) => Ok(Expr::Str(s)),
297 Tok::Name(n) => Ok(Expr::Pv(n)),
298 Tok::Sym("(") => {
299 let e = self.cond()?;
300 self.expect(")")?;
301 Ok(e)
302 }
303 Tok::Ident(f) => {
304 self.expect("(")?;
305 let mut args = Vec::new();
306 if !self.eat(")") {
307 loop {
308 args.push(self.cond()?);
309 if self.eat(")") {
310 break;
311 }
312 self.expect(",")?;
313 }
314 }
315 call(f, args)
316 }
317 Tok::Sym(s) => Err(format!("unexpected '{s}'")),
318 }
319 }
320}
321
322fn call(f: String, mut args: Vec<Expr>) -> Result<Expr, String> {
324 let arity = |n: usize| {
325 if args.len() == n {
326 Ok(())
327 } else {
328 Err(format!("{f}() takes {n} arguments"))
329 }
330 };
331 match f.as_str() {
332 "pv" => {
333 arity(1)?;
334 Ok(match args.pop().unwrap() {
335 Expr::Str(name) | Expr::Pv(name) => Expr::Pv(name),
336 other => Expr::DynPv(Box::new(other)),
337 })
338 }
339 "concat" | "min" | "max" => Ok(Expr::Call(f, args)),
340 "atan2" | "hypot" | "pow" => {
341 arity(2)?;
342 Ok(Expr::Call(f, args))
343 }
344 _ if unary_fn(&f).is_some() || f == "toString" => {
345 arity(1)?;
346 Ok(Expr::Call(f, args))
347 }
348 _ => Err(format!("unknown function {f}()")),
349 }
350}
351
352fn unary_fn(f: &str) -> Option<fn(f64) -> f64> {
353 Some(match f {
354 "abs" => f64::abs,
355 "acos" => f64::acos,
356 "asin" => f64::asin,
357 "atan" => f64::atan,
358 "cbrt" => f64::cbrt,
359 "ceil" => f64::ceil,
360 "cos" => f64::cos,
361 "cosh" => f64::cosh,
362 "exp" => f64::exp,
363 "expm1" => f64::exp_m1,
364 "floor" => f64::floor,
365 "log" => f64::ln,
366 "log10" => f64::log10,
367 "log1p" => f64::ln_1p,
368 "round" => f64::round,
369 "signum" => f64::signum,
370 "sin" => f64::sin,
371 "sinh" => f64::sinh,
372 "sqrt" => f64::sqrt,
373 "tan" => f64::tan,
374 "tanh" => f64::tanh,
375 "toDegrees" => f64::to_degrees,
376 "toRadians" => f64::to_radians,
377 _ => return None,
378 })
379}
380
381fn parse(text: &str) -> Result<Expr, String> {
382 let toks = lex(text)?;
383 if toks.len() > MAX_TOKENS {
384 return Err(format!("formula has more than {MAX_TOKENS} tokens"));
385 }
386 let mut p = Parser {
387 toks,
388 at: 0,
389 depth: 0,
390 };
391 let e = p.cond()?;
392 match p.peek() {
393 None => Ok(e),
394 Some(t) => Err(format!("unexpected {t:?}")),
395 }
396}
397
398#[derive(Clone, Debug)]
400enum Val {
401 Num(f64),
402 Str(String),
403 Pv(Box<PvState>),
406}
407
408impl Val {
409 fn num(&self) -> f64 {
410 match self {
411 Val::Num(n) => *n,
412 Val::Str(s) => s.trim().parse().unwrap_or(f64::NAN),
413 Val::Pv(s) => s.value.as_f64().unwrap_or(f64::NAN),
414 }
415 }
416
417 fn text(&self) -> String {
418 match self {
419 Val::Num(n) => n.to_string(),
420 Val::Str(s) => s.clone(),
421 Val::Pv(s) => s.format(None, false),
422 }
423 }
424
425 fn is_str(&self) -> bool {
426 match self {
427 Val::Str(_) => true,
428 Val::Pv(s) => matches!(s.value, Value::Str(_) | Value::StrArray(_)),
429 Val::Num(_) => false,
430 }
431 }
432}
433
434fn bool_val(b: bool) -> Val {
435 Val::Num(if b { 1.0 } else { 0.0 })
436}
437
438struct Eval<'a> {
440 states: &'a HashMap<String, PvState>,
442 wanted: Vec<(String, String)>,
444 severity: Severity,
445 timestamp: Option<web_time::SystemTime>,
446}
447
448struct Missing;
450
451impl Eval<'_> {
452 fn read(&mut self, name: String) -> Result<Val, Missing> {
453 let key = PvName::parse(&name).key();
454 let state = self
455 .states
456 .get(&key)
457 .filter(|s| s.connected && s.value != Value::None);
458 self.wanted.push((key, name));
459 let state = state.ok_or(Missing)?;
460 self.severity = self.severity.max(state.severity);
461 self.timestamp = self.timestamp.max(state.timestamp);
462 Ok(Val::Pv(Box::new(state.clone())))
463 }
464
465 fn eval(&mut self, e: &Expr) -> Result<Val, Missing> {
468 Ok(match e {
469 Expr::Num(n) => Val::Num(*n),
470 Expr::Str(s) => Val::Str(s.clone()),
471 Expr::Pv(name) => return self.read(name.clone()),
472 Expr::DynPv(name) => {
473 let name = self.eval(name)?.text();
474 return self.read(name.trim().to_string());
475 }
476 Expr::Neg(a) => Val::Num(-self.eval(a)?.num()),
477 Expr::Not(a) => bool_val(self.eval(a)?.num() == 0.0),
478 Expr::Cond(c, a, b) => {
479 let (c, a, b) = (self.eval(c), self.eval(a), self.eval(b));
480 if c?.num() != 0.0 { a? } else { b? }
481 }
482 Expr::Binary(op, a, b) => {
483 let (a, b) = (self.eval(a), self.eval(b));
484 binary(*op, a?, b?)
485 }
486 Expr::Call(f, args) => {
487 let args: Vec<_> = args.iter().map(|a| self.eval(a)).collect();
488 let args = args.into_iter().collect::<Result<Vec<_>, _>>()?;
489 let nums = || args.iter().map(Val::num);
490 match f.as_str() {
491 "concat" => Val::Str(args.iter().map(Val::text).collect()),
492 "toString" => Val::Str(args[0].text()),
493 "min" => Val::Num(nums().fold(f64::NAN, f64::min)),
494 "max" => Val::Num(nums().fold(f64::NAN, f64::max)),
495 "atan2" => Val::Num(args[0].num().atan2(args[1].num())),
496 "hypot" => Val::Num(args[0].num().hypot(args[1].num())),
497 "pow" => Val::Num(args[0].num().powf(args[1].num())),
498 _ => Val::Num(unary_fn(f).expect("checked by the parser")(args[0].num())),
499 }
500 }
501 })
502 }
503}
504
505fn binary(op: Op, a: Val, b: Val) -> Val {
506 let strings = a.is_str() && b.is_str();
507 match op {
508 Op::Add if a.is_str() || b.is_str() => Val::Str(a.text() + &b.text()),
509 Op::Eq if strings => bool_val(a.text() == b.text()),
510 Op::Ne if strings => bool_val(a.text() != b.text()),
511 _ => {
512 let (x, y) = (a.num(), b.num());
513 match op {
514 Op::Add => Val::Num(x + y),
515 Op::Sub => Val::Num(x - y),
516 Op::Mul => Val::Num(x * y),
517 Op::Div => Val::Num(x / y),
518 Op::Rem => Val::Num(x % y),
519 Op::Pow => Val::Num(x.powf(y)),
520 Op::Lt => bool_val(x < y),
521 Op::Le => bool_val(x <= y),
522 Op::Gt => bool_val(x > y),
523 Op::Ge => bool_val(x >= y),
524 Op::Eq => bool_val(x == y),
525 Op::Ne => bool_val(x != y),
526 Op::And => bool_val(x != 0.0 && y != 0.0),
527 Op::Or => bool_val(x != 0.0 || y != 0.0),
528 }
529 }
530 }
531}
532
533const MAX_EVALUATIONS: u32 = 1000;
535
536fn same(a: &PvState, b: &PvState) -> bool {
538 let nan = |s: &PvState| matches!(s.value, Value::Double(v) if v.is_nan());
539 if nan(a) && nan(b) {
540 let strip = |s: &PvState| PvState {
541 value: Value::None,
542 ..s.clone()
543 };
544 strip(a) == strip(b)
545 } else {
546 a == b
547 }
548}
549
550type Input = (Pv, Listener);
552
553pub(crate) struct Formula {
555 expr: Option<Expr>,
557 shared: Arc<Shared>,
558 manager: Weak<Inner>,
561 me: Weak<Formula>,
562 key: String,
563 states: Mutex<HashMap<String, PvState>>,
566 inputs: Mutex<HashMap<String, Input>>,
568 pending: Mutex<HashMap<String, PvState>>,
570 dirty: AtomicBool,
574 queued: AtomicBool,
575 busy: AtomicBool,
576 warned: AtomicBool,
579 runs: Mutex<(u64, u32)>,
582}
583
584impl Formula {
585 pub(crate) fn new(name: &PvName, shared: Arc<Shared>, manager: Weak<Inner>) -> Arc<Self> {
586 let expr = match parse(&name.pv) {
587 Ok(e) => Some(e),
588 Err(e) => {
589 log::warn!("{name}: {e}");
590 shared.update(|s| s.error = Some(format!("bad formula: {e}")));
591 None
592 }
593 };
594 Arc::new_cyclic(|me| Self {
595 expr,
596 shared,
597 manager,
598 me: me.clone(),
599 key: name.key(),
600 states: Mutex::new(HashMap::new()),
601 inputs: Mutex::new(HashMap::new()),
602 pending: Mutex::new(HashMap::new()),
603 dirty: AtomicBool::new(false),
604 queued: AtomicBool::new(false),
605 busy: AtomicBool::new(false),
606 warned: AtomicBool::new(false),
607 runs: Mutex::new((0, 0)),
608 })
609 }
610
611 pub(crate) fn start(&self) {
614 self.changed();
615 }
616
617 fn changed(&self) {
620 self.dirty.store(true, Ordering::SeqCst);
621 if self.queued.swap(true, Ordering::SeqCst) {
622 return;
623 }
624 let me = self.me.clone();
625 crate::outside_listeners(Box::new(move || {
626 if let Some(me) = me.upgrade() {
627 me.queued.store(false, Ordering::SeqCst);
628 me.evaluate();
629 }
630 }));
631 }
632
633 fn looping(&self) -> bool {
638 let run = crate::drain_number();
639 let mut runs = self.runs.lock();
640 if runs.0 != run {
641 *runs = (run, 0);
642 }
643 runs.1 += 1;
644 let looping = runs.1 > MAX_EVALUATIONS;
645 if looping && !self.warned.swap(true, Ordering::Relaxed) {
646 log::warn!("{}: formulas reading each other do not settle", self.key);
647 }
648 looping
649 }
650
651 fn evaluate(&self) {
653 struct Busy<'a>(&'a Formula);
655 impl Drop for Busy<'_> {
656 fn drop(&mut self) {
657 self.0.busy.store(false, Ordering::SeqCst);
658 }
659 }
660 while self
661 .busy
662 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
663 .is_ok()
664 {
665 let busy = Busy(self);
666 while self.dirty.swap(false, Ordering::SeqCst) {
667 if self.looping() {
668 break;
669 }
670 self.refresh();
671 }
672 drop(busy);
673 if !self.dirty.load(Ordering::SeqCst) {
675 break;
676 }
677 }
678 }
679
680 fn refresh(&self) {
683 let Some(expr) = &self.expr else { return };
684 let evaluated = {
685 let mut states = self.states.lock();
686 states.extend(self.pending.lock().drain());
687 let mut ev = Eval {
688 states: &states,
689 wanted: Vec::new(),
690 severity: Severity::NoAlarm,
691 timestamp: None,
692 };
693 let result = ev.eval(expr);
694 let (severity, timestamp) = (ev.severity, ev.timestamp);
695 let old = self.shared.snapshot();
696 let new = match result {
697 Ok(Val::Pv(pv)) => PvState {
698 severity,
699 writable: false,
700 writable_known: false,
701 write_error: None,
702 ..*pv
703 },
704 Ok(v) => PvState {
705 connected: true,
706 value: match v {
707 Val::Str(t) => Value::Str(t),
708 v => Value::Double(v.num()),
709 },
710 severity,
711 timestamp,
712 ..PvState::default()
713 },
714 Err(Missing) => PvState {
715 connected: false,
716 ..old.clone()
717 },
718 };
719 (old, new, ev.wanted)
720 };
721 let (old, new, wanted) = evaluated;
722 if !same(&new, &old) {
724 self.shared.update(|s| *s = new);
725 }
726 self.follow(wanted);
727 }
728
729 fn follow(&self, wanted: Vec<(String, String)>) {
732 let mut inputs = self.inputs.lock();
733 let mut names: HashMap<String, String> = HashMap::new();
736 for (key, name) in wanted {
737 if key == self.key {
739 if !self.warned.swap(true, Ordering::Relaxed) {
740 log::warn!("{}: a formula cannot read itself", self.key);
741 }
742 continue;
743 }
744 let has_args = |n: &str| PvName::parse(n).args().is_some();
745 match names.get(&key) {
746 Some(n) if has_args(n) || !has_args(&name) => {}
747 _ => {
748 names.insert(key, name);
749 }
750 }
751 }
752 let new: Vec<(String, String)> = names
753 .iter()
754 .filter(|(k, _)| !inputs.contains_key(*k))
755 .map(|(k, n)| (k.clone(), n.clone()))
756 .collect();
757 let gone: Vec<String> = inputs
758 .keys()
759 .filter(|k| !names.contains_key(*k))
760 .cloned()
761 .collect();
762 let old: Vec<Input> = gone.iter().filter_map(|k| inputs.remove(k)).collect();
763 drop(inputs);
764 if !old.is_empty() {
765 self.states.lock().retain(|k, _| names.contains_key(k));
766 self.pending.lock().retain(|k, _| names.contains_key(k));
767 }
768 drop(old);
769 if new.is_empty() {
770 return;
771 }
772 self.subscribe(new);
773 }
774
775 fn subscribe(&self, new: Vec<(String, String)>) {
777 let Some(inner) = self.manager.upgrade() else {
778 return;
779 };
780 let manager = PvManager { inner, tag: None };
781 for (key, name) in new {
782 if self.inputs.lock().contains_key(&key) {
784 continue;
785 }
786 let pv = manager.subscribe(&name);
787 let me = self.me.clone();
788 let state_key = key.clone();
789 let listener: Listener = Arc::new(move |state: &PvState| {
792 if let Some(me) = me.upgrade() {
793 me.pending.lock().insert(state_key.clone(), state.clone());
794 me.changed();
795 }
796 });
797 self.inputs
798 .lock()
799 .insert(key, (pv.clone(), listener.clone()));
800 pv.listen(&listener);
801 }
802 }
803}
804
805#[cfg(test)]
806mod tests {
807 use super::*;
808
809 fn eval(text: &str, states: &[(&str, Value)]) -> (Option<Val>, Vec<String>) {
810 let states = states
811 .iter()
812 .map(|(n, v)| {
813 let s = PvState {
814 connected: true,
815 value: v.clone(),
816 ..PvState::default()
817 };
818 (PvName::parse(n).key(), s)
819 })
820 .collect();
821 let mut ev = Eval {
822 states: &states,
823 wanted: Vec::new(),
824 severity: Severity::NoAlarm,
825 timestamp: None,
826 };
827 let v = ev.eval(&parse(text).unwrap()).ok();
828 (v, ev.wanted.into_iter().map(|(_, name)| name).collect())
829 }
830
831 fn num(text: &str) -> f64 {
832 eval(text, &[]).0.unwrap().num()
833 }
834
835 #[test]
836 fn arithmetic_and_logic() {
837 assert_eq!(num("1 + 2 * 3"), 7.0);
838 assert_eq!(num("(1 + 2) * 3"), 9.0);
839 assert_eq!(num("124913500/2.5e2"), 499654.0);
840 assert_eq!(num("-2^2"), -4.0);
841 assert_eq!(num("7 % 4 - 1"), 2.0);
842 assert_eq!(num("1 < 2 && 3 >= 3 || 0"), 1.0);
843 assert_eq!(num("!(1 == 1)"), 0.0);
844 assert_eq!(num("0 ? 5 : 1 ? 6 : 7"), 6.0);
845 assert_eq!(num("max(1, 4, 2) + abs(-1) + sqrt(16)"), 9.0);
846 assert_eq!(
847 eval("concat(\"a\", 1.5, \"b\")", &[]).0.unwrap().text(),
848 "a1.5b"
849 );
850 assert_eq!(num("\"a\" == \"a\""), 1.0);
851 }
852
853 #[test]
854 fn lists_the_pvs_a_formula_names() {
855 assert_eq!(
856 formula_inputs("(pv(\"X:Y\")*1000) + 'loc://m(1)' + `X:Y` + max(A, -B) + (C ? 1 : D)"),
857 ["X:Y", "loc://m(1)", "A", "B", "C", "D"]
858 );
859 let dynamic = r#"pv(concat("SR01C-DI-PIN-17:", toString('loc://mode<VString>("RATE")')))"#;
860 assert_eq!(formula_inputs(dynamic), [r#"loc://mode<VString>("RATE")"#]);
861 assert!(formula_inputs("1 +").is_empty());
862 }
863
864 #[test]
865 fn reads_pvs_by_any_spelling() {
866 let pvs = [("X:Y", Value::Double(2.0)), ("loc://m(1)", Value::Long(3))];
867 let (v, wanted) = eval("(pv(\"X:Y\")*1000) + 'loc://m(1)' + `X:Y` + X:Y", &pvs);
868 assert_eq!(v.unwrap().num(), 2007.0);
869 assert_eq!(wanted, ["X:Y", "loc://m(1)", "X:Y", "X:Y"]);
870 }
871
872 #[test]
873 fn computes_pv_names() {
874 let text = r#"pv(concat("SR01C-DI-PIN-17:", toString('loc://ll<VString>("RATE")')))"#;
875 let mode = ("loc://ll<VString>(\"RATE\")", Value::Str("LOG".into()));
876 let (v, wanted) = eval(text, std::slice::from_ref(&mode));
877 assert!(v.is_none());
879 assert_eq!(
880 wanted,
881 ["loc://ll<VString>(\"RATE\")", "SR01C-DI-PIN-17:LOG"]
882 );
883 let (v, _) = eval(text, &[mode, ("SR01C-DI-PIN-17:LOG", Value::Double(5.0))]);
884 assert_eq!(v.unwrap().num(), 5.0);
885 }
886
887 #[test]
888 fn missing_inputs_are_still_wanted() {
889 let (v, wanted) = eval("pv(\"A\") + pv(\"B\")", &[("B", Value::Double(1.0))]);
890 assert!(v.is_none());
891 assert_eq!(wanted, ["A", "B"]);
892 }
893
894 #[test]
895 fn bad_formulas_fail_to_parse() {
896 for text in ["1 +", "nope(1)", "pv(1, 2)", "(1", "'x", "1 $ 2"] {
897 assert!(parse(text).is_err(), "{text}");
898 }
899 }
900
901 #[test]
902 fn hostile_formulas_fail_to_parse_not_overflow() {
903 let deep = std::thread::Builder::new()
907 .stack_size(4 * 1024 * 1024)
908 .spawn(|| {
909 for n in [100, 100_000] {
912 for text in [
913 format!("{}1{}", "(".repeat(n), ")".repeat(n)),
914 format!("{}1", "-".repeat(n)),
915 format!("{}1", "!".repeat(n)),
916 format!("{}1", "+".repeat(n)),
917 "2^".repeat(n) + "2",
918 "1?".repeat(n) + "1" + &":1".repeat(n),
919 "1?1:".repeat(n) + "1",
920 "abs(".repeat(n) + "1" + &")".repeat(n),
921 ] {
922 assert!(parse(&text).is_err(), "{}", &text[..20]);
923 }
924 }
925 assert!(parse(&vec!["1"; 100_000].join("+")).is_err());
927 assert!(parse(&format!("max({})", vec!["1"; 100_000].join(","))).is_err());
928 let ok = format!("{}1{}", "(".repeat(30), ")".repeat(30));
930 assert_eq!(parse(&ok), Ok(Expr::Num(1.0)));
931 assert!(parse(&format!("{}1", "-".repeat(30))).is_ok());
932 assert_eq!(num(&vec!["1"; 512].join("+")), 512.0);
934 })
935 .unwrap()
936 .join();
937 assert!(deep.is_ok());
938 }
939
940 #[test]
941 fn a_formula_pv_follows_its_inputs() {
942 let pvs = PvManager::new();
943 let f = pvs.subscribe("=(pv(\"loc://fa(2)\")*1000) + 'loc://fb(1)'");
944 assert_eq!(f.state().value, Value::Double(2001.0));
945 assert!(f.connected());
946 pvs.subscribe("loc://fa").write("3");
947 assert_eq!(f.state().value, Value::Double(3001.0));
948 assert!(f.state().format(None, false).starts_with("3001"));
949 }
950
951 #[test]
952 fn a_formula_pv_switches_with_a_computed_name() {
953 let pvs = PvManager::new();
954 let text = r#"=pv(concat("loc://fsw_", toString('loc://fsw_mode<VString>("a")')))"#;
955 let a = pvs.subscribe("loc://fsw_a(1)");
956 let b = pvs.subscribe("loc://fsw_b<VString>(\"bee\")");
957 let f = pvs.subscribe(text);
958 assert_eq!(f.state().value, Value::Double(1.0));
959 pvs.subscribe("loc://fsw_mode").write("b");
960 assert_eq!(f.state().value, Value::Str("bee".into()));
962 a.write("5");
963 b.write("buzz");
964 assert_eq!(f.state().value, Value::Str("buzz".into()));
965 let Some(formula) = &f.0.formula else {
967 panic!()
968 };
969 let inputs = formula.inputs.lock();
970 assert!(inputs.contains_key("loc://fsw_b") && !inputs.contains_key("loc://fsw_a"));
971 }
972
973 #[test]
974 fn a_formula_reads_two_spellings_of_a_pv_as_one() {
975 let (tx, rx) = std::sync::mpsc::channel();
978 std::thread::spawn(move || {
979 let pvs = PvManager::new();
980 let f = pvs.subscribe("='loc://fsp(1)' + 'loc://fsp' + pv(\" loc://fsp\")");
981 let _ = tx.send(f.state().value);
982 });
983 let value = rx.recv_timeout(std::time::Duration::from_secs(5));
984 assert_eq!(value, Ok(Value::Double(3.0)));
985 }
986
987 #[test]
988 fn a_computed_name_switching_on_two_threads_does_not_deadlock() {
989 let (tx, rx) = std::sync::mpsc::channel();
990 std::thread::spawn(move || {
991 let pvs = PvManager::new();
992 let x = pvs.subscribe("loc://fdx(1)");
993 let y = pvs.subscribe("loc://fdy(2)");
994 let sel = pvs.subscribe("loc://fdsel<VString>(\"loc://fdx\")");
995 let f = pvs.subscribe("=pv(toString('loc://fdsel')) + 0 * 'loc://fdz(0)'");
996 let z = pvs.subscribe("loc://fdz");
997 let flip = std::thread::spawn(move || {
998 for i in 0..2000 {
999 sel.write(if i % 2 == 0 { "loc://fdy" } else { "loc://fdx" });
1000 }
1001 sel.write("loc://fdy");
1002 });
1003 for i in 0..2000 {
1004 z.write(i.to_string());
1005 x.write("1");
1006 }
1007 flip.join().unwrap();
1008 y.write("2");
1009 let _ = tx.send(f.state().value);
1010 });
1011 let value = rx.recv_timeout(std::time::Duration::from_secs(20));
1012 assert_eq!(value, Ok(Value::Double(2.0)));
1013 }
1014
1015 #[test]
1016 fn a_formula_reading_a_formula_keeps_up() {
1017 let pvs = PvManager::new();
1018 let g = pvs.subscribe("=pv(\"=2*'loc://fch(1)'\") + 100");
1019 assert_eq!(g.state().value, Value::Double(102.0));
1020 let x = pvs.subscribe("loc://fch");
1021 for v in [5.0, 7.0, 9.0] {
1022 x.write(v.to_string());
1023 assert_eq!(g.state().value, Value::Double(2.0 * v + 100.0));
1024 }
1025 }
1026
1027 #[test]
1028 fn formulas_reading_each_other_settle() {
1029 let (tx, rx) = std::sync::mpsc::channel();
1032 std::thread::spawn(move || {
1033 let pvs = PvManager::new();
1034 let a = "=1 ? 0/0 : pv(toString('loc://fsna'))";
1035 let b = "=1 ? 0/0 : pv(toString('loc://fsnb'))";
1036 pvs.subscribe("loc://fsnb<VString>(\"x\")").write(a);
1037 pvs.subscribe("loc://fsna<VString>(\"x\")").write(b);
1038 let _nan = pvs.subscribe(a);
1039 let a = "='loc://fso(1)' ? 1 : 1 - pv(toString('loc://fsob'))";
1040 let b = "=pv(toString('loc://fsoa')) + 0";
1041 pvs.subscribe("loc://fsoa<VString>(\"x\")").write(a);
1042 pvs.subscribe("loc://fsob<VString>(\"x\")").write(b);
1043 let (_a, _b) = (pvs.subscribe(a), pvs.subscribe(b));
1044 pvs.subscribe("loc://fso").write("0");
1045 let _ = tx.send(());
1046 });
1047 assert!(rx.recv_timeout(std::time::Duration::from_secs(10)).is_ok());
1048 }
1049
1050 #[test]
1051 fn formulas_naming_each_other_do_not_deadlock() {
1052 let (tx, rx) = std::sync::mpsc::channel();
1053 std::thread::spawn(move || {
1054 let pvs = PvManager::new();
1055 let _n = pvs.subscribe("loc://fcn<VString>(\"=pv(toString('loc://fcm'))\")");
1056 let _m = pvs.subscribe("loc://fcm<VString>(\"=pv(toString('loc://fcn'))\")");
1057 let f = pvs.subscribe("=pv(toString('loc://fcn'))");
1058 let _ = tx.send(f.connected());
1059 });
1060 assert_eq!(
1061 rx.recv_timeout(std::time::Duration::from_secs(5)),
1062 Ok(false)
1063 );
1064 }
1065
1066 #[test]
1067 fn a_formula_waits_for_its_inputs_and_cannot_be_written() {
1068 let pvs = PvManager::new();
1069 let f = pvs.subscribe("=pv(\"sim://nope\") + 1");
1070 assert!(!f.connected());
1071 f.write("1");
1072 assert!(pvs.notice().1.contains("cannot be written"));
1073 let bad = pvs.subscribe("=1 +");
1074 assert!(bad.state().error.unwrap().contains("bad formula"));
1075 }
1076}