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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#[derive(Debug, Clone, PartialEq)]
pub enum CoreOp {
// Operands
PushConst(f64),
PushVar(u8), // 0..15 = A..P
PushDoubleVar(u8), // 0..11 = AA..LL (string vars, fetched as numeric)
// Arithmetic
Add,
Sub,
Mul,
Div,
Mod,
Neg,
Power,
// Comparison
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
// Logical
And,
Or,
Not,
// Bitwise
BitAnd,
BitOr,
BitXor,
BitNot,
Shl,
Shr,
ShrLogical,
// Conditional
CondIf,
CondElse,
CondEnd,
// Functions (1 arg)
Abs,
Sqrt,
Exp,
Log10,
LogE,
Sin,
Cos,
Tan,
Asin,
Acos,
Atan,
Sinh,
Cosh,
Tanh,
Ceil,
Floor,
Nint,
IsNan(u8), // vararg: number of args
IsInf,
Finite(u8), // vararg: number of args
// Functions (2 arg)
Atan2,
Fmod,
// Vararg functions
Max(u8), // number of args
Min(u8), // number of args
// Binary operators (2 arg, infix)
MaxVal, // >?
MinVal, // <?
// Constants
Pi,
D2R,
R2D,
/// sCalc/aCalc `S2R` — arc-seconds to radians, PI/(180*3600)
/// (`sCalcPerform.c:470-473`, `aCalcPostfix.c:195`). Base has no such
/// constant.
S2R,
/// sCalc/aCalc `R2S` — radians to arc-seconds, (180*3600)/PI
/// (`sCalcPerform.c:475-478`).
R2S,
// Special
Random,
NormalRandom,
FetchVal,
/// C `FETCH_SVAL` (sCalcPerform.c:927-932) — push the previous *string*
/// result (`psresult`). String-calc only; the numeric and array
/// evaluators reject it, as their C element tables cannot emit it.
FetchSval,
// Assignment
StoreVar(u8), // 0..15 = A..P
StoreDoubleVar(u8), // 0..11 = AA..LL
// End
End,
}
#[derive(Debug, Clone, PartialEq)]
pub enum StringOp {
// Phase 2A: Core
/// C `LITERAL_STRING`. The bytes the compiler copied out of the source
/// verbatim (`sCalcPostfix.c:808`) — raw, un-decoded, backslashes intact.
/// `$T` / `TR_ESC` is the only translator.
PushString(Vec<u8>),
ToString, // STR: number→string
ToDouble, // DBL: string→number
Len, // string length
Byte, // first char ASCII value
// Phase 2B: Advanced
TrEsc,
Esc,
Printf,
Sscanf,
BinRead,
BinWrite,
Crc16,
Crc16Append, // MODBUS
Lrc,
LrcAppend, // AMODBUS
Xor8,
Xor8Append, // ADD_XOR8
Subrange, // [i:j]
Replace, // {find,replace}
SubLast, // |- last substring removal
/// sCalc `@` — C `A_FETCH` (`sCalcPerform.c:1446-1460`). The scalar argument
/// the operand INDEXES, so `@1` is B; the index is rounded with `myNINT` and
/// out of range answers 0. A sibling of aCalc's [`ArrayOp::DynFetch`], but it
/// lives here because the string evaluator is the only one whose C table can
/// emit it.
///
/// It is NOT in C's `USES_STRING` list (`sCalcPostfix.c:450-471`) — it answers
/// a double — which is why `uses_string` classifies it `false`.
DynFetch,
/// sCalc `@@` — C `A_SFETCH` (`sCalcPerform.c:1462-1476`). The STRING argument
/// the operand indexes, so `@@1` is BB; out of range is the EMPTY string, not
/// an error. This is where sCalc and aCalc genuinely part: aCalc's `@@` is
/// `A_AFETCH`, the ARRAY argument ([`ArrayOp::DynAFetch`]).
///
/// It IS in C's `USES_STRING` list (`sCalcPostfix.c:461`).
DynSFetch,
/// sCalc `@n:=v` — C `A_STORE` (`sCalcPerform.c:440-450` no-string,
/// `:897-906` string). The mirror of [`DynFetch`](Self::DynFetch): it pops
/// the VALUE and then the INDEX (the index expression was emitted first),
/// rounds the index with `myNINT`, and writes the scalar argument it names.
/// Out of range prints and stores nothing — it is not an error, exactly as
/// for the fetch. It pushes NOTHING back.
DynStore,
/// sCalc `@@n:=v` — C `A_SSTORE` (`sCalcPerform.c:909-918`). The same, for
/// the STRING argument (`toString` on the value first). aCalc's `@@:=` is a
/// different opcode, [`ArrayOp::DynAStore`], as its `@@` is a different
/// fetch.
DynSStore,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ControlOp {
Until(usize), // jump target = UntilEnd pc
UntilEnd(usize), // jump target = Until pc
}
#[derive(Debug, Clone, PartialEq)]
pub enum ArrayOp {
ConstIndex, // IX: [0,1,...,n-1]
ToArray, // ARR: scalar→array
ToDouble, // array→scalar (first element, empty=0.0)
Average,
StdDev,
Fwhm,
ArraySum,
ArrayMax,
ArrayMin,
IndexMax,
IndexMin,
IndexZero,
IndexNonZero,
// Phase 3B: Advanced
Smooth,
NSmooth,
Deriv,
NDeriv,
Cum,
Cat,
ArrayRandom,
ArraySubrange,
ArraySubrangeInPlace,
/// `FITPOLY(y)` (`aCalcPerform.c:1005-1012`) — fit `y = c + b*i + a*i*i` over
/// the operand's window and REPLACE it with the fitted curve.
FitPoly,
/// `FITMPOLY(y, mask)` (`:1013-1036`) — the same, with the fit restricted to
/// the points the mask admits and the window taken from the MASK operand.
FitMPoly,
/// `FITQ(y [,c][,b][,a])` (`:1193-1231`) — VARARG (`aCalcPostfix.c:140`), and
/// the payload is the argument count, because the trailing arguments NAME the
/// scalar arguments the coefficients are stored into.
FitQ(u8),
/// `FITMQ(y, mask [,c][,b][,a])` (`:1234-1285`) — likewise (`aCalcPostfix.c:141`).
FitMQ(u8),
/// `ANEG` (`aCalcPerform.c:772,1041`) — zero the NEGATIVE elements, keep the
/// rest. (The name says which sign it removes, not which it keeps.)
ANeg,
/// `APOS` (`aCalcPerform.c:773,1042`) — zero the POSITIVE elements.
APos,
/// `AVAL` (`aCalcPerform.c:534-539`) — push the record's previous AVAL, the
/// array-valued counterpart of the `VAL` token.
FetchAval,
/// `@` A_FETCH (`aCalcPerform.c:1461-1477`) — fetch the scalar argument the
/// operand INDEXES, i.e. `@1` is B. Out of range is 0.
DynFetch,
/// `@@` A_AFETCH (`aCalcPerform.c:1479-1494`) — the same for arrays: `@@1`
/// is BB. Out of range is an all-zero array.
DynAFetch,
/// `@n:=v` A_STORE (`aCalcPerform.c:494-504`) — pop the value, pop and round
/// the index, write that scalar argument. Out of range prints and stores
/// nothing. Pushes nothing.
DynStore,
/// `@@n:=v` A_ASTORE (`aCalcPerform.c:506-525`) — the same for the ARRAY
/// argument: an array value is copied element-wise, a SCALAR value is
/// broadcast to every element, and the store sets `amask` bit `n` so the
/// record posts the field it wrote.
DynAStore,
/// aCalc `LEN` (`aCalcPostfix.c:199`) — in the element table, so it LEXES and
/// COMPILES, but `aCalcPerform` has no `case LEN` and no `default`, so the
/// opcode falls straight through the switch and the operand is left on the
/// stack untouched. C's own table comment says "Array length not
/// implemented". Compiled aCalc: `LEN(AA)` returns AA unchanged. It is a
/// no-op, and the port must not invent a length for it.
LenNoop,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Opcode {
Core(CoreOp),
String(StringOp),
Control(ControlOp),
Array(ArrayOp),
}