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
//! 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