Files
gw-basic-2026/tests/run_ffi_test.sh
Eremey Valetov 89fe0fb0b3 Compiler: $EXTERN pragma for calling C functions from BASIC (Level 2 FFI) (#1)
Add a '$EXTERN NAME(ARGTYPES) AS RET pragma so compiled BASIC can call C
functions directly, the natural follow-up to Level 1 (--emit-obj /
--main-name). The pragma is an apostrophe comment, so the interpreter
ignores it while the compiler registers it.

Map INTEGER/SINGLE/DOUBLE/STRING to int16_t/float/double/const char* at the
boundary: a string argument crosses as a temporary C copy that is freed
after the call, and a string return is copied into the pool. The call name
is matched case-insensitively but emitted as the C symbol with the case
written in the pragma. Names are recognized before parse_var() truncates
identifiers to two significant characters, so multi-character C function
names work.

A string return that aliases a char* argument is copied before the argument
temporaries are freed, which avoids a use-after-free. Over-supplied
arguments are consumed without desyncing the token stream and warn on arity
mismatch.

Docs: getting-started.md "Foreign Functions from BASIC". Test:
tests/run_ffi_test.sh, wired into CI. 63/63 compiler, 72/72 interpreter,
68/68 compat still pass.

Also refile the roadmap "Next Up" backlog as git-bug issues and prune
docs/roadmap.md to point at git-bug as the source of truth for planned work.

Co-authored-by: Eremey Valetov <evvaletov@users.noreply.github.com>
2026-06-13 15:06:23 +03:00

46 lines
1.5 KiB
Bash
Executable File

#!/bin/bash
# Exercise the Level 2 cross-language path: compile a BASIC program that calls
# C functions via '$EXTERN pragmas, link it against a companion C object, run
# it, and compare against expected output.
set -u
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
COMPILE="${PROJECT_DIR}/build/gwbasic-compile"
FFI_DIR="${SCRIPT_DIR}/ffi"
WORK_DIR=$(mktemp -d)
trap 'rm -rf "$WORK_DIR"' EXIT
if [ ! -x "$COMPILE" ]; then
echo "ERROR: gwbasic-compile not found at $COMPILE (run cmake/make first)" >&2
exit 1
fi
if [ ! -f "$PROJECT_DIR/build/libgwrt.a" ]; then
echo "ERROR: libgwrt.a not built yet (run cmake/make first)" >&2
exit 1
fi
cp "$FFI_DIR/extern_demo.bas" "$FFI_DIR/extern_lib.c" "$WORK_DIR/"
cd "$WORK_DIR" || exit 1
# BASIC -> object (entry point stays main; the C lib provides only helpers)
if ! "$COMPILE" extern_demo.bas --emit-obj --runtime "$PROJECT_DIR" >/dev/null 2>&1; then
echo "FAIL: gwbasic-compile --emit-obj failed" >&2
exit 1
fi
gcc -c extern_lib.c -o extern_lib.o || { echo "FAIL: C lib compile" >&2; exit 1; }
LINK="gcc extern_demo.o extern_lib.o -o extern_demo -L$PROJECT_DIR/build -lgwrt -lm -lpthread"
if ! $LINK -lpulse-simple 2>/dev/null; then
$LINK 2>/dev/null || { echo "FAIL: link" >&2; exit 1; }
fi
./extern_demo > got.txt 2>&1
if diff -u "$FFI_DIR/expected.txt" got.txt; then
echo "PASS ffi extern_demo"
exit 0
else
echo "FAIL ffi extern_demo (output mismatch above)" >&2
exit 1
fi