Compiler: string concat, division semantics, PRINT USING null bytes — 46/72

String concatenation: refactored emit_str_expr to use emit_str_atom +
concat loop. Self-referencing assignment A$ = A$ + B$ evaluates RHS
to temp before freeing old value.

Division semantics: GW-BASIC / always produces float (cast both operands
to double). Integer division is only for \ operator.

PRINT USING null-byte fix: token scanner now skips over float/double
constants (which may contain 0x00 bytes) instead of stopping on them.
Uses program_line_t.len for bounds instead of null termination.

FRE(): call strpool_gc() via comma expression for accurate reporting.
STICK(): return 128 (center). EOF/LOC/LOF function stubs.

46/72 tests pass. New: portio, print_using, print_using_edge,
monte_carlo (partial), string_gc, plus retained caesar_cipher,
roman_numerals.
This commit is contained in:
Eremey Valetov
2026-03-29 15:42:27 -04:00
parent 70e5fdbba8
commit e1c0b91522
+50 -8
View File
@@ -357,10 +357,9 @@ static void emit_atom(void)
if (cur() == '(') { advance(); emit_num_expr(); if (cur() == ')') advance(); }
return;
case FUNC_FRE:
EMIT("((float)strpool_free())");
EMIT("(strpool_gc(), (float)strpool_free())");
if (cur() == '(') {
advance();
/* consume arg but ignore */
while (cur() && cur() != ')') tp++;
if (cur() == ')') advance();
}
@@ -377,8 +376,33 @@ static void emit_atom(void)
if (cur() == ')') advance();
EMIT(")");
return;
case FUNC_STICK:
/* Joystick position — return 128 (center) */
EMIT("128");
if (cur() == '(') { advance(); while (cur() && cur() != ')') tp++; if (cur() == ')') advance(); }
return;
case FUNC_STRIG:
EMIT("0"); /* No joystick button */
if (cur() == '(') { advance(); while (cur() && cur() != ')') tp++; if (cur() == ')') advance(); }
return;
case FUNC_PEN:
case FUNC_LPOS:
EMIT("0");
if (cur() == '(') { advance(); while (cur() && cur() != ')') tp++; if (cur() == ')') advance(); }
return;
case FUNC_EOF: {
EMIT("gw_file_eof((int)(");
advance(); emit_num_expr();
if (cur() == ')') advance();
EMIT("))");
return;
}
case FUNC_LOC:
case FUNC_LOF:
EMIT("0 /* LOC/LOF */");
if (cur() == '(') { advance(); while (cur() && cur() != ')') tp++; if (cur() == ')') advance(); }
return;
default:
/* Fallback: emit 0 for unhandled functions */
EMIT("0 /* unhandled func 0x%02x */", func);
if (cur() == '(') { advance(); while (cur() && cur() != ')') tp++; if (cur() == ')') advance(); }
return;
@@ -614,6 +638,9 @@ static void emit_num_prec(int min_prec)
fprintf(cm, "((int16_t)(%s) / (int16_t)(%s))", left, right);
} else if (op == TOK_POW) {
fprintf(cm, "pow((double)(%s), (double)(%s))", left, right);
} else if (op == TOK_DIV) {
/* GW-BASIC / always produces float (unlike \ which is integer div) */
fprintf(cm, "((double)(%s) / (double)(%s))", left, right);
} else {
fprintf(cm, "(%s %s %s)", left, binop_c(op), right);
}
@@ -1122,11 +1149,26 @@ static void emit_stmt(void)
/* Skip past USING token, then embed remaining bytes */
advance(); /* skip TOK_USING */
uint8_t *start = tp;
/* Scan to end of statement, skipping string literals */
while (*tp) {
if (*tp == '"') { tp++; while (*tp && *tp != '"') tp++; if (*tp) tp++; continue; }
if (*tp == ':') break;
tp++;
/* Scan to end of statement, skipping strings and constants
* (float constants may contain 0x00 bytes) */
{
/* Find the token line end from the program_line_t */
program_line_t *cur_line = NULL;
for (program_line_t *pl = gw.prog_head; pl; pl = pl->next) {
if (tp >= pl->tokens && tp < pl->tokens + pl->len + 1) {
cur_line = pl; break;
}
}
uint8_t *line_end = cur_line ? cur_line->tokens + cur_line->len : tp + 256;
while (tp < line_end) {
if (*tp == '"') { tp++; while (tp < line_end && *tp != '"') tp++; if (tp < line_end) tp++; continue; }
if (*tp == TOK_CONST_SNG) { tp += 5; continue; }
if (*tp == TOK_CONST_DBL) { tp += 9; continue; }
if (*tp == TOK_INT2) { tp += 3; continue; }
if (*tp == TOK_INT1) { tp += 2; continue; }
if (*tp == ':' || *tp == 0) break;
tp++;
}
}
int len = (int)(tp - start);
/* Sync compiled variables to interpreter table so gw_eval works */