Skip to main content

pv/
formula.rs

1//! Formula PVs: `=expr` as in CS-Studio BOY, or `eq://expr` as in Phoebus.
2//!
3//! An expression of numbers, `"strings"` and PVs, which are named by
4//! `pv("name")`, `'name'`, `` `name` `` or a bare name such as `X:Y`, with
5//! arithmetic (`+ - * / % ^`), comparisons, `! && ||`, `c ? a : b`, the
6//! string functions `concat` and `toString`, and the usual maths functions.
7//!
8//! A PV's name may itself be computed, as the DLS diagnostics screens do to
9//! pick a PV by a `loc://` PV that a menu sets:
10//! `pv(concat("SR01C-DI-PIN-17:", toString('loc://mode<VString>("RATE")')))`.
11//! The formula then follows whichever PV the name names.
12//!
13//! There is no task: the formula is evaluated again whenever a PV it reads
14//! changes, by the thread that changed it once that has let go of the PV's
15//! lock: a listener only leaves the input's state. So evaluating, which
16//! takes the formula's own lock and subscribes to inputs, holds no other
17//! PV's lock, and formulas reading each other cannot wait for each other.
18//! One thread at a time evaluates a formula; an input changing meanwhile
19//! leaves its state for that thread.
20
21use 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
29/// The PVs a formula's text names (`a+b` for the formula `=a+b`), each
30/// once, in order; none when it does not parse. A PV whose name is
31/// computed is left out, though the PVs its name is computed from are not.
32pub 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/// A parsed formula.
55#[derive(Clone, Debug, PartialEq)]
56enum Expr {
57    Num(f64),
58    Str(String),
59    /// A PV named in the text.
60    Pv(String),
61    /// A PV whose name is computed.
62    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    /// A quoted or bare PV name, or a function's name before `(`.
93    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            // An exponent, as in 1e6 or 2.5E-3.
118            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            // A PV name, which may hold double quotes: 'loc://x<VString>("a")'.
155            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
181/// A recursive descent parser, lowest precedence first.
182struct Parser {
183    toks: Vec<Tok>,
184    at: usize,
185    /// Nesting of brackets, `?:`, prefix operators and `^`, bounded by
186    /// `MAX_DEPTH`.
187    depth: usize,
188}
189
190/// Deepest nesting of brackets, `?:`, prefix operators and `^` a formula
191/// may have. The parser, the evaluator and `Drop` all recurse once per level,
192/// and a display from the web is not trusted to keep within the stack.
193const MAX_DEPTH: usize = 64;
194/// Most tokens a formula may have: a long chain (`1+1+1+...`) builds a deep
195/// left-leaning tree that the evaluator and `Drop` walk recursively.
196const MAX_TOKENS: usize = 1024;
197
198impl Parser {
199    /// `f`, one level deeper.
200    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    /// Binary operators at `level` and above.
249    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
322/// A call of function `f`, checked here so a bad formula fails at once.
323fn 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/// An intermediate value.
399#[derive(Clone, Debug)]
400enum Val {
401    Num(f64),
402    Str(String),
403    /// A PV's state, kept whole so `=pv(...)` alone passes on its
404    /// metadata (enum labels, precision, units, limits).
405    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
438/// One evaluation over the latest states of the PVs read.
439struct Eval<'a> {
440    /// By [`PvName::key`], so every spelling of a PV reads one state.
441    states: &'a HashMap<String, PvState>,
442    /// Every PV the formula reads now: its key, and its name as written.
443    wanted: Vec<(String, String)>,
444    severity: Severity,
445    timestamp: Option<web_time::SystemTime>,
446}
447
448/// Why a formula has no value: a PV it reads has none yet.
449struct 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    /// Every argument is evaluated, even when one is missing, so `wanted`
466    /// holds every PV a name can be computed for.
467    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
533/// Most times a formula is evaluated in one run of a thread's left work.
534const MAX_EVALUATIONS: u32 = 1000;
535
536/// Whether two states are the same, a NaN value being the same as itself.
537fn 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
550/// A PV a formula reads, with the listener feeding its state to the formula.
551type Input = (Pv, Listener);
552
553/// A formula PV: its inputs, and the state its value is written to.
554pub(crate) struct Formula {
555    /// `None` when the text does not parse.
556    expr: Option<Expr>,
557    shared: Arc<Shared>,
558    /// For subscribing to the inputs: weak, so a formula does not keep the
559    /// manager alive.
560    manager: Weak<Inner>,
561    me: Weak<Formula>,
562    key: String,
563    /// The latest state of each input, by its key, as the evaluating thread
564    /// last took them from `pending`.
565    states: Mutex<HashMap<String, PvState>>,
566    /// The inputs by key, with the listener feeding `pending` from each.
567    inputs: Mutex<HashMap<String, Input>>,
568    /// Input states not yet evaluated. Never held while calling out.
569    pending: Mutex<HashMap<String, PvState>>,
570    /// Whether an input changed since the last evaluation began, whether
571    /// an evaluation is queued to run outside listeners, and whether a
572    /// thread is evaluating.
573    dirty: AtomicBool,
574    queued: AtomicBool,
575    busy: AtomicBool,
576    /// Whether reading itself or not settling has been reported, so it is
577    /// said once.
578    warned: AtomicBool,
579    /// The thread's run of left work it last evaluated in, and how many
580    /// times in that run.
581    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    /// Subscribe to the inputs. Not done in [`Formula::new`], which is
612    /// called with the manager's subscriptions locked.
613    pub(crate) fn start(&self) {
614        self.changed();
615    }
616
617    /// Evaluate again once this thread holds no PV's lock, unless a thread
618    /// is evaluating already: then it will, as it finds `dirty` set.
619    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    /// Whether this is one evaluation too many in this thread's run of left
634    /// work: formulas reading each other whose values never settle (NaN,
635    /// or 1 - each other) would otherwise evaluate each other for ever. It
636    /// waits for the next change of an input from outside the loop.
637    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    /// Evaluate while `dirty`, unless another thread is evaluating.
652    fn evaluate(&self) {
653        /// Lets go of the formula, even if evaluating it panicked.
654        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            // A change after the last check but before letting go is ours.
674            if !self.dirty.load(Ordering::SeqCst) {
675                break;
676            }
677        }
678    }
679
680    /// Evaluate, publish the result, and follow the PVs it now reads. Only
681    /// one thread at a time, from [`Formula::evaluate`].
682    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        // Only a change goes out, so formulas reading each other settle.
723        if !same(&new, &old) {
724            self.shared.update(|s| *s = new);
725        }
726        self.follow(wanted);
727    }
728
729    /// Subscribe to the PVs in `wanted` not yet subscribed, and drop those
730    /// no longer read (when a computed name changes).
731    fn follow(&self, wanted: Vec<(String, String)>) {
732        let mut inputs = self.inputs.lock();
733        // Keyed, so two spellings of one PV are one input; the one giving a
734        // local PV's initial value is the one subscribed.
735        let mut names: HashMap<String, String> = HashMap::new();
736        for (key, name) in wanted {
737            // Its own updates would evaluate it again for ever.
738            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    /// Subscribe to the inputs `new`, by key and name, and listen to each.
776    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            // Subscribed meanwhile, by an evaluation after the one asking.
783            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            // Called under the input's lock: it only leaves the state, for
790            // this thread to evaluate once it has let go.
791            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        // Not there yet, but now wanted.
878        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        // On a known stack, so unbounded recursion fails this test instead
904        // of passing on a larger one. Release builds need a tenth of this
905        // for the deepest formulas allowed.
906        let deep = std::thread::Builder::new()
907            .stack_size(4 * 1024 * 1024)
908            .spawn(|| {
909                // Nested past the cap, both within the token limit and far
910                // beyond it.
911                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                // Long, flat formulas.
926                assert!(parse(&vec!["1"; 100_000].join("+")).is_err());
927                assert!(parse(&format!("max({})", vec!["1"; 100_000].join(","))).is_err());
928                // Reasonable nesting still parses.
929                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                // The longest chain allowed evaluates, and is dropped.
933                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        // Passed through whole: a string PV stays a string.
961        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        // Only the PV now named is followed.
966        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        // Each spelling once deadlocked, listening to the PV from its own
976        // listener.
977        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        // NaN is never equal to itself, and 1 - B with B = A never settles;
1030        // both once evaluated each other for ever.
1031        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}