diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b46ac1d..4a027ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,33 +51,51 @@ jobs: tar -xJf /tmp/ow-snapshot.tar.xz -C ~/openwatcom-v2 rm -f /tmp/ow-snapshot.tar.xz + - name: Configure OpenWatcom env + run: | + # Generate the setvars.sh that build_dos.sh sources automatically. + # The cache step skips its creation step so this also runs on + # cache hits; -f overwrites without complaint. + cat > ~/openwatcom-v2/setvars.sh <<'EOF' + export WATCOM=$HOME/openwatcom-v2 + export PATH=$WATCOM/binl64:$WATCOM/binl:$PATH + export EDPATH=$WATCOM/eddat + export INCLUDE=$WATCOM/h + EOF + chmod +x ~/openwatcom-v2/setvars.sh + # Also export to subsequent steps directly so a missing + # setvars.sh on a future runner doesn't silently break the build. + { + echo "WATCOM=$HOME/openwatcom-v2" + echo "EDPATH=$HOME/openwatcom-v2/eddat" + echo "INCLUDE=$HOME/openwatcom-v2/h" + } >> "$GITHUB_ENV" + echo "$HOME/openwatcom-v2/binl64" >> "$GITHUB_PATH" + echo "$HOME/openwatcom-v2/binl" >> "$GITHUB_PATH" + - name: Build 16-bit DOS target - run: ./build_dos.sh 16 + run: | + ./build_dos.sh 16 + mkdir -p .ci-stash + cp gwbasic16.exe .ci-stash/ - name: Build 32-bit DOS target run: | ./build_dos.sh clean ./build_dos.sh 32 + cp gwbasic.exe .ci-stash/ - name: Verify binary sizes look sane run: | - test -f gwbasic.exe - # 32-bit LE executable; should be a few hundred KB - size=$(stat -c%s gwbasic.exe) - echo "gwbasic.exe = $size bytes" - [ "$size" -gt 100000 ] && [ "$size" -lt 500000 ] - # Rebuild 16-bit too for the artifact upload - ./build_dos.sh clean - ./build_dos.sh 16 - test -f gwbasic16.exe - size16=$(stat -c%s gwbasic16.exe) + size16=$(stat -c%s .ci-stash/gwbasic16.exe) + size32=$(stat -c%s .ci-stash/gwbasic.exe) echo "gwbasic16.exe = $size16 bytes" - [ "$size16" -gt 80000 ] && [ "$size16" -lt 200000 ] + echo "gwbasic.exe = $size32 bytes" + [ "$size16" -gt 80000 ] && [ "$size16" -lt 200000 ] + [ "$size32" -gt 100000 ] && [ "$size32" -lt 500000 ] - name: Upload DOS artifacts uses: actions/upload-artifact@v4 with: name: dos-binaries - path: | - gwbasic16.exe - gwbasic.exe + path: .ci-stash/*.exe diff --git a/docs/getting-started.md b/docs/getting-started.md index c3d2467..5829e14 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -102,6 +102,12 @@ build/gwbasic-compile program.bas build/gwbasic-compile -c --runtime . program.bas ``` +Both numbered (`10 PRINT "HI"`) and unnumbered (`PRINT "HI"`) sources +compile. Unnumbered lines get auto-assigned numbers (10, 20, 30, ...) so +the analysis pass and codegen can produce labeled statements; explicit +line numbers are preserved. Direct-mode scratchpad scripts and classic +"just a list of statements" programs compile without manual renumbering. + ### Compiler Options ``` diff --git a/src/codegen.c b/src/codegen.c index e6fd455..4809e7e 100644 --- a/src/codegen.c +++ b/src/codegen.c @@ -486,19 +486,19 @@ static void emit_atom(void) if (xfunc == XFUNC_CVI) { EMIT("gw_fn_cvi(&(gw_value_t){.type=VT_STR,.sval="); if (cur() == '(') advance(); - emit_str_atom(); + emit_str_expr(); if (cur() == ')') advance(); EMIT("}).ival"); } else if (xfunc == XFUNC_CVS) { EMIT("gw_fn_cvs(&(gw_value_t){.type=VT_STR,.sval="); if (cur() == '(') advance(); - emit_str_atom(); + emit_str_expr(); if (cur() == ')') advance(); EMIT("}).fval"); } else if (xfunc == XFUNC_CVD) { EMIT("gw_fn_cvd(&(gw_value_t){.type=VT_STR,.sval="); if (cur() == '(') advance(); - emit_str_atom(); + emit_str_expr(); if (cur() == ')') advance(); EMIT("}).dval"); } else { @@ -823,20 +823,21 @@ static void emit_num_prec(int min_prec) /* For string comparisons, emit strcmp-based code */ bool is_relational = cop || op == TOK_GT || op == TOK_LT || op == TOK_EQ; if (is_relational && left_type == VT_STR) { - /* Re-emit left as string (it was emitted as numeric atom) */ + /* Re-emit left as a full string expression (was emitted as numeric + * atom); emit_str_expr stops at the comparison op since it only + * consumes TOK_PLUS. */ uint8_t *save_tp = tp; tp = left_start; char *left_str; { FILE *orig = out; char *buf = NULL; size_t sz = 0; out = open_memstream(&buf, &sz); - emit_str_atom(); + emit_str_expr(); fclose(out); out = orig; left_str = buf; } tp = save_tp; - /* Buffer right as string expression */ char *right_str; { FILE *orig = out; char *buf = NULL; size_t sz = 0; out = open_memstream(&buf, &sz); - emit_str_atom(); + emit_str_expr(); fclose(out); out = orig; right_str = buf; } const char *cmpop = cop ? cop : binop_c(op); char *combined = NULL; size_t csz = 0; diff --git a/tests/run_freedos_qemu.sh b/tests/run_freedos_qemu.sh new file mode 100755 index 0000000..906ed72 --- /dev/null +++ b/tests/run_freedos_qemu.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# Launch a FreeDOS 1.4 QEMU VM with gwbasic16.exe and the smoke test on a +# FAT disk image, for manual verification of the BIOS-rendered TUI. +# +# Usage: bash tests/run_freedos_qemu.sh +# +# The script is *not* a self-contained automated smoke test — DOS doesn't +# pipe console input cleanly enough through QEMU's -serial stdio for that +# to be reliable, and modern qemu-kvm builds don't expose -fda on the +# default machine type. Use tests/run_dos_smoke.sh (DOSBox-X) for the +# automated smoke; this script is for going through the manual TUI +# checklist in docs/getting-started.md against bare FreeDOS. +# +# Once FreeDOS boots, press a key to skip the boot menu (or wait for the +# default), then at the C:\> prompt: +# +# C:\> D: +# D:\> DIR (sanity-check the FAT image is mounted) +# D:\> GWBASIC.EXE SMOKE.BAS (runs the non-interactive smoke) +# D:\> TYPE OUT.TXT (compare against tests/expected/dos_smoke.expected) +# D:\> GWBASIC.EXE (no args -- exercises the TUI editor) +# +# Quit QEMU: Ctrl-Alt-G then close the window, or in -nographic mode use +# the QEMU monitor (Ctrl-A then X). +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +EXE="$PROJECT_DIR/gwbasic16.exe" +SMOKE="$SCRIPT_DIR/dos_smoke.bas" +FREEDOS_IMG="${FREEDOS_IMG:-$HOME/DOS/images/freedos.qcow2}" +QEMU="${QEMU:-/usr/libexec/qemu-kvm}" +DATA_IMG="$HOME/.cache/gw-basic-2026/freedos_d.img" + +if [ ! -x "$EXE" ]; then + echo "ERROR: $EXE not found. Run ./build_dos.sh 16 first." >&2 + exit 1 +fi +if [ ! -f "$FREEDOS_IMG" ]; then + echo "ERROR: FreeDOS qcow2 not found at $FREEDOS_IMG." >&2 + echo "Override with FREEDOS_IMG=/path/to/freedos.qcow2" >&2 + exit 1 +fi +if [ ! -x "$QEMU" ]; then + echo "ERROR: qemu-kvm not found at $QEMU. Override with QEMU=/path/to/qemu" >&2 + exit 1 +fi + +echo "==> Building FAT data image at $DATA_IMG" +mkdir -p "$(dirname "$DATA_IMG")" +dd if=/dev/zero of="$DATA_IMG" bs=1M count=8 status=none +mkfs.vfat "$DATA_IMG" >/dev/null +mcopy -i "$DATA_IMG" "$EXE" ::GWBASIC.EXE +mcopy -i "$DATA_IMG" "$SMOKE" ::SMOKE.BAS +sed 's/$/\r/' "$SMOKE" > /tmp/_smoke_crlf.bas +mcopy -i "$DATA_IMG" -o /tmp/_smoke_crlf.bas ::SMOKE.BAS +rm -f /tmp/_smoke_crlf.bas +mdir -i "$DATA_IMG" + +echo +echo "==> Launching FreeDOS QEMU. See header of $0 for the manual test sequence." +echo "==> Quit: Ctrl-Alt-G, close window. Press Enter to continue." +read -r + +exec "$QEMU" \ + -machine pc \ + -cpu max \ + -m 32 \ + -hda "$FREEDOS_IMG" \ + -hdb "$DATA_IMG" \ + -nic none \ + -boot c \ + -display gtk \ + -rtc base=localtime