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
//! Single owner of "a put landed on a CALC-class field (`CALC`, `OCAL`) →
//! compile it → apply the record type's C disposition".
//!
//! C runs the compile from `special()` (`SPC_CALC`, or the field-index switch
//! in the synApps records), i.e. *after* `dbPut` has already stored the new
//! string. Two dispositions hang off that one hook, and they are not the same:
//!
//! - **calcRecord** (`calcRecord.c:139-155`): a `postfix()` failure is returned
//! to the caller as `S_db_badField`. `dbPut` then skips the field's monitor
//! post (`dbAccess.c:1399-1405` — `if (status) goto done`) and `dbPutField`
//! skips the `pp(TRUE)` process, so the client's write FAILS
//! (rsrv `write_action` → `ECA_PUTFAIL`). The uncompilable string stays
//! stored and `RPCL` is left as an empty program.
//! - **calcoutRecord** (`calcoutRecord.c:326-345`), **sCalcoutRecord**
//! (`sCalcoutRecord.c:462-480`) and **aCalcoutRecord**
//! (`aCalcoutRecord.c:469-491`): the `postfix()` *return value* is stored in
//! the `DBF_LONG` `CLCV`/`OCLV` field, `DBE_VALUE` is posted for it, and
//! `special()` returns 0 — the put SUCCEEDS.
//!
//! Both dispositions consume the same two things: the compiled program and
//! `postfix()`'s return status. That status is **0 on success and -1 on
//! failure** (`postfix.c:239,507`; `sCalcPostfix.c:430,873-881`;
//! `aCalcPostfix.c:437,801-809`) — never the `CALC_ERR_*` code, which only
//! reaches the errlog line. So `caget calcout.CLCV` on a C IOC reads back 0 or
//! -1, and this module is what makes both records agree on that.
//!
//! The three engines differ on the empty expression, and the difference is
//! wire-visible: base `postfix("")` is `CALC_ERR_NULL_ARG` → -1
//! (`postfix.c:235-240`), while `sCalcPostfix("")` / `aCalcPostfix("")` return
//! 0 with an empty program (`sCalcPostfix.c:432-434`, `aCalcPostfix.c:439-441`).
//! They do NOT differ on what that program then does: it fails every evaluation
//! (see [`CompiledExpr::empty`]).
//!
//! # Invariant
//!
//! **A CALC-class field always owns a program.** Success, empty and failure all
//! produce one — C never leaves the `RPCL` buffer absent, it writes
//! `END_EXPRESSION` into it on every failure path. So no caller may ask whether
//! a program is present; a broken CALC is not "nothing to run", it is
//! "something that always fails", and that is precisely how C makes it alarm on
//! every process instead of only at compile time.
use crate;
/// C `postfix()` success status.
pub const POSTFIX_OK: i32 = 0;
/// C `postfix()` failure status — what lands in `CLCV`/`OCLV`.
pub const POSTFIX_ERR: i32 = -1;
/// What a CALC-class compile produced: C's `RPCL`/`ORPC` program plus the
/// `postfix()` return status.
pub
/// C base `postfix()` (`postfix.c`) — the numeric engine behind calc, calcout
/// and swait. An empty expression is `CALC_ERR_NULL_ARG`, status -1
/// (`crate::calc::compile` owns that rule).
pub
/// C `sCalcPostfix()` (`sCalcPostfix.c:410-434`) — the string engine behind
/// scalcout. An empty expression is a valid empty program, status 0, unlike
/// base `postfix()`.
pub
/// C `aCalcPostfix()` (`aCalcPostfix.c:410-441`) — the array engine behind
/// acalcout. An empty expression is a valid empty program, status 0.
pub