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>
46 lines
1.5 KiB
Bash
Executable File
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
|