Files
gw-basic-2026/src/eval.c
T
Eremey Valetov 616259537a Phase 2: variables, arrays, program execution, and control flow
Add variable storage (scalar and array), program line storage with
RUN/LIST/NEW, and full control flow: FOR/NEXT, GOTO, GOSUB/RETURN,
IF/THEN/ELSE, WHILE/WEND, ON GOTO/GOSUB, DATA/READ/RESTORE.

New modules: interp.c (1445 lines - execution loop and statement
dispatcher), vars.c, arrays.c, input.c. Version bumped to 0.2.0.
16 test programs pass including Leibniz pi and prime sieve.
2026-02-10 11:16:58 -05:00

930 lines
24 KiB
C

#include "gwbasic.h"
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
/*
* Expression evaluator - reimplements GWEVAL.ASM's FRMEVL.
* Uses recursive descent with precedence climbing.
*
* Operator precedence (from GWDATA.ASM):
* ^ 127
* * / 124
* \ 123
* MOD 122
* + - 121
* relational 64
* NOT 90
* AND 70
* OR 60
* XOR 50
* EQV 40
* IMP 40
*/
/* Forward declarations */
static gw_value_t eval_expr(int min_prec);
static gw_value_t eval_unary(void);
static gw_value_t eval_atom(void);
static gw_value_t eval_function(uint8_t prefix, uint8_t func_tok);
static gw_value_t eval_paren(void);
static gw_value_t eval_number(void);
static gw_value_t eval_string_literal(void);
/* Operator precedence lookup */
static int op_prec(uint8_t tok)
{
switch (tok) {
case TOK_IMP: return 40;
case TOK_EQV: return 40;
case TOK_XOR: return 50;
case TOK_OR: return 60;
case TOK_AND: return 70;
case TOK_GT:
case TOK_EQ:
case TOK_LT: return 64;
case TOK_PLUS:
case TOK_MINUS: return 121;
case TOK_MOD: return 122;
case TOK_IDIV: return 123;
case TOK_MUL:
case TOK_DIV: return 124;
case TOK_POW: return 127;
default: return -1;
}
}
/* Apply a binary operator */
static gw_value_t apply_binop(uint8_t op, gw_value_t left, gw_value_t right)
{
gw_value_t result;
/* String concatenation */
if (op == TOK_PLUS && left.type == VT_STR && right.type == VT_STR)
return gw_str_concat(&left, &right);
/* Relational operators can compare strings */
if ((op == TOK_GT || op == TOK_EQ || op == TOK_LT)
&& left.type == VT_STR && right.type == VT_STR) {
char *ls = gw_str_to_cstr(&left.sval);
char *rs = gw_str_to_cstr(&right.sval);
int cmp = strcmp(ls, rs);
free(ls); free(rs);
gw_str_free(&left.sval);
gw_str_free(&right.sval);
result.type = VT_INT;
switch (op) {
case TOK_GT: result.ival = cmp > 0 ? -1 : 0; break;
case TOK_EQ: result.ival = cmp == 0 ? -1 : 0; break;
case TOK_LT: result.ival = cmp < 0 ? -1 : 0; break;
}
return result;
}
/* Type mismatch if mixed string/numeric */
if (left.type == VT_STR || right.type == VT_STR)
gw_error(ERR_TM);
/* Logical/bitwise operators: force to integer */
if (op == TOK_AND || op == TOK_OR || op == TOK_XOR
|| op == TOK_EQV || op == TOK_IMP) {
int16_t a = gw_to_int(&left);
int16_t b = gw_to_int(&right);
result.type = VT_INT;
switch (op) {
case TOK_AND: result.ival = a & b; break;
case TOK_OR: result.ival = a | b; break;
case TOK_XOR: result.ival = a ^ b; break;
case TOK_EQV: result.ival = ~(a ^ b); break;
case TOK_IMP: result.ival = (~a) | b; break;
}
return result;
}
/* Integer division and MOD: force to integer */
if (op == TOK_IDIV || op == TOK_MOD) {
int16_t a = gw_to_int(&left);
int16_t b = gw_to_int(&right);
if (b == 0) gw_error(ERR_DZ);
result.type = VT_INT;
if (op == TOK_IDIV)
result.ival = a / b;
else
result.ival = a % b;
return result;
}
/* Promote to common numeric type */
gw_promote(&left, &right);
/* Integer arithmetic */
if (left.type == VT_INT) {
result.type = VT_INT;
switch (op) {
case TOK_PLUS: result.ival = gw_int_add(left.ival, right.ival); break;
case TOK_MINUS: result.ival = gw_int_sub(left.ival, right.ival); break;
case TOK_MUL: result.ival = gw_int_mul(left.ival, right.ival); break;
case TOK_DIV:
/* Integer / integer -> single in GW-BASIC */
if (right.ival == 0) gw_error(ERR_DZ);
result.type = VT_SNG;
result.fval = (float)left.ival / (float)right.ival;
break;
case TOK_POW:
result.type = VT_SNG;
result.fval = (float)gw_fpow(left.ival, right.ival);
break;
case TOK_GT: result.ival = left.ival > right.ival ? -1 : 0; break;
case TOK_EQ: result.ival = left.ival == right.ival ? -1 : 0; break;
case TOK_LT: result.ival = left.ival < right.ival ? -1 : 0; break;
default: gw_error(ERR_SN);
}
return result;
}
/* Float/double arithmetic */
double a = (left.type == VT_SNG) ? left.fval : left.dval;
double b = (right.type == VT_SNG) ? right.fval : right.dval;
double r;
int is_relational = 0;
switch (op) {
case TOK_PLUS: r = gw_fadd(a, b); break;
case TOK_MINUS: r = gw_fsub(a, b); break;
case TOK_MUL: r = gw_fmul(a, b); break;
case TOK_DIV: r = gw_fdiv(a, b); break;
case TOK_POW: r = gw_fpow(a, b); break;
case TOK_GT: result.type = VT_INT; result.ival = a > b ? -1 : 0; is_relational = 1; break;
case TOK_EQ: result.type = VT_INT; result.ival = a == b ? -1 : 0; is_relational = 1; break;
case TOK_LT: result.type = VT_INT; result.ival = a < b ? -1 : 0; is_relational = 1; break;
default: gw_error(ERR_SN); r = 0;
}
if (!is_relational) {
if (left.type == VT_DBL || right.type == VT_DBL) {
result.type = VT_DBL;
result.dval = r;
} else {
result.type = VT_SNG;
result.fval = (float)r;
}
}
return result;
}
/* Combined relational: handle >=, <=, <> as combined tokens */
static int try_combined_relational(uint8_t first, gw_value_t left, gw_value_t *result)
{
uint8_t *save = gw.text_ptr;
gw_skip_spaces();
uint8_t next = gw_chrgot();
if (first == TOK_LT && next == TOK_GT) {
/* <> (not equal) */
gw.text_ptr++; /* consume '>' */
gw_value_t right = eval_expr(65);
*result = apply_binop(TOK_EQ, left, right);
result->ival = ~result->ival;
return 1;
}
if (first == TOK_LT && next == TOK_EQ) {
/* <= */
gw.text_ptr++;
gw_value_t right = eval_expr(65);
gw_value_t gt_result = apply_binop(TOK_GT, left, right);
result->type = VT_INT;
result->ival = ~gt_result.ival;
return 1;
}
if (first == TOK_GT && next == TOK_EQ) {
/* >= */
gw.text_ptr++;
gw_value_t right = eval_expr(65);
gw_value_t lt_result = apply_binop(TOK_LT, left, right);
result->type = VT_INT;
result->ival = ~lt_result.ival;
return 1;
}
if (first == TOK_EQ && (next == TOK_LT || next == TOK_GT)) {
/* =< or => (unusual but valid) */
gw.text_ptr++;
gw_value_t right = eval_expr(65);
if (next == TOK_LT) {
gw_value_t gt_result = apply_binop(TOK_GT, left, right);
result->type = VT_INT;
result->ival = ~gt_result.ival;
} else {
gw_value_t lt_result = apply_binop(TOK_LT, left, right);
result->type = VT_INT;
result->ival = ~lt_result.ival;
}
return 1;
}
/* Not a combined relational, put back */
gw.text_ptr = save;
return 0;
}
/* Main expression evaluator entry point */
static gw_value_t eval_expr(int min_prec)
{
gw_value_t left = eval_unary();
for (;;) {
gw_skip_spaces();
uint8_t tok = gw_chrgot();
int prec = op_prec(tok);
if (prec < min_prec)
break;
gw_chrget(); /* consume operator */
/* Handle combined relationals like >=, <=, <> */
if (tok == TOK_GT || tok == TOK_LT || tok == TOK_EQ) {
gw_value_t combined;
if (try_combined_relational(tok, left, &combined)) {
left = combined;
continue;
}
}
gw_value_t right = eval_expr(prec + 1);
left = apply_binop(tok, left, right);
}
return left;
}
/* Unary operators: NOT, unary +, unary - */
static gw_value_t eval_unary(void)
{
gw_skip_spaces();
uint8_t tok = gw_chrgot();
if (tok == TOK_MINUS) {
gw_chrget();
gw_value_t v = eval_unary();
if (v.type == VT_STR) gw_error(ERR_TM);
if (v.type == VT_INT) {
v.ival = gw_int_neg(v.ival);
} else if (v.type == VT_SNG) {
v.fval = -v.fval;
} else {
v.dval = -v.dval;
}
return v;
}
if (tok == TOK_PLUS) {
gw_chrget();
gw_value_t v = eval_unary();
if (v.type == VT_STR) gw_error(ERR_TM);
return v;
}
if (tok == TOK_NOT) {
gw_chrget();
gw_value_t v = eval_expr(90);
int16_t i = gw_to_int(&v);
gw_value_t result;
result.type = VT_INT;
result.ival = ~i;
return result;
}
return eval_atom();
}
/* Parse a parenthesized expression */
static gw_value_t eval_paren(void)
{
gw_chrget(); /* skip '(' */
gw_value_t v = eval_expr(0);
gw_skip_spaces();
if (gw_chrgot() != ')')
gw_error(ERR_SN);
gw_chrget(); /* skip ')' */
return v;
}
/* Parse a numeric literal from token stream */
static gw_value_t eval_number(void)
{
gw_value_t v;
uint8_t tok = gw_chrgot();
/* Single-byte integer constant 0-9 */
if (tok >= 0x11 && tok <= 0x1A) {
gw.text_ptr++;
v.type = VT_INT;
v.ival = tok - 0x11;
return v;
}
/* One-byte integer constant (TOK_INT1 followed by 1 data byte) */
if (tok == TOK_INT1) {
gw.text_ptr++;
v.type = VT_INT;
v.ival = *gw.text_ptr++;
return v;
}
/* Two-byte integer constant (TOK_INT2 followed by 2 data bytes) */
if (tok == TOK_INT2) {
gw.text_ptr++;
uint8_t lo = *gw.text_ptr++;
uint8_t hi = *gw.text_ptr++;
v.type = VT_INT;
v.ival = (int16_t)(lo | (hi << 8));
return v;
}
/* Single precision constant (0x1C followed by 4 data bytes) */
if (tok == TOK_CONST_SNG) {
gw.text_ptr++;
float fval;
memcpy(&fval, gw.text_ptr, 4);
gw.text_ptr += 4;
v.type = VT_SNG;
v.fval = fval;
return v;
}
/* Double precision constant (0x1F followed by 8 data bytes) */
if (tok == TOK_CONST_DBL) {
gw.text_ptr++;
double dval;
memcpy(&dval, gw.text_ptr, 8);
gw.text_ptr += 8;
v.type = VT_DBL;
v.dval = dval;
return v;
}
gw_error(ERR_SN);
v.type = VT_INT;
v.ival = 0;
return v;
}
/* Parse a string literal */
static gw_value_t eval_string_literal(void)
{
/* Advance past the opening quote - don't skip spaces */
gw.text_ptr++;
uint8_t *start = gw.text_ptr;
int len = 0;
while (*gw.text_ptr && *gw.text_ptr != '"') {
gw.text_ptr++;
len++;
}
gw_value_t v;
v.type = VT_STR;
v.sval = gw_str_alloc(len);
memcpy(v.sval.data, start, len);
if (*gw.text_ptr == '"')
gw.text_ptr++; /* skip closing " - don't use chrget, next token reads normally */
return v;
}
/* Evaluate built-in functions (0xFF prefix) */
static gw_value_t eval_function(uint8_t prefix, uint8_t func_tok)
{
gw_value_t v, arg;
int n;
/* Most functions: FUNCNAME(expr) */
switch (func_tok) {
/* Numeric -> Numeric functions */
case FUNC_SGN:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_INT;
v.ival = (int16_t)gw_sgn(gw_to_dbl(&arg));
return v;
case FUNC_INT:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = (arg.type == VT_DBL) ? VT_DBL : VT_SNG;
if (v.type == VT_DBL) v.dval = gw_int_fn(arg.dval);
else v.fval = (float)gw_int_fn(gw_to_dbl(&arg));
return v;
case FUNC_ABS:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = arg.type;
if (arg.type == VT_INT) v.ival = arg.ival < 0 ? gw_int_neg(arg.ival) : arg.ival;
else if (arg.type == VT_SNG) v.fval = (float)gw_abs(arg.fval);
else v.dval = gw_abs(arg.dval);
return v;
case FUNC_SQR:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_DBL;
v.dval = gw_sqr(gw_to_dbl(&arg));
if (arg.type != VT_DBL) { v.type = VT_SNG; v.fval = (float)v.dval; }
return v;
case FUNC_SIN:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_DBL;
v.dval = gw_sin(gw_to_dbl(&arg));
if (arg.type != VT_DBL) { v.type = VT_SNG; v.fval = (float)v.dval; }
return v;
case FUNC_COS:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_DBL;
v.dval = gw_cos(gw_to_dbl(&arg));
if (arg.type != VT_DBL) { v.type = VT_SNG; v.fval = (float)v.dval; }
return v;
case FUNC_TAN:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_DBL;
v.dval = gw_tan(gw_to_dbl(&arg));
if (arg.type != VT_DBL) { v.type = VT_SNG; v.fval = (float)v.dval; }
return v;
case FUNC_ATN:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_DBL;
v.dval = gw_atn(gw_to_dbl(&arg));
if (arg.type != VT_DBL) { v.type = VT_SNG; v.fval = (float)v.dval; }
return v;
case FUNC_LOG:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_DBL;
v.dval = gw_log(gw_to_dbl(&arg));
if (arg.type != VT_DBL) { v.type = VT_SNG; v.fval = (float)v.dval; }
return v;
case FUNC_EXP:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_DBL;
v.dval = gw_exp(gw_to_dbl(&arg));
if (arg.type != VT_DBL) { v.type = VT_SNG; v.fval = (float)v.dval; }
return v;
case FUNC_RND:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_SNG;
v.fval = (float)gw_rnd(gw_to_dbl(&arg));
return v;
case FUNC_FIX:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = arg.type;
if (arg.type == VT_INT) v.ival = arg.ival;
else if (arg.type == VT_SNG) v.fval = (float)gw_fix(arg.fval);
else v.dval = gw_fix(arg.dval);
return v;
case FUNC_CINT:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_INT;
v.ival = gw_cint(gw_to_dbl(&arg));
return v;
case FUNC_CSNG:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_SNG;
v.fval = gw_csng(gw_to_dbl(&arg));
return v;
case FUNC_CDBL:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
v.type = VT_DBL;
v.dval = gw_cdbl(&arg);
return v;
/* String functions */
case FUNC_LEN:
gw_expect('(');
arg = gw_eval_str();
gw_expect_rparen();
return gw_fn_len(&arg);
case FUNC_ASC:
gw_expect('(');
arg = gw_eval_str();
gw_expect_rparen();
return gw_fn_asc(&arg);
case FUNC_CHR:
gw_expect('(');
n = gw_eval_int();
gw_expect_rparen();
return gw_fn_chr(n);
case FUNC_VAL:
gw_expect('(');
arg = gw_eval_str();
gw_expect_rparen();
return gw_fn_val(&arg);
case FUNC_STR:
gw_expect('(');
arg = gw_eval_num();
gw_expect_rparen();
return gw_fn_str(&arg);
case FUNC_SPACE:
gw_expect('(');
n = gw_eval_int();
gw_expect_rparen();
return gw_fn_space(n);
case FUNC_LEFT:
{
gw_expect('(');
gw_value_t s = gw_eval_str();
gw_skip_spaces();
gw_expect(',');
n = gw_eval_int();
gw_expect_rparen();
return gw_fn_left(&s, n);
}
case FUNC_RIGHT:
{
gw_expect('(');
gw_value_t s = gw_eval_str();
gw_skip_spaces();
gw_expect(',');
n = gw_eval_int();
gw_expect_rparen();
return gw_fn_right(&s, n);
}
case FUNC_MID:
{
gw_expect('(');
gw_value_t s = gw_eval_str();
gw_skip_spaces();
gw_expect(',');
int start = gw_eval_int();
int len = -1;
gw_skip_spaces();
if (gw_chrgot() == ',') {
gw_chrget();
len = gw_eval_int();
}
gw_expect_rparen();
if (len < 0)
len = 255; /* default: rest of string */
return gw_fn_mid(&s, start, len);
}
case FUNC_HEX:
gw_expect('(');
n = gw_eval_int();
gw_expect_rparen();
return gw_fn_hex(n);
case FUNC_OCT:
gw_expect('(');
n = gw_eval_int();
gw_expect_rparen();
return gw_fn_oct(n);
case FUNC_FRE:
gw_expect('(');
arg = gw_eval(); /* can be string or numeric */
gw_expect_rparen();
if (arg.type == VT_STR) gw_str_free(&arg.sval);
/* Return fake free memory value */
v.type = VT_SNG;
v.fval = 60000.0f;
return v;
case FUNC_POS:
gw_expect('(');
gw_eval(); /* dummy argument */
gw_expect_rparen();
v.type = VT_INT;
v.ival = gw_hal ? gw_hal->get_cursor_col() + 1 : 1;
return v;
case FUNC_LPOS:
gw_expect('(');
gw_eval();
gw_expect_rparen();
v.type = VT_INT;
v.ival = 1;
return v;
case FUNC_INP:
case FUNC_PEEK:
case FUNC_PEN:
case FUNC_STICK:
case FUNC_STRIG:
case FUNC_EOF:
case FUNC_LOC:
case FUNC_LOF:
gw_expect('(');
gw_eval();
gw_expect_rparen();
v.type = VT_INT;
v.ival = 0;
return v;
default:
gw_error(ERR_SN);
v.type = VT_INT;
v.ival = 0;
return v;
}
}
/* Atom: number, string, function, or parenthesized expression */
static gw_value_t eval_atom(void)
{
gw_skip_spaces();
uint8_t tok = gw_chrgot();
/* Parenthesized expression */
if (tok == '(')
return eval_paren();
/* String literal */
if (tok == '"')
return eval_string_literal();
/* Numeric constant tokens: 0x0E-0x1F covers all embedded constants */
if (tok >= TOK_INT2 && tok <= TOK_CONST_DBL)
return eval_number();
/* Built-in function (0xFF prefix) */
if (tok == TOK_PREFIX_FF) {
gw_chrget(); /* consume 0xFF */
uint8_t func = gw_chrgot();
gw_chrget(); /* consume function token */
return eval_function(TOK_PREFIX_FF, func);
}
/* Extended function (0xFD prefix) */
if (tok == TOK_PREFIX_FD) {
gw_chrget();
uint8_t func = gw_chrgot();
gw_chrget();
return eval_function(TOK_PREFIX_FD, func);
}
/* STRING$ function (single-byte token but acts like function) */
if (tok == TOK_STRINGS) {
gw_chrget();
gw_expect('(');
int n = gw_eval_int();
gw_skip_spaces();
gw_expect(',');
gw_skip_spaces();
gw_value_t arg2 = gw_eval();
gw_expect_rparen();
int code;
if (arg2.type == VT_STR) {
if (arg2.sval.len == 0) gw_error(ERR_FC);
code = (uint8_t)arg2.sval.data[0];
gw_str_free(&arg2.sval);
} else {
code = gw_to_int(&arg2);
}
return gw_fn_strings(n, code);
}
/* INSTR function */
if (tok == TOK_INSTR) {
gw_chrget();
gw_expect('(');
/* First arg could be start position or string */
gw_value_t first = gw_eval();
int start = 1;
gw_value_t haystack, needle;
gw_skip_spaces();
gw_expect(',');
if (first.type != VT_STR) {
start = gw_to_int(&first);
haystack = gw_eval_str();
gw_skip_spaces();
gw_expect(',');
needle = gw_eval_str();
} else {
haystack = first;
needle = gw_eval_str();
}
gw_expect_rparen();
return gw_fn_instr(start, &haystack, &needle);
}
/* ERL, ERR (error info pseudo-variables) */
if (tok == TOK_ERL) {
gw_chrget();
gw_value_t v;
v.type = VT_INT;
v.ival = 0;
return v;
}
if (tok == TOK_ERR) {
gw_chrget();
gw_value_t v;
v.type = VT_INT;
v.ival = gw_errno;
return v;
}
/* CSRLIN pseudo-variable */
if (tok == TOK_CSRLIN) {
gw_chrget();
gw_value_t v;
v.type = VT_INT;
v.ival = gw_hal ? gw_hal->get_cursor_row() + 1 : 1;
return v;
}
/* INKEY$ pseudo-variable */
if (tok == TOK_INKEYS) {
gw_chrget();
gw_value_t v;
v.type = VT_STR;
if (gw_hal && gw_hal->kbhit()) {
int ch = gw_hal->getch();
v.sval = gw_str_alloc(1);
v.sval.data[0] = ch;
} else {
v.sval = gw_str_alloc(0);
}
return v;
}
/* Extended statement tokens that work as functions (DATE$, TIME$) */
if (tok == TOK_PREFIX_FE) {
uint8_t *save = gw.text_ptr;
gw_chrget();
uint8_t xtok = gw_chrgot();
if (xtok == XSTMT_DATE || xtok == XSTMT_TIME) {
gw_chrget();
gw_value_t v;
v.type = VT_STR;
if (xtok == XSTMT_DATE) {
v.sval = gw_str_from_cstr("01-01-2026");
} else {
v.sval = gw_str_from_cstr("00:00:00");
}
return v;
}
gw.text_ptr = save;
}
/* FN call */
if (tok == TOK_FN) {
gw_chrget();
return gw_eval_fn_call();
}
/* Variable or array element */
if (gw_is_letter(tok)) {
char name[2];
gw_valtype_t type = gw_parse_varname(name);
gw_skip_spaces();
if (gw_chrgot() == '(') {
/* Array element */
gw_value_t *elem = gw_array_element(name, type);
gw_value_t v = *elem;
if (v.type == VT_STR && v.sval.data)
v.sval = gw_str_copy(&elem->sval);
return v;
}
/* Scalar variable */
var_entry_t *var = gw_var_find_or_create(name, type);
gw_value_t v = var->val;
if (v.type == VT_STR && v.sval.data)
v.sval = gw_str_copy(&var->val.sval);
return v;
}
/* End of expression */
if (tok == 0 || tok == ':' || tok == ')' || tok == ','
|| tok == ';' || tok == TOK_THEN || tok == TOK_ELSE
|| tok == TOK_TO || tok == TOK_STEP)
gw_error(ERR_MO);
gw_error(ERR_SN);
gw_value_t dummy = { .type = VT_INT, .ival = 0 };
return dummy;
}
/* Public API */
gw_value_t gw_eval(void)
{
return eval_expr(0);
}
gw_value_t gw_eval_num(void)
{
gw_value_t v = eval_expr(0);
if (v.type == VT_STR)
gw_error(ERR_TM);
return v;
}
gw_value_t gw_eval_str(void)
{
gw_value_t v = eval_expr(0);
if (v.type != VT_STR)
gw_error(ERR_TM);
return v;
}
int16_t gw_eval_int(void)
{
gw_value_t v = gw_eval_num();
return gw_to_int(&v);
}
uint16_t gw_eval_uint16(void)
{
gw_value_t v = gw_eval_num();
double d = gw_to_dbl(&v);
if (d < 0 || d > 65535)
gw_error(ERR_FC);
return (uint16_t)d;
}
void gw_expect_rparen(void)
{
gw_skip_spaces();
if (gw_chrgot() != ')')
gw_error(ERR_SN);
gw_chrget();
}
/* Type conversion helpers */
int16_t gw_to_int(gw_value_t *v)
{
switch (v->type) {
case VT_INT: return v->ival;
case VT_SNG: return gw_cint(v->fval);
case VT_DBL: return gw_cint(v->dval);
default: gw_error(ERR_TM); return 0;
}
}
float gw_to_sng(gw_value_t *v)
{
switch (v->type) {
case VT_INT: return (float)v->ival;
case VT_SNG: return v->fval;
case VT_DBL: return (float)v->dval;
default: gw_error(ERR_TM); return 0;
}
}
double gw_to_dbl(gw_value_t *v)
{
switch (v->type) {
case VT_INT: return (double)v->ival;
case VT_SNG: return (double)v->fval;
case VT_DBL: return v->dval;
default: gw_error(ERR_TM); return 0;
}
}