Phase 1: expression calculator with direct mode

GW-BASIC reimplementation in C11, using Microsoft's open-sourced 8088
assembly as the authoritative reference.

Tokenizer (CRUNCH/LIST), expression evaluator with operator precedence,
all math functions (SIN, COS, TAN, ATN, SQR, LOG, EXP, RND, etc.),
string functions (LEFT$, RIGHT$, MID$, CHR$, ASC, VAL, STR$, etc.),
PRINT statement with comma/semicolon zones, TAB(), SPC().

Handles integer/single/double types with correct promotion, D exponent
for double-precision literals, type suffixes (%, !, #), &H hex/&O octal
literals, MBF conversion routines, and GW-BASIC-compatible number
formatting.

Platform-independent via HAL vtable; POSIX backend included.
This commit is contained in:
Eremey Valetov
2026-02-10 10:25:08 -05:00
commit d8e8375366
26 changed files with 3396 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
#ifndef GW_ERROR_H
#define GW_ERROR_H
#include <setjmp.h>
/* Error codes matching GW-BASIC's ERRTAB */
#define ERR_NF 1 /* NEXT without FOR */
#define ERR_SN 2 /* Syntax error */
#define ERR_RG 3 /* RETURN without GOSUB */
#define ERR_OD 4 /* Out of DATA */
#define ERR_FC 5 /* Illegal function call */
#define ERR_OV 6 /* Overflow */
#define ERR_OM 7 /* Out of memory */
#define ERR_UL 8 /* Undefined line number */
#define ERR_BS 9 /* Subscript out of range */
#define ERR_DD 10 /* Duplicate Definition */
#define ERR_DZ 11 /* Division by zero */
#define ERR_ID 12 /* Illegal direct */
#define ERR_TM 13 /* Type mismatch */
#define ERR_OS 14 /* Out of string space */
#define ERR_LS 15 /* String too long */
#define ERR_ST 16 /* String formula too complex */
#define ERR_CN 17 /* Can't continue */
#define ERR_UF 18 /* Undefined user function */
#define ERR_NR 19 /* No RESUME */
#define ERR_RW 20 /* RESUME without error */
#define ERR_UE 21 /* Unprintable error */
#define ERR_MO 22 /* Missing operand */
#define ERR_BO 23 /* Line buffer overflow */
#define ERR_DT 24 /* Device Timeout */
#define ERR_DF 25 /* Device Fault */
#define ERR_FW 26 /* FOR without NEXT */
#define ERR_OP 27 /* Out of Paper */
#define ERR_WH 29 /* WHILE without WEND */
#define ERR_WE 30 /* WEND without WHILE */
/* Disk errors (starting at 50) */
#define ERR_FO 50 /* FIELD overflow */
#define ERR_IE 51 /* Internal error */
#define ERR_BN 52 /* Bad file number */
#define ERR_FF 53 /* File not found */
#define ERR_BM 54 /* Bad file mode */
#define ERR_AO 56 /* File already open */
#define ERR_IO 58 /* Device I/O Error */
#define ERR_FE 60 /* File already exists */
#define ERR_FL 62 /* Disk full */
#define ERR_EF 63 /* Input past end */
#define ERR_RN 64 /* Bad record number */
#define ERR_NM 65 /* Bad file name */
#define ERR_DS 67 /* Direct statement in file */
#define ERR_TF 68 /* Too many files */
const char *gw_error_msg(int errnum);
void gw_error(int errnum);
/* Error recovery jump target - set in main loop */
extern jmp_buf gw_error_jmp;
extern int gw_errno;
#endif
+21
View File
@@ -0,0 +1,21 @@
#ifndef GW_EVAL_H
#define GW_EVAL_H
#include "types.h"
/* Expression evaluator - recursive descent with precedence climbing */
gw_value_t gw_eval(void); /* evaluate any expression */
gw_value_t gw_eval_num(void); /* evaluate, require numeric */
gw_value_t gw_eval_str(void); /* evaluate, require string */
int16_t gw_eval_int(void); /* evaluate, require integer result */
uint16_t gw_eval_uint16(void);
/* Force type conversions */
int16_t gw_to_int(gw_value_t *v);
float gw_to_sng(gw_value_t *v);
double gw_to_dbl(gw_value_t *v);
/* Check for closing paren */
void gw_expect_rparen(void);
#endif
+54
View File
@@ -0,0 +1,54 @@
#ifndef GW_MATH_H
#define GW_MATH_H
#include "types.h"
/* Integer arithmetic with overflow detection */
int16_t gw_int_add(int16_t a, int16_t b);
int16_t gw_int_sub(int16_t a, int16_t b);
int16_t gw_int_mul(int16_t a, int16_t b);
int16_t gw_int_div(int16_t a, int16_t b);
int16_t gw_int_mod(int16_t a, int16_t b);
int16_t gw_int_idiv(int16_t a, int16_t b);
int16_t gw_int_neg(int16_t a);
/* Float operations */
double gw_fadd(double a, double b);
double gw_fsub(double a, double b);
double gw_fmul(double a, double b);
double gw_fdiv(double a, double b);
double gw_fpow(double base, double exp);
double gw_fneg(double a);
/* Type promotion for binary ops */
void gw_promote(gw_value_t *a, gw_value_t *b);
/* Transcendental functions */
double gw_sin(double x);
double gw_cos(double x);
double gw_tan(double x);
double gw_atn(double x);
double gw_sqr(double x);
double gw_log(double x);
double gw_exp(double x);
double gw_abs(double x);
double gw_sgn(double x);
double gw_int_fn(double x);
double gw_fix(double x);
double gw_rnd(double x);
/* Conversion functions */
int16_t gw_cint(double x);
float gw_csng(double x);
double gw_cdbl(gw_value_t *v);
/* MBF conversion (for file I/O compatibility) */
float gw_mbf_to_ieee_single(mbf_single_t mbf);
double gw_mbf_to_ieee_double(mbf_double_t mbf);
mbf_single_t gw_ieee_to_mbf_single(float f);
mbf_double_t gw_ieee_to_mbf_double(double d);
/* Number formatting for PRINT */
void gw_format_number(gw_value_t *v, char *buf, int bufsize);
#endif
+25
View File
@@ -0,0 +1,25 @@
#ifndef GWBASIC_H
#define GWBASIC_H
#include "types.h"
#include "tokens.h"
#include "error.h"
#include "hal.h"
#include "interp.h"
#include "eval.h"
#include "gw_math.h"
#include "strings.h"
#define GW_VERSION "0.1.0"
#define GW_BANNER "GW-BASIC " GW_VERSION
/* Tokenizer */
int gw_crunch(const char *text, uint8_t *out, int outsize);
void gw_list_line(uint8_t *tokens, int len, char *out, int outsize);
/* PRINT statement */
void gw_stmt_print(void);
void gw_print_value(gw_value_t *v);
void gw_print_newline(void);
#endif
+34
View File
@@ -0,0 +1,34 @@
#ifndef GW_HAL_H
#define GW_HAL_H
#include <stdint.h>
#include <stdbool.h>
/* Hardware Abstraction Layer vtable */
typedef struct hal_ops {
/* Terminal I/O */
void (*putch)(int ch);
void (*puts)(const char *s);
int (*getch)(void); /* blocking read */
bool (*kbhit)(void); /* key available? */
void (*locate)(int row, int col);
int (*get_cursor_row)(void);
int (*get_cursor_col)(void);
void (*cls)(void);
void (*set_width)(int cols);
/* Terminal properties */
int screen_width;
int screen_height;
/* Lifecycle */
void (*init)(void);
void (*shutdown)(void);
} hal_ops_t;
extern hal_ops_t *gw_hal;
/* Platform implementations */
hal_ops_t *hal_posix_create(void);
#endif
+51
View File
@@ -0,0 +1,51 @@
#ifndef GW_INTERP_H
#define GW_INTERP_H
#include "types.h"
#include <stdbool.h>
/* Interpreter state - the global context */
typedef struct {
/* Text pointer (TXTPTR equivalent) */
uint8_t *text_ptr;
/* Current accumulator (FAC equivalent) */
gw_value_t fac;
/* Program state */
uint16_t cur_line_num; /* CURLIN - 65535 = direct mode */
program_line_t *prog_head; /* first line of program */
bool trace_on; /* TRON/TROFF */
/* Tokenizer buffers */
uint8_t kbuf[300]; /* crunch buffer */
char buf[256]; /* input line buffer */
/* Default variable types for A-Z (DEFTBL) */
gw_valtype_t def_type[26];
/* DATA pointer */
uint8_t *data_ptr;
uint16_t data_line;
} interp_state_t;
extern interp_state_t gw;
/* Direct mode line number sentinel */
#define LINE_DIRECT 65535
void gw_init(void);
void gw_exec_line(uint8_t *tokens);
void gw_exec_direct(const char *line);
/* CHRGET/CHRGOT - advance/peek the text pointer */
uint8_t gw_chrget(void);
uint8_t gw_chrgot(void);
void gw_skip_spaces(void);
bool gw_is_letter(uint8_t ch);
bool gw_is_digit(uint8_t ch);
/* Expect a specific token, else syntax error */
void gw_expect(uint8_t tok);
#endif
+32
View File
@@ -0,0 +1,32 @@
#ifndef GW_STRINGS_H
#define GW_STRINGS_H
#include "types.h"
#include <stdint.h>
/* String creation/management */
gw_string_t gw_str_alloc(int len);
gw_string_t gw_str_from_cstr(const char *s);
gw_string_t gw_str_copy(gw_string_t *s);
void gw_str_free(gw_string_t *s);
char *gw_str_to_cstr(gw_string_t *s); /* caller must free */
/* String functions */
gw_value_t gw_fn_len(gw_value_t *s);
gw_value_t gw_fn_left(gw_value_t *s, int n);
gw_value_t gw_fn_right(gw_value_t *s, int n);
gw_value_t gw_fn_mid(gw_value_t *s, int start, int len);
gw_value_t gw_fn_chr(int code);
gw_value_t gw_fn_asc(gw_value_t *s);
gw_value_t gw_fn_val(gw_value_t *s);
gw_value_t gw_fn_str(gw_value_t *v);
gw_value_t gw_fn_space(int n);
gw_value_t gw_fn_strings(int n, int code);
gw_value_t gw_fn_instr(int start, gw_value_t *haystack, gw_value_t *needle);
gw_value_t gw_fn_hex(int16_t n);
gw_value_t gw_fn_oct(int16_t n);
/* Concatenation */
gw_value_t gw_str_concat(gw_value_t *a, gw_value_t *b);
#endif
+231
View File
@@ -0,0 +1,231 @@
#ifndef GW_TOKENS_H
#define GW_TOKENS_H
#include <stdint.h>
/*
* Token values derived from IBMRES.ASM.
* Single-byte tokens: 0x80-0xF9
* Two-byte tokens: 0xFD xx (extended functions), 0xFE xx (extended stmts),
* 0xFF xx (built-in functions)
*/
/* Statement tokens (STMDSP) - starting at 128 */
#define TOK_END 0x80 /* 128 */
#define TOK_FOR 0x81 /* 129 */
#define TOK_NEXT 0x82 /* 130 */
#define TOK_DATA 0x83 /* 131 */
#define TOK_INPUT 0x84 /* 132 */
#define TOK_DIM 0x85 /* 133 */
#define TOK_READ 0x86 /* 134 */
#define TOK_LET 0x87 /* 135 */
#define TOK_GOTO 0x88 /* 136 */
#define TOK_RUN 0x89 /* 137 */
#define TOK_IF 0x8A /* 138 */
#define TOK_RESTORE 0x8B /* 139 */
#define TOK_GOSUB 0x8C /* 140 */
#define TOK_RETURN 0x8D /* 141 */
#define TOK_REM 0x8E /* 142 */
#define TOK_STOP 0x8F /* 143 */
#define TOK_PRINT 0x90 /* 144 */
#define TOK_CLEAR 0x91 /* 145 */
#define TOK_LIST 0x92 /* 146 */
#define TOK_NEW 0x93 /* 147 */
#define TOK_ON 0x94 /* 148 */
#define TOK_WAIT 0x95 /* 149 */
#define TOK_DEF 0x96 /* 150 */
#define TOK_POKE 0x97 /* 151 */
#define TOK_CONT 0x98 /* 152 */
/* 153-154: padding (SNERR) */
#define TOK_OUT 0x9B /* 155 */
#define TOK_LPRINT 0x9C /* 156 */
#define TOK_LLIST 0x9D /* 157 */
/* 158: padding */
#define TOK_WIDTH 0x9F /* 159 */
#define TOK_ELSE 0xA0 /* 160 */
#define TOK_TRON 0xA1 /* 161 */
#define TOK_TROFF 0xA2 /* 162 */
#define TOK_SWAP 0xA3 /* 163 */
#define TOK_ERASE 0xA4 /* 164 */
#define TOK_EDIT 0xA5 /* 165 */
#define TOK_ERROR 0xA6 /* 166 */
#define TOK_RESUME 0xA7 /* 167 */
#define TOK_DELETE 0xA8 /* 168 */
#define TOK_AUTO 0xA9 /* 169 */
#define TOK_RENUM 0xAA /* 170 */
#define TOK_DEFSTR 0xAB /* 171 */
#define TOK_DEFINT 0xAC /* 172 */
#define TOK_DEFSNG 0xAD /* 173 */
#define TOK_DEFDBL 0xAE /* 174 */
#define TOK_LINE 0xAF /* 175 */
#define TOK_WHILE 0xB0 /* 176 */
#define TOK_WEND 0xB1 /* 177 */
#define TOK_CALL 0xB2 /* 178 */
/* 179-181: padding */
#define TOK_WRITE 0xB6 /* 182 */
#define TOK_OPTION 0xB7 /* 183 */
#define TOK_RANDOMIZE 0xB8 /* 184 */
#define TOK_OPEN 0xB9 /* 185 */
#define TOK_CLOSE 0xBA /* 186 */
#define TOK_LOAD 0xBB /* 187 */
#define TOK_MERGE 0xBC /* 188 */
#define TOK_SAVE 0xBD /* 189 */
#define TOK_COLOR 0xBE /* 190 */
#define TOK_CLS 0xBF /* 191 */
#define TOK_MOTOR 0xC0 /* 192 */
#define TOK_BSAVE 0xC1 /* 193 */
#define TOK_BLOAD 0xC2 /* 194 */
#define TOK_SOUND 0xC3 /* 195 */
#define TOK_BEEP 0xC4 /* 196 */
#define TOK_PSET 0xC5 /* 197 */
#define TOK_PRESET 0xC6 /* 198 */
#define TOK_SCREEN 0xC7 /* 199 */
#define TOK_KEY 0xC8 /* 200 */
#define TOK_LOCATE 0xC9 /* 201 */
/* Non-statement keyword tokens */
#define TOK_TO 0xCA /* 202 */
#define TOK_THEN 0xCB /* 203 */
#define TOK_TAB 0xCC /* 204 */
#define TOK_STEP 0xCD /* 205 */
#define TOK_USR 0xCE /* 206 */
#define TOK_FN 0xCF /* 207 */
#define TOK_SPC 0xD0 /* 208 */
#define TOK_NOT 0xD1 /* 209 */
#define TOK_ERL 0xD2 /* 210 */
#define TOK_ERR 0xD3 /* 211 */
#define TOK_STRINGS 0xD4 /* 212 - STRING$ */
#define TOK_USING 0xD5 /* 213 */
#define TOK_INSTR 0xD6 /* 214 */
#define TOK_SQUOTE 0xD7 /* 215 - ' (single quote = REM) */
#define TOK_VARPTR 0xD8 /* 216 */
#define TOK_CSRLIN 0xD9 /* 217 */
#define TOK_POINT 0xDA /* 218 */
#define TOK_OFF 0xDB /* 219 */
#define TOK_INKEYS 0xDC /* 220 - INKEY$ */
/* Operator tokens (after 7-byte gap at 221-227) */
#define TOK_GT 0xE4 /* 228 - > */
#define TOK_EQ 0xE5 /* 229 - = */
#define TOK_LT 0xE6 /* 230 - < */
#define TOK_PLUS 0xE7 /* 231 - + */
#define TOK_MINUS 0xE8 /* 232 - - */
#define TOK_MUL 0xE9 /* 233 - * */
#define TOK_DIV 0xEA /* 234 - / */
#define TOK_POW 0xEB /* 235 - ^ */
#define TOK_AND 0xEC /* 236 */
#define TOK_OR 0xED /* 237 */
#define TOK_XOR 0xEE /* 238 */
#define TOK_EQV 0xEF /* 239 */
#define TOK_IMP 0xF0 /* 240 */
#define TOK_MOD 0xF1 /* 241 */
#define TOK_IDIV 0xF2 /* 242 - \ (integer division) */
/* Multi-byte token prefixes */
#define TOK_PREFIX_FD 0xFD /* extended functions */
#define TOK_PREFIX_FE 0xFE /* extended statements */
#define TOK_PREFIX_FF 0xFF /* built-in functions */
/* Built-in function tokens (prefix 0xFF) */
#define FUNC_LEFT 0x80 /* 128 */
#define FUNC_RIGHT 0x81 /* 129 */
#define FUNC_MID 0x82 /* 130 */
#define FUNC_SGN 0x83 /* 131 */
#define FUNC_INT 0x84 /* 132 */
#define FUNC_ABS 0x85 /* 133 */
#define FUNC_SQR 0x86 /* 134 */
#define FUNC_RND 0x87 /* 135 */
#define FUNC_SIN 0x88 /* 136 */
#define FUNC_LOG 0x89 /* 137 */
#define FUNC_EXP 0x8A /* 138 */
#define FUNC_COS 0x8B /* 139 */
#define FUNC_TAN 0x8C /* 140 */
#define FUNC_ATN 0x8D /* 141 */
#define FUNC_FRE 0x8E /* 142 */
#define FUNC_INP 0x8F /* 143 */
#define FUNC_POS 0x90 /* 144 */
#define FUNC_LEN 0x91 /* 145 */
#define FUNC_STR 0x92 /* 146 */
#define FUNC_VAL 0x93 /* 147 */
#define FUNC_ASC 0x94 /* 148 */
#define FUNC_CHR 0x95 /* 149 */
#define FUNC_PEEK 0x96 /* 150 */
#define FUNC_SPACE 0x97 /* 151 */
#define FUNC_OCT 0x98 /* 152 */
#define FUNC_HEX 0x99 /* 153 */
#define FUNC_LPOS 0x9A /* 154 */
#define FUNC_CINT 0x9B /* 155 */
#define FUNC_CSNG 0x9C /* 156 */
#define FUNC_CDBL 0x9D /* 157 */
#define FUNC_FIX 0x9E /* 158 */
#define FUNC_PEN 0x9F /* 159 */
#define FUNC_STICK 0xA0 /* 160 */
#define FUNC_STRIG 0xA1 /* 161 */
#define FUNC_EOF 0xA2 /* 162 */
#define FUNC_LOC 0xA3 /* 163 */
#define FUNC_LOF 0xA4 /* 164 */
/* Extended statement tokens (prefix 0xFE) */
#define XSTMT_FILES 0x80
#define XSTMT_FIELD 0x81
#define XSTMT_SYSTEM 0x82
#define XSTMT_NAME 0x83
#define XSTMT_LSET 0x84
#define XSTMT_RSET 0x85
#define XSTMT_KILL 0x86
#define XSTMT_PUT 0x87
#define XSTMT_GET 0x88
#define XSTMT_RESET 0x89
#define XSTMT_COMMON 0x8A
#define XSTMT_CHAIN 0x8B
#define XSTMT_DATE 0x8C
#define XSTMT_TIME 0x8D
#define XSTMT_PAINT 0x8E
#define XSTMT_COM 0x8F
#define XSTMT_CIRCLE 0x90
#define XSTMT_DRAW 0x91
#define XSTMT_PLAY 0x92
#define XSTMT_TIMER 0x93
#define XSTMT_ERDEV 0x94
#define XSTMT_IOCTL 0x95
#define XSTMT_CHDIR 0x96
#define XSTMT_MKDIR 0x97
#define XSTMT_RMDIR 0x98
#define XSTMT_SHELL 0x99
#define XSTMT_ENVIRON 0x9A
#define XSTMT_VIEW 0x9B
#define XSTMT_WINDOW 0x9C
#define XSTMT_PMAP 0x9D
#define XSTMT_PALETTE 0x9E
#define XSTMT_LCOPY 0x9F
#define XSTMT_CALLS 0xA0
/* Extended function tokens (prefix 0xFD) */
#define XFUNC_CVI 0x80
#define XFUNC_CVS 0x81
#define XFUNC_CVD 0x82
#define XFUNC_MKI 0x83
#define XFUNC_MKS 0x84
#define XFUNC_MKD 0x85
/* Special token constants - embedded constant prefixes */
#define TOK_COLON ':' /* statement separator (not tokenized) */
#define TOK_INT2 0x0E /* 2-byte integer (int16_t follows) */
#define TOK_INT1 0x0F /* 1-byte integer 0-255 (uint8_t follows) */
/* 0x11-0x1A: literal integers 0-9 (no data bytes) */
#define TOK_CONST_SNG 0x1C /* single-precision (4 bytes IEEE follow) */
#define TOK_CONST_DBL 0x1F /* double-precision (8 bytes IEEE follow) */
/* Keyword table entry */
typedef struct {
const char *name;
uint8_t token; /* token value (or second byte for multi-byte) */
uint8_t prefix; /* 0 for single-byte, 0xFD/0xFE/0xFF for multi */
} keyword_entry_t;
extern const keyword_entry_t gw_keywords[];
extern int gw_keyword_count;
const char *gw_token_name(uint8_t prefix, uint8_t token);
#endif
+58
View File
@@ -0,0 +1,58 @@
#ifndef GW_TYPES_H
#define GW_TYPES_H
#include <stdint.h>
#include <stdbool.h>
/* Value types matching GW-BASIC's VALTYP */
typedef enum {
VT_INT = 2, /* 16-bit integer (%) */
VT_SNG = 4, /* single precision (!) */
VT_DBL = 8, /* double precision (#) */
VT_STR = 3, /* string ($) */
} gw_valtype_t;
/* String descriptor - matches original's 3-byte layout concept */
typedef struct {
uint8_t len;
char *data; /* malloc'd, NOT null-terminated */
} gw_string_t;
/* Unified value - the FAC (floating accumulator) equivalent */
typedef struct {
gw_valtype_t type;
union {
int16_t ival;
float fval;
double dval;
gw_string_t sval;
};
} gw_value_t;
/* Program line stored in memory */
typedef struct program_line {
struct program_line *next;
uint16_t num; /* line number 0-65529 */
uint16_t len; /* token data length */
uint8_t *tokens; /* tokenized line data */
} program_line_t;
/* Variable entry */
typedef struct {
char name[2]; /* 2-char name (original GW-BASIC limit) */
gw_valtype_t type;
gw_value_t val;
} var_entry_t;
/* MBF (Microsoft Binary Format) types for file compatibility */
typedef struct {
uint8_t mantissa[3];
uint8_t exponent;
} mbf_single_t;
typedef struct {
uint8_t mantissa[7];
uint8_t exponent;
} mbf_double_t;
#endif