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
|
# Core language pitfalls
Part of the [pitfalls index](../PITFALLS.md). Every bullet was confirmed against the
source, most also by compiling and running test programs.
- **OUTPUT accepts comma-separated expressions (§5.1).** Values are evaluated and
printed left to right without automatic separators; the final scalar adds a newline.
Whole arrays and subarrays still print each element on its own line, including in
mixed output lists; a final array adds no extra newline. INPUT accepts writable
scalar locations, including array elements, public record/class fields and
pointer dereferences, using the shared lvalue checks. Whole arrays and whole
records/classes remain unsupported INPUT targets. Scalar INPUT reads a validated
whitespace-delimited token (IR helpers in `InputGen.cc`, no scanf); STRING INPUT reads
the rest of the line via READ_INPUT. Bad input and end of input are runtime errors.
- **Arrays cross function boundaries only as parameters.** `ArrayTable` is scoped per
function, so referencing another function's array fails cleanly as
"Undeclared array". Whole arrays pass as `ARRAY OF T` / `ARRAY[l:u,...] OF T`
parameters (BYVAL copies the entire buffer; the copy is freed when the callee ends); a whole-array
argument against a scalar parameter is still a compile error. FUNCTIONs cannot
RETURN arrays. A whole-array variable is also rejected as an operand or as a
value of its element type.
- **Arrays are additionally block-scoped.** An array DECLAREd inside an
IF/WHILE/REPEAT/FOR/TRY/CATCH body is not visible after the block ("Undeclared
array") — its buffer setup and bound SSA values live inside the branch, so letting
it escape would mean wild-pointer writes or invalid IR. Scalars remain function-scoped
(their alloca is hoisted to the entry block); declare at the top of the function
like the exam board intends and neither matters.
- **Parameter modes are sticky and BYREF is FUNCTION-forbidden (§8.3).**
`(BYREF a : T, b : T)` makes `b` BYREF too. Literal arguments against an
accidentally-BYREF parameter fail with "BYREF argument must be a variable";
annotate `BYVAL` explicitly.
- **`/` always yields REAL** (double FDiv), even for two INTEGERs; integer division is
DIV/MOD only.
- CHR returns CHAR, not STRING; CHR(0) is a NUL CHAR that converts to a one-byte STRING.
- STR_TO_NUM always returns REAL and shares parseNumber (strtod, full consumption, overflow rejected, only `0-9 . e E + -` after leading whitespace, so no nan/inf/hex) with IS_NUM and INPUT REAL (`__cps_parse_real` in InputGen.cc); change them together.
- NUM_TO_STR keeps the full signed i64 range for INTEGER; REAL uses the shortest
round-trip text (realText in string.cc), byte-identical to OUTPUT's IR helper in
OutputGen.cc (`__cps_output_real`); change both together. C_DOUBLE output keeps `%f`.
- **`getExprTypeInfo` returns nullptr** for expressions it can't classify (null-check
it at any new call site) and internally defaults to INTEGER for unclassified
arithmetic expressions; unknown or unmatched calls diagnose and return nullptr.
OUTPUT reports an error for a nullptr result.
- **CONSTANT** accepts only literals (including negative numeric literals), never
expressions or other names. Constants have lexical block scope and may shadow outer
names; bindings hiding constants are restored on block exit too. Functions inherit
previously declared constants from their own unit and all visible imported
top-level constants. Constants have no writable storage: assignment, INPUT,
READFILE targets, FOR variables, BYREF and address-of are rejected.
- **CASE OF** reads its selector once and tests expression labels in source order,
with inclusive TO ranges and an optional final OTHERWISE. Label headers occupy their
own source line; bodies may begin after the colon or on subsequent lines.
Comparisons reuse ordinary operator rules; records/classes need BOOLEAN-returning
comparison overloads, and whole arrays are unsupported.
- The lexer emits no newline tokens; most grammar is keyword-delimited, while CASE
uses token line information to recognize labels. Bare `RETURN` is recognized
before block terminators and the next CASE label.
- `verifyModule` runs at the end of `compile()`; invalid modules make the driver
exit non-zero without writing IR.
- The FOR loop variable must be pre-DECLAREd as INTEGER; FOR reuses that alloca. The
loop direction is decided at run time from the STEP value's sign; STEP is evaluated
exactly once, before the loop. (The TO expression is still re-evaluated every
iteration.)
- WHILE accepts both the standard header without DO and the older WHILE DO form.
- **Literal escapes.** C++17 escapes are decoded by the lexer. Unicode escapes encode
UTF-8 in strings; CHAR Unicode escapes must be ASCII. Ordinary STRING literals
retain legacy NUL truncation: `"a\0b"` is observed as `"a"`. STRING can retain NUL
bytes built through CHR or the count/character constructor; raw C text I/O
truncates at NUL. The suffix `"text"C` yields C_STRING without a STRING class.
|