emitcm7.c reimplements the emitter for sources that mention M7 syntax, and
three things were lost in the port. All of them only reached M4 fixtures,
because M1-M3 and M6 take the M6 fast path.
Local initializers named "0". The LET/VAR case passed the declaration node to
emit_lvalue, which matches only IDENT/MEMBER/INDEX and otherwise falls through
to the raw expression path -- a declaration node renders there as "0", so every
`var x = init;` emitted `0 = init;`. Teach emit_lvalue that a declaration names
its own storage, which also fixes the catch path that had the same call.
Aggregate initializers were not constant. A string-literal `const` lowered to a
maker call, but C89 requires a constant expression for aggregate initializers
at file scope and for automatics alike, and the build runs with -za. Restore
the braced form for both the local and the global path.
Slice helpers were never emitted. The final loop in m7_emit_type_helpers is
commented as reusing the M3 index/slice generator but only ported the index
half, so bodies called fe_slice_*/fe_full_*/fe_tail_* that no declaration
defined. Emit the three slicers for array and slice types.
The first defect masked the other two: wcc386 died on `0 = ...` before it could
reach them, and wcl386 reports that as "Unable to invoke wcc386.exe" with no
diagnostic, which is why this needed bisecting against master's output rather
than reading an error message.
M1-M7: 7 failed, 176 passed -> 3 failed, 180 passed. The remainder is m5
runtime, which is a separate fixture issue.
SPEC.md allows `try` only inside a function returning an error union, but
check.c only tests it in the FE_N_EXPR_STMT case, so `var x = try e;` and
`x = try e;` walk straight past. m5/owned.fe and m5/runtime.fe both depend on
that gap.
Moving the check onto the try expression is a four-line change and it is
correct, but it cannot land yet. runtime.fe's `run` allocates and returns a
value, so closing the hole forces it to return an error union -- and master
rejects `return <value>;` in `-> !i32` ("return type mismatch") as well as a
bare `return;` in `-> !void` ("void expression returned from value function").
Both need contextual success construction, which is M7 work. `catch` and
`@trap()`, the two spellings SPEC offers as alternatives, are also M7-only, so
there is no way to express `run` legally on master today. All three paths were
tried in DOSBox-X, not assumed.
Fix owned.fe now, since `main() -> !void` is legal today and matches
m4/try-fpr.fe, and leave the checker alone until M7 lands with the rest.
Verified: 155 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BScg8CF1sAAM2zVHAu5zvW
Every fec error message was written to stderr, and COMMAND.COM can only
redirect handle 1 -- ">" is the whole vocabulary, "2>" is not parsed at all.
So a failing compile recorded exit code 1 and a zero-byte log, and the actual
message went to a screen nobody reads. Confirmed directly: `FEC.EXE --check`
on a fixture that must fail produced rc=1 and 0 bytes of stdout.
That is why an unexpected compiler failure was undiagnosable. It also means
the M6 reject cases have only ever asserted "exit code was nonzero" -- the
error text they nominally check has never been observable to the runner.
Add fe_diag_stream(), which resolves once to stdout when FE_DIAG_STDOUT is set
and stderr otherwise, and route diag.c and driver.c through it. The default is
unchanged, so interactive use keeps writing to stderr; the runner sets the
variable in RUN.BAT. The stderr references in check.c and emit_c.c are the
Ferro language's own std.io.stderr writer and are deliberately untouched.
Verified in DOSBox-X: 155 passed. A rejecting compile now records its message,
source excerpt and caret in RESULTS\<key>.LOG.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BScg8CF1sAAM2zVHAu5zvW
The bounds-no-checks cases emitted and built but never ran, so they only
proved that --no-checks produces compilable C -- not that it removes the
check, which is the entire point of the flag.
A run case could not simply be appended. BOUNDS.FE returns the out-of-bounds
element directly, so with checks removed its exit code is whatever sits past
the array on the stack and there is no correct status to assert. Asserting on
the generated C instead does not work either: emit_c.c defines fe_trap_bounds
unconditionally and --no-checks only suppresses the call sites.
Add NOCHK.FE, which reads one element past a [2]i32 and returns x - x. That
is 0 for whatever garbage the unchecked read produced, so the same source has
a defined outcome both ways: compiled with checks it must trap, compiled with
--no-checks it must run to completion and exit 0. Register both halves and
drop the two BOUNDS-N cases they supersede.
Verified in DOSBox-X: 155 passed, including m3-nochk-trap failing as expected
and m3-nochk-off-run succeeding.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BScg8CF1sAAM2zVHAu5zvW