Compiler: DEF FN, DEFINT, STRING$("c"), gw_cint rounding — 37/72 tests

DEF FN: embed definition tokens and call gw_exec_stmt() at runtime.
FN calls: embed call tokens, sync variables, call gw_eval_fn_call().
Unlocks def_fn.bas, def_types.bas, stats_calc (partial).

DEFINT/DEFSNG/DEFDBL/DEFSTR: pre-scan pass in analysis.c sets the
def_type table before variable type resolution. Integer variables now
correctly use gw_cint() rounding instead of C truncation (7.9 → 8,
not 7). Unlocks variables.bas, mult_table.bas.

STRING$(n, "c"): handle string second argument by extracting the first
character's ASCII code at compile time. Unlocks diamond.bas.

Extended expressions (FE-prefix): TIMER, DATE$, TIME$, ENVIRON$, ERDEV$
handled in both emit_atom and emit_str_expr.

37/72 tests pass. 0 compile errors. 32 output mismatches.
This commit is contained in:
Eremey Valetov
2026-03-29 14:28:28 -04:00
parent 12c4ee7cbb
commit 1858ad4b0c
2 changed files with 119 additions and 12 deletions
+43
View File
@@ -250,6 +250,49 @@ void analysis_run(analysis_t *a)
for (int i = 0; i < 26; i++)
a->def_type[i] = VT_SNG;
/* Pass 0: scan for DEFINT/DEFSNG/DEFDBL/DEFSTR to set type defaults */
for (program_line_t *line = gw.prog_head; line; line = line->next) {
uint8_t *p = line->tokens;
while (*p) {
if (*p == TOK_DEFINT || *p == TOK_DEFSNG ||
*p == TOK_DEFDBL || *p == TOK_DEFSTR) {
gw_valtype_t dt;
switch (*p) {
case TOK_DEFINT: dt = VT_INT; break;
case TOK_DEFSNG: dt = VT_SNG; break;
case TOK_DEFDBL: dt = VT_DBL; break;
case TOK_DEFSTR: dt = VT_STR; break;
default: dt = VT_SNG; break;
}
p++;
while (*p == ' ') p++;
/* Parse letter ranges: A-Z, X-Z, etc. */
while (*p && *p != ':' && *p != 0) {
if (is_letter(*p)) {
int from = toupper(*p) - 'A';
int to = from;
p++;
while (*p == ' ') p++;
if (*p == TOK_MINUS || *p == '-') {
p++;
while (*p == ' ') p++;
if (is_letter(*p)) {
to = toupper(*p) - 'A';
p++;
}
}
for (int c = from; c <= to && c < 26; c++)
a->def_type[c] = dt;
}
while (*p == ' ' || *p == ',') p++;
if (!is_letter(*p)) break;
}
continue;
}
p++;
}
}
/* Pass 1: collect line numbers and scan tokens */
for (program_line_t *line = gw.prog_head; line; line = line->next) {
if (a->line_count >= MAX_LINES) break;
+76 -12
View File
@@ -289,7 +289,7 @@ static void emit_atom(void)
return;
}
case FUNC_CINT: {
EMIT("((int16_t)gw_round(");
EMIT("((int16_t)gw_cint(");
advance(); emit_num_expr();
if (cur() == ')') advance();
EMIT("))");
@@ -385,6 +385,52 @@ static void emit_atom(void)
}
}
/* FN call: embed tokens and use interpreter's gw_eval_fn_call */
if (tok == TOK_FN) {
uint8_t *start = tp;
tp++; /* skip TOK_FN */
skip_spaces();
/* Skip function name + arguments */
while (is_letter(cur()) || (cur() >= '0' && cur() <= '9')) advance();
if (cur() == '(') {
int depth = 1; advance();
while (*tp && depth > 0) {
if (cur() == '(') depth++;
else if (cur() == ')') depth--;
advance();
}
}
int len = (int)(tp - start);
/* Sync variables, embed tokens, call evaluator */
EMIT("({");
for (int vi = 0; vi < ana->var_count; vi++) {
var_info_t *v = &ana->vars[vi];
if (v->name[1])
EMIT(" gw_var_find_or_create(\"%c%c\",%d)->val=(gw_value_t){.type=%d,",
v->name[0], v->name[1], v->type, v->type);
else
EMIT(" gw_var_find_or_create(\"%c\",%d)->val=(gw_value_t){.type=%d,",
v->name[0], v->type, v->type);
switch (v->type) {
case VT_INT: EMIT(".ival="); break;
case VT_SNG: EMIT(".fval="); break;
case VT_DBL: EMIT(".dval="); break;
case VT_STR: EMIT(".sval="); break;
}
emit_varname(v->name, v->type);
EMIT("};");
}
EMIT(" static const uint8_t _fn[]={");
for (int i = 0; i < len; i++)
EMIT("%s%u", i ? "," : "", start[i]);
EMIT(",0};");
EMIT(" gw.text_ptr=(uint8_t*)_fn+1;"); /* skip TOK_FN */
EMIT(" while(*gw.text_ptr==' ')gw.text_ptr++;"); /* skip spaces */
EMIT(" gw_value_t _fr=gw_eval_fn_call();");
EMIT(" _fr.type==VT_INT?_fr.ival:_fr.type==VT_DBL?_fr.dval:(double)_fr.fval; })");
return;
}
/* Extended expressions (0xFE prefix) — DATE$, TIME$, TIMER, etc. */
if (tok == TOK_PREFIX_FE) {
uint8_t xtok = tp[1];
@@ -754,13 +800,13 @@ static void emit_str_expr(void)
char *arg1 = emit_to_buf(emit_prec_wrapper, 0);
if (cur() == ',') advance();
skip_spaces();
/* Second arg can be a number (char code) or a string */
if (cur() == '"' || (is_letter(cur()) && ({
uint8_t *sv = tp; char n[2]; gw_valtype_t t = parse_var(n); tp = sv; t == VT_STR; }))) {
/* String second arg — use first char's ASCII value */
char *arg2 = emit_to_buf(emit_prec_wrapper, 0);
EMIT("gw_fn_strings((int)(%s), (int)(%s)).sval", arg1, arg2);
free(arg2);
if (cur() == '"') {
/* STRING$(n, "c") — use ASCII value of first char */
tp++; /* skip opening " */
int ch = *tp ? *tp : 32;
while (*tp && *tp != '"') tp++;
if (*tp == '"') tp++;
EMIT("gw_fn_strings((int)(%s), %d).sval", arg1, ch);
} else {
char *arg2 = emit_to_buf(emit_prec_wrapper, 0);
EMIT("gw_fn_strings((int)(%s), (int)(%s)).sval", arg1, arg2);
@@ -1005,9 +1051,15 @@ static void emit_assignment(void)
} else {
EMIT(" ");
emit_varname(name, type);
EMIT(" = (%s)(", c_type(type));
emit_num_expr();
EMIT(");\n");
if (type == VT_INT) {
EMIT(" = gw_cint((double)(");
emit_num_expr();
EMIT("));\n");
} else {
EMIT(" = (%s)(", c_type(type));
emit_num_expr();
EMIT(");\n");
}
}
}
@@ -1390,8 +1442,20 @@ static void emit_stmt(void)
} else {
EMIT(" gw.def_seg = 0;\n");
}
} else if (cur() == TOK_FN) {
/* DEF FN — embed tokens for runtime interpreter */
uint8_t *start = tp - 2; /* back to TOK_DEF */
while (*tp && *tp != ':') tp++;
int len = (int)(tp - start);
EMIT(" { static const uint8_t _df[] = {");
for (int i = 0; i < len; i++)
EMIT("%s%u", i ? "," : "", start[i]);
EMIT(",0};\n");
EMIT(" uint8_t *_save = gw.text_ptr;\n");
EMIT(" gw.text_ptr = (uint8_t *)_df;\n");
EMIT(" gw_exec_stmt();\n");
EMIT(" gw.text_ptr = _save; }\n");
} else {
/* DEF FN — skip for now, handled by analysis */
while (cur() && cur() != ':' && cur() != 0) tp++;
}
return;