- Add CHANGES.TXT with full version history (DOS-friendly format)
- Add DOS/FreeDOS section to README.md
- Move compiler memory safety and DOS target to Completed in roadmap
- Add hal_dos.c to architecture.md module map
- Add .tab-color to .gitignore
The 4KB screen buffer (80x25x2 bytes) was allocated from near heap via
calloc(), which exhausted the 64KB data segment on 16-bit DOS. Now uses
_fcalloc()/_ffree() from OpenWatcom's far heap on 16-bit, keeping the
buffer outside DGROUP.
The TUI now works fully on 16-bit FreeDOS: full-screen editor, F-key
bar, cursor positioning, and scroll -- all via BIOS INT 10h through the
DOS HAL, with the screen buffer in far memory.
Changes:
- tui.h: GW_FAR macro (expands to __far on 16-bit, nothing elsewhere),
tui.screen declared as tui_cell_t GW_FAR *
- tui.c: _fcalloc/_ffree for 16-bit, _fmemmove for scroll_up()
- TUI_CELL() macro works unchanged (far pointer dereference is
transparent)
Fix 6 crash paths when TUI screen buffer allocation fails (common on
16-bit DOS due to near-heap exhaustion):
- main.c: REPL and AUTO mode fall back to fgets-based line reader
- tui.c: tui_key_on/off/list return early if tui.screen is NULL
Add DOS build documentation to getting-started.md (16-bit and 32-bit
targets, running on FreeDOS). Fix stale version string (0.14.0 -> 0.16.0).
New Makefile.dos16 builds with OpenWatcom wcc (16-bit, MEDIUM model)
producing a standard MZ executable that runs on any DOS without DOS/4GW.
All 24 source files compile clean; tested on FreeDOS 1.4 via QEMU.
Changes for 16-bit compatibility:
- hal_dos.c: INTX macro selects int86() vs int386() based on _M_I86
- sound.c: reduce stack buffer from 8192 to 512 samples on 16-bit
- tui.c: gracefully disable TUI if screen buffer allocation fails
(near heap exhaustion common on 16-bit), batch mode still works
- .gitignore: add .obj/.exe/.err/.lib for OpenWatcom build artifacts
Size comparison:
- 32-bit DOS/4GW: 175KB + 265KB extender = 440KB total
- 16-bit real-mode: 127KB standalone
The 32-bit build (Makefile.dos) and Linux build are unaffected.
72/72 interpreter tests pass.
1. ABS() now preserves integer argument type in codegen — ABS(int%)
returns int%, uses gw_int_neg in safe mode. Previously always emitted
fabs() returning float, causing ABS(30000)+ABS(30000) to silently
produce 60000.0 instead of raising Overflow.
2. SGN() return type added to VT_INT list in peek_expr_type, so
SGN(A%)+32767 correctly triggers gw_int_add overflow check.
3. gwrt_array_elem() now uses ERR_BS (Subscript out of range) instead
of ERR_FC (Illegal function call) for bounds errors, matching the
safe variant and the interpreter. ON ERROR handlers see ERR=9 in
both modes.
4. COMMON variables now tracked as assigned in analysis warnings,
preventing false "used but never assigned" warnings.
5. IF...THEN followed by inline assignment (IF 1 THEN A=5) now
correctly sets assign_ctx after THEN, preventing false uninitialized
variable warnings.
In the buffered expression path, left_type was set once from
peek_expr_type() and never updated. After I% * 2.5, the intermediate
result is float but left_type stayed VT_INT, causing the subsequent
+ I% to incorrectly use gw_int_add which truncated the float to
int16_t and raised a spurious overflow.
Now left_type is updated after each binary operator based on type
promotion rules: division and power produce VT_DBL, comparisons
produce VT_INT, and mixed int/float operations promote to the wider
type.
1. peek_expr_type() now handles parenthesized expressions — (A%) + (B%)
correctly triggers gw_int_add in --safe mode instead of falling back
to plain C addition.
2. FOR limit and step assignments use gw_cint() for integer variables in
--safe mode, catching out-of-range values (e.g., FOR I% = 1 TO 100000
now raises Overflow instead of silently truncating).
3. SWAP sets multi_assign so both variables are marked as assigned in the
--warn uninitialized variable analysis.
4. Bare NEXT (without variable name) now emits the loop variable increment
from the FOR stack, fixing a pre-existing bug where the loop body
would execute but the variable never advanced.
All 24 source files compile and link for DOS/4GW 32-bit target using
OpenWatcom V2 cross-compiler (wcc386 -bt=dos -mf -za99 -D__MSDOS__).
Platform portability fixes:
- hal_dos.c: use int386() instead of int86() for 32-bit DOS
- interp.c: mkdir() 1-arg on DOS, _dos_findfirst/findnext for FILES,
monotonic_time() portable wrapper for clock_gettime/clock()
- virmem.c: replace clock_gettime with portable time()/localtime()
- graphics.c: define M_PI, avoid non-constant aggregate initializers
- tui.c: guard sys/ioctl.h and sigaction, use signal() on DOS,
use HAL screen size instead of TIOCGWINSZ
Produces: gwbasic.exe (154KB LE executable, requires DOS4GW.EXE)
Linux build and all 72+14+69 tests unaffected.
platform/hal_dos.c: DOS HAL implementation using BIOS INT 10h for
screen/cursor, INT 16h for keyboard, direct character output via
BIOS write-char. Compile-time selection via __MSDOS__ define.
Linux HAL (hal_posix.c) unchanged -- full backward compatibility.
hal.h: add hal_dos_create() declaration under __MSDOS__ guard.
main.c, gwrt.c: select HAL at compile time via #ifdef __MSDOS__.
Makefile.dos: OpenWatcom wmake build file targeting DOS/4GW 32-bit.
Builds GWBASIC.EXE (interpreter), GWBASCOM.EXE (compiler),
GWRT.LIB (runtime library for compiled programs).
All 72 interpreter, 14 kernel, and 69 compiler tests continue to
pass on Linux (no regression).
Remove Unicode em dashes (U+2014) and en dashes (U+2013) from all
Markdown files. Use ASCII -- for parenthetical breaks and - for
hyphenation, matching standard plain-text conventions.
README.md: rewrite with v0.16.0 version, compiler section, Jupyter
kernel section, hardware I/O in statement table, accurate test counts
(72 interpreter + 14 kernel + 69 compiler), build instructions for
all three targets.
docs/roadmap.md: clean up Phase 2 accumulation into single coherent
compiler description. List all language coverage, operators, functions,
and optimizations. Remove stale intermediate progress markers.
docs/development.md: update test counts (72 programs, 68 golden files),
add kernel and compiler test commands.
Constant folding: when both operands of an arithmetic operator are
numeric literals, compute the result at compile time. Eliminates
runtime computation for expressions like 2*3.14 or 4*1024.
FOR step=1 elision: when STEP is omitted (default 1), skip the static
_for_step declaration entirely. NEXT emits var++ instead of
var += _for_step. Reduces generated code by 1-2 lines per FOR loop.
Tracked via has_step flag in FOR stack.
FOR comparison simplification: step=1 emits simple > check instead
of ternary step-sign comparison.
69/69 compiler tests pass (play_scale is audio-timeout flaky).
Selective variable sync in emit_delegate_stmt: scan embedded token bytes
for variable name references, only sync variables that appear. Reduces
generated code by ~50% for PRINT USING and other delegated statements.
DRAW/PLAY use full sync (=variable; string substitution references vars
not visible in tokens). CHAIN uses full sync (passes COMMON vars).
Fast-path expression emitter: skip open_memstream buffering when no
MOD/IDIV/POW/string-comparison operators present (the common case).
Scan ahead for special operators; emit directly to output stream.
FOR loop simplification: when STEP is omitted (default 1), emit simple
> comparison instead of step-sign check. Integer FOR vars use gw_cint().
Division-by-zero check in fast path matches buffered path.
69/69 compiler tests, 72/72 interpreter, 14/14 kernel — all pass.
Fast-path expression emitter: skip open_memstream buffering for the
common case (no MOD/IDIV/POW/string-comparison). Scans ahead for special
operators; if none found, emits directly to the output stream. Reduces
compilation overhead for the majority of expressions.
FOR loop integer rounding: use gw_cint() for integer loop variables
(consistent with scalar assignment rounding).
Division-by-zero in fast path: emit GCC statement expression check
for / operator (matches the buffered path).
Dead code elimination: skip statements after GOTO/END/STOP.
Skip gwrt_check_line() for REM-only lines.
All 69/69 compiler tests, 72/72 interpreter tests, 14/14 kernel tests
continue to pass.
RNG: use gw_rnd() (GW-BASIC's LCG) instead of rand()/RAND_MAX.
RANDOMIZE: set gw_rnd_seed directly instead of srand(). Compiled and
interpreted programs now produce identical random sequences.
Dead code elimination: skip statements after unconditional GOTO/END/STOP
on the same line. Skip gwrt_check_line() for REM-only lines.
69 of 69 eligible compiler tests now pass (100%). Zero failures.
(3 tests without line numbers are skipped by the compiler.)
CHAIN: delegate to runtime interpreter via emit_delegate_stmt with full
variable sync. Runtime loads .bas file, preserves COMMON variables, and
runs it via gw_run_loop(). Compiled code exits after CHAIN returns.
COMMON: delegate to runtime to mark variables for CHAIN preservation.
RUN "file": delegate to runtime interpreter which loads and runs the
file. Compiled code exits after (RUN doesn't return to caller).
67/72 tests pass (93%). Only 2 remain: monte_carlo and number_guess
(RNG seed differences — not bugs).
misc_stmts: fix ERDEV/IOCTL$ PRINT detection (peek past spaces for $
suffix), add IOCTL$() handler in emit_str_atom that consumes (#filenum).
random_access: use emit_delegate_stmt with read_back=true for FIELD/GET/
PUT/LSET/RSET so FIELD variables are synced back after GET.
error_handler: add division-by-zero check in compiled / operator (GCC
statement expression checks divisor==0 → gw_error(11)). Second error now
caught correctly.
get_put: fix CLS to call gfx_cls()+gfx_flush() when graphics active,
producing the expected Sixel frame after SCREEN mode changes.
64/72 tests pass (89%). Only 5 remain: 3 structural (CHAIN/COMMON/RUN
"file" unsupported) + 2 RNG-dependent (different random seed).
Add emit_delegate_stmt() helper that embeds raw token bytes with full
variable sync (write before, read back after) and calls gw_exec_stmt().
Reusable pattern replaces ad-hoc token embedding in multiple handlers.
OPEN/CLOSE: delegate to runtime (handles all OPEN syntax variants).
PRINT #n: delegate with correct PRINT token position (was off-by-one).
WRITE #n: detect '#' and delegate (screen WRITE still inline).
INPUT/LINE INPUT: delegate with variable read-back.
LINE (INPUT or graphics): delegate.
LPRINT/LLIST: delegate.
50/72 tests pass. New: file_io, mbf_format, write_input.
CVI/CVS/CVD: handle 0xFD-prefix extended functions in emit_atom.
MKI$/MKS$/MKD$: handle in emit_str_atom. peek_expr_type detects FD
functions for correct PRINT type. Unlocks mkicvi.bas.
MID$ assignment: delegate to runtime via token embedding with variable
sync (both write and read-back for string variables).
ON ERROR GOTO / RESUME: set up gw_run_jmp in generated main() so
gw_error() can longjmp to the error handler. Create dummy
program_line_t so gw_find_line succeeds. Set gw.on_error_line when
ON ERROR GOTO is compiled. RESUME NEXT clears gw.in_error_handler
and dispatches to next line via line-number lookup.
ERR/ERL: emit gw_errno and gw.err_line_num for error pseudo-variables.
All-line labels: emit L_n labels for every program line (not just GOTO
targets) so RESUME NEXT can dispatch to any line.
49/72 tests pass (mkicvi new). error_handler and mid_assign partial.
String comparison: detect VT_STR left operand in relationals (>, <, =,
<=, >=, <>), re-emit as string atom, and use strcmp-based comparison
via GCC statement expression. Unlocks bubble_sort.
ON ERROR GOTO: add setjmp guard in generated main() that dispatches
to all GOTO target labels on error. Partial error_handler support.
Graphics/sound/file I/O extended statements: delegate to runtime via
token embedding with variable sync (same technique as PRINT USING and
DEF FN). Includes CIRCLE, DRAW, PAINT, PLAY, VIEW, WINDOW, PALETTE,
FIELD, LSET, RSET, PUT, GET. Fixed FE-prefix position tracking.
FRE("") GC: sync string variables to interpreter table before GC so
the collector can find live strings in compiled programs.
Division semantics: / always float (cast to double).
48/72 tests pass. New: bubble_sort, invoice. play_music/play_scale
restored after FE-prefix fix.
String concatenation: refactored emit_str_expr to use emit_str_atom +
concat loop. Self-referencing assignment A$ = A$ + B$ evaluates RHS
to temp before freeing old value.
Division semantics: GW-BASIC / always produces float (cast both operands
to double). Integer division is only for \ operator.
PRINT USING null-byte fix: token scanner now skips over float/double
constants (which may contain 0x00 bytes) instead of stopping on them.
Uses program_line_t.len for bounds instead of null termination.
FRE(): call strpool_gc() via comma expression for accurate reporting.
STICK(): return 128 (center). EOF/LOC/LOF function stubs.
46/72 tests pass. New: portio, print_using, print_using_edge,
monte_carlo (partial), string_gc, plus retained caesar_cipher,
roman_numerals.
String concatenation: refactor emit_str_expr into emit_str_atom + concat
loop. S$ = S$ + R$(I) now correctly accumulates via gw_str_concat.
String assignment order fix: evaluate RHS to temp before freeing old
value, so self-referencing A$ = A$ + B$ reads A$ before clearing it.
READ into array elements: subscript parsing with multi-dim support.
Array assignment: don't zero element before RHS evaluation (fixes
C(I,J) = C(I,J) + A(I,K)*B(K,J) self-referencing pattern).
PRINT USING colon-in-string: skip quoted strings when scanning for
statement-end colon.
43/72 tests pass. New: caesar_cipher, roman_numerals.
READ into array elements: parse subscripts after variable name in READ
handler, call gwrt_array_elem + gwrt_data_read. Unlocks matrix_mult,
roman_numerals (partial).
PRINT USING colon-in-string: scan past quoted strings when finding
statement-end colon for token embedding. Fixes truncated format strings
like "Pi estimate: #.####".
Array element assignment: don't zero element before RHS evaluation.
Previous code did `*_elem = {.type=4}` which zeroed fval before
reading C(I,J) in `C(I,J) = C(I,J) + A(I,K)*B(K,J)`, making
self-referencing assignments always read 0.
DEF FN call fix: skip past TOK_FN byte before calling gw_eval_fn_call.
DEFINT pre-scan: analysis pass processes DEFINT/DEFSNG/DEFDBL/DEFSTR
before variable type resolution.
Integer assignment rounding: use gw_cint() (rint) instead of (int16_t)
C truncation.
41/72 tests pass. 0 compile errors.
New: hundred_doors, matrix_mult, pascal_triangle, stats_calc.
PRINT USING: embed token bytes in generated C and call gw_print_using()
at runtime. Variable values synced to interpreter table before the call
so gw_eval() can resolve them. Unlocks fibonacci, temp_table, calendar.
STRING$ (single-byte token 0xD4): handle in emit_str_expr and PRINT
string detection. Emits gw_fn_strings() call.
INSTR (single-byte token 0xD6): handle in emit_atom with start position
detection. Emits gw_fn_instr() call.
PRINT USING token skip fix: skip past TOK_USING before embedding bytes
(gw_print_using expects text_ptr after USING, not at it).
27 of 72 tests now pass. New: fibonacci, calendar, temp_table.