docs: @SuppressWarnings("PMD")
    static Double eval(String expr, Map<String, ? extends Number> vars) {
      Deque<Double> stack = new ArrayDeque<>();
      String[] parts = expr.split("[,\\s]+");
      for (String part : parts) {
        switch (part) {
          case ":add":        binaryOp(stack, (a, b) -> a + b); break;
          case ":sub":        binaryOp(stack, (a, b) -> a - b); break;
          case ":mul":        binaryOp(stack, (a, b) -> a * b); break;
          case ":div":        binaryOp(stack, (a, b) -> a / b); break;
          case ":if-changed": ifChanged(stack);                 break;
          default:
            if (part.startsWith("{") && part.endsWith("}")) {
              Number v = vars.get(part.substring(1, part.length() - 1));
              if (v == null) v = Double.NaN;
              stack.addFirst(v.doubleValue());
            } else {
              stack.addFirst(Double.parseDouble(part));
            }
            break;
        }
      }
      return stack.removeFirst();
    }