1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! R13-2 — a `[…]` or `{…}` following a function call applies the function to
//! the SUBRANGE / REPLACE result, not to the function's own argument.
//!
//! C never emits a function at the `)`. `CLOSE_PAREN` (`sCalcPostfix.c:634-663`)
//! pops only down to the `(`, removes it, and — for a `VARARG_OPERATOR` below —
//! merely copies the accumulated argument count onto it. The function itself
//! stays on the operator stack at `in_stack_pri` 9.
//!
//! `[` and `{` are `BINARY_OPERATOR`s with `in_coming_pri` **11**
//! (`sCalcPostfix.c:215-216`, `aCalcPostfix.c:212-213`) — the only value above 10
//! in any of the three tables. So the pop-on-arrival (`>= in_coming_pri`) does
//! not reach the function at 9, and the function is emitted only later, AFTER the
//! subrange. C's own postfix dump for `ABS(1)[0,1]` is
//! `1, 0, 1, SUBRANGE, ABS_VAL`.
//!
//! The port used to flush the function at the `)`, emitting
//! `1, ABS, 0, 1, SUBRANGE` — the function bound to its own argument, and the
//! subrange applied to the result. Silently different numbers, no error.
//!
//! Every expected value below is an output of the compiled upstream engines
//! (`sCalcPostfix.c`+`sCalcPerform.c`, `aCalcPostfix.c`+`aCalcPerform.c`).
use epics_base_rs::calc::{
ArrayInputs, ArrayStackValue, StackValue, StringInputs, acalc, scalc, scalc_compile,
};
fn s(expr: &str) -> StackValue {
let mut inputs = StringInputs::new();
scalc(expr, &mut inputs).unwrap()
}
fn s_num(expr: &str) -> f64 {
match s(expr) {
StackValue::Double(v) => v,
other => panic!("{expr}: expected a double, got {other:?}"),
}
}
fn s_str(expr: &str) -> String {
match s(expr) {
StackValue::Str(v) => v.as_str_lossy().to_string(),
other => panic!("{expr}: expected a string, got {other:?}"),
}
}
/// Compiled-C harness shape: arraySize 8, AA = the given values, zero-padded.
fn a(expr: &str, aa: &[f64]) -> ArrayStackValue {
let mut inputs = ArrayInputs::new(8);
let mut buf = aa.to_vec();
buf.resize(8, 0.0);
inputs.arrays[0] = buf;
acalc(expr, &mut inputs).unwrap()
}
/// The four cases the audit pinned, each an output of the compiled C engine.
///
/// `LEN("abcd")[0,1]`: C takes the subrange `"ab"` first, then `LEN` of it = 2.
/// Binding `LEN` to `"abcd"` first would give 4 — what the port used to answer.
#[test]
fn len_applies_to_the_subrange_not_to_its_argument() {
assert_eq!(s_num(r#"LEN("abcd")[0,1]"#), 2.0);
}
/// `SQRT(4){"2","9"}`: C replaces `"2"` with `"9"` in `"4"`... which does not
/// match, so the subject `"4"` survives, and `SQRT` of it is 2. Binding `SQRT`
/// first gives `"2"` -> replace -> `"9"` -> 9, the old port's answer.
#[test]
fn sqrt_applies_to_the_replace_result() {
assert_eq!(s_num(r#"SQRT(4){"2","9"}"#), 2.0);
}
/// `INT("12.9")[0,1]`: subrange `"12"` first, then `INT` = 12. `INT` first would
/// round 12.9 to 13.
#[test]
fn int_applies_to_the_subrange() {
assert_eq!(s_num(r#"INT("12.9")[0,1]"#), 12.0);
}
/// aCalc, AA = [3,1,2,9]: `AMAX(AA)[0,1]` is the max of the SUBRANGE [3,1] = 3.
/// Binding `AMAX` first gives the max of the whole array, 9.
#[test]
fn acalc_amax_applies_to_the_array_subrange() {
assert_eq!(
a("AMAX(AA)[0,1]", &[3.0, 1.0, 2.0, 9.0]),
ArrayStackValue::Double(3.0)
);
}
/// The emitted program itself, against C's own postfix dump. C compiles
/// `ABS(1)[0,1]` to `1, 0, 1, SUBRANGE, ABS_VAL` — the function LAST. The port
/// used to emit `1, ABS, 0, 1, SUBRANGE`.
#[test]
fn the_function_is_emitted_after_the_subrange() {
let code = format!("{:?}", scalc_compile("ABS(1)[0,1]").unwrap().code);
assert_eq!(
code,
"[Core(PushConst(1.0)), Core(PushConst(0.0)), Core(PushConst(1.0)), \
String(Subrange), Core(Abs), Core(End)]",
"C dumps `1, 0, 1, SUBRANGE, ABS_VAL` for ABS(1)[0,1]"
);
}
/// A vararg function defers the same way, and the `,` inside the BRACKET must NOT
/// be charged to it — C's `SEPARATOR` (`sCalcPostfix.c:612-631`) stops at `(`,
/// `[` and `{` alike, but only `CLOSE_PAREN` (`:647-658`) hands the accumulated
/// count to a vararg below the `(`. This is why `[` / `{` are a stack entry of
/// their own rather than a second `LParen`: sharing one variant let the bracket's
/// `,` bump `MAX`'s argument count from 2 to 3.
///
/// Pinned on the emitted program, because `MAX` keeps its `nargs` in the opcode:
/// `Max(2)`, never `Max(3)`. (Note that C's runtime here feeds `MAX` the SUBRANGE
/// of its own second argument — `MAX(4, "7")` — which needs the vararg
/// string-coercion rule of W10-A1 to evaluate; the compile-stage property is what
/// R13-2 owns and is what this pins.)
#[test]
fn a_bracket_comma_is_not_charged_to_the_vararg() {
let code = format!("{:?}", scalc_compile("MAX(4,7)[0,0]").unwrap().code);
assert!(
code.contains("Max(2)"),
"MAX still takes exactly 2 arguments; the bracket's `,` is the SUBRANGE's. \
Got: {code}"
);
assert!(!code.contains("Max(3)"), "got: {code}");
}
/// Every other operator has `in_coming_pri` <= 10, so it DOES pop the function on
/// arrival and the emitted order is unchanged. These pin that the deferral did
/// not disturb the ordinary case. Compiled C: `SQRT(4)+1` = 3, `SQRT(4)*3` = 6,
/// `ABS(0-2)^2` = 4.
#[test]
fn an_ordinary_operator_after_a_call_still_pops_the_function() {
assert_eq!(s_num("SQRT(4)+1"), 3.0);
assert_eq!(s_num("SQRT(4)*3"), 6.0);
assert_eq!(s_num("ABS(0-2)^2"), 4.0);
assert_eq!(s_num("SQRT(4)>2"), 0.0);
// A `)` closing an OUTER call pops the inner function normally.
assert_eq!(s_num("SQRT(ABS(0-16))"), 4.0);
}
/// Subrange and replace on a plain (non-call) subject are untouched by the
/// restructure. Compiled C: `"abcdef"[1,3]` = `"bcd"`, `"abcdef"{"cd","X"}` =
/// `"abXef"`.
#[test]
fn subrange_and_replace_on_a_plain_subject_are_unchanged() {
assert_eq!(s_str(r#""abcdef"[1,3]"#), "bcd");
assert_eq!(s_str(r#""abcdef"{"cd","X"}"#), "abXef");
}
/// The deferral is per-DELIMITER, not per-function: `{` defers exactly as `[`
/// does, and in aCalc `{` is `SUBRANGE_IP` (`aCalcPostfix.c:213`), a different
/// opcode from sCalc's `REPLACE`. Compiled C, AA=[3,1,2,9]:
/// `AMAX(AA){1,2}` = 2 (the max over the in-place window [1,2], i.e. of [1,2]),
/// not 9. The other reductions defer the same way.
#[test]
fn acalc_reductions_defer_past_both_delimiters() {
let aa = [3.0, 1.0, 2.0, 9.0];
assert_eq!(a("AMAX(AA){1,2}", &aa), ArrayStackValue::Double(2.0));
assert_eq!(a("AMIN(AA)[0,2]", &aa), ArrayStackValue::Double(1.0));
assert_eq!(a("SUM(AA)[0,1]", &aa), ArrayStackValue::Double(4.0));
assert_eq!(a("AVG(AA)[2,3]", &aa), ArrayStackValue::Double(5.5));
}
/// Writing the subrange INSIDE the parentheses and writing it AFTER them now
/// mean the same thing, which is C's point: the subrange runs first either way.
/// Compiled C: `LEN("abcdef")` = 6, `LEN("abcdef"[0,3])` = 4, and
/// `LEN("abcdef")[0,3]` = 4 — both spellings take `"abcd"` and measure it.
/// Before the fix the second spelling answered 6, because `LEN` ran first and the
/// subrange then sliced the digit string `"6"`.
#[test]
fn the_subrange_runs_first_whichever_side_of_the_paren_it_is_on() {
assert_eq!(s_num(r#"LEN("abcdef")"#), 6.0);
assert_eq!(s_num(r#"LEN("abcdef"[0,3])"#), 4.0);
assert_eq!(s_num(r#"LEN("abcdef")[0,3]"#), 4.0);
}