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>
83 lines
2.3 KiB
C
83 lines
2.3 KiB
C
#ifndef ANALYSIS_H
|
|
#define ANALYSIS_H
|
|
|
|
#include "types.h"
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#define MAX_LINES 8192
|
|
#define MAX_VARS 1024
|
|
#define MAX_GOTOS 1024
|
|
#define MAX_DATA 4096
|
|
#define MAX_GOSUB_RET 1024
|
|
|
|
#define MAX_EXTERNS 64
|
|
#define MAX_EXTERN_ARGS 8
|
|
#define EXTERN_NAME_MAX 32
|
|
|
|
typedef struct {
|
|
uint16_t line_num;
|
|
bool is_target; /* referenced by GOTO/GOSUB/etc. */
|
|
bool has_data; /* contains DATA statement */
|
|
int data_start; /* index into data pool */
|
|
} line_info_t;
|
|
|
|
/* A C function declared via the '$EXTERN NAME(ARGS) AS RET pragma.
|
|
* name is stored case-preserving (emitted as the C symbol); matching
|
|
* against BASIC call sites is case-insensitive. */
|
|
typedef struct {
|
|
char name[EXTERN_NAME_MAX];
|
|
gw_valtype_t ret_type;
|
|
gw_valtype_t arg_types[MAX_EXTERN_ARGS];
|
|
int argc;
|
|
} extern_func_t;
|
|
|
|
typedef struct {
|
|
char name[2];
|
|
gw_valtype_t type;
|
|
uint16_t first_assign_line; /* 0 = never assigned */
|
|
uint16_t first_use_line; /* 0 = never used */
|
|
} var_info_t;
|
|
|
|
typedef struct {
|
|
line_info_t lines[MAX_LINES];
|
|
int line_count;
|
|
|
|
var_info_t vars[MAX_VARS];
|
|
int var_count;
|
|
|
|
uint16_t goto_targets[MAX_GOTOS];
|
|
int goto_count;
|
|
|
|
char *data_pool[MAX_DATA]; /* collected DATA literals */
|
|
int data_count;
|
|
|
|
int data_line_map[MAX_LINES][2]; /* [line_num, data_start_index] */
|
|
int data_line_count;
|
|
|
|
gw_valtype_t def_type[26]; /* from DEFINT/DEFSNG/DEFDBL/DEFSTR */
|
|
|
|
extern_func_t externs[MAX_EXTERNS]; /* '$EXTERN FFI declarations */
|
|
int extern_count;
|
|
} analysis_t;
|
|
|
|
/* Run analysis pass over the loaded program */
|
|
void analysis_run(analysis_t *a);
|
|
|
|
/* Find a variable in the census, return index or -1 */
|
|
int analysis_find_var(analysis_t *a, const char name[2], gw_valtype_t type);
|
|
|
|
/* Add a variable to the census if not already present */
|
|
int analysis_add_var(analysis_t *a, const char name[2], gw_valtype_t type);
|
|
|
|
/* Check if a line number is a jump target */
|
|
bool analysis_is_target(analysis_t *a, uint16_t line_num);
|
|
|
|
/* Find a declared extern function by name (case-insensitive), or NULL */
|
|
const extern_func_t *analysis_find_extern(analysis_t *a, const char *name);
|
|
|
|
/* Emit static analysis warnings to stderr */
|
|
void analysis_warnings(analysis_t *a);
|
|
|
|
#endif
|