Add GitHub Actions CI with automated build and test. Implement real terminal I/O with raw mode (enable_raw/disable_raw, proper INKEY$ polling via VMIN=0/VTIME=0, INPUT$ function). Add Sixel graphics engine with virtual framebuffer (SCREEN 1: 320x200, SCREEN 2: 640x200), Bresenham line drawing, midpoint circle, flood fill PAINT, DRAW mini-language parser, and Sixel encoder with RLE. Replace all graphics stubs with real implementations (PSET, LINE, CIRCLE, DRAW, PAINT, COLOR, SCREEN, POINT). Fix AND/OR/XOR operator precedence to be lower than relational operators. Add 13 classic test programs (39 total). Bump version to 0.5.0.
217 lines
5.6 KiB
C
217 lines
5.6 KiB
C
#include "gwbasic.h"
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
/*
|
|
* INPUT and LINE INPUT statements.
|
|
* INPUT: prompt, read comma-separated values into variables.
|
|
* LINE INPUT: read entire line as string.
|
|
*/
|
|
|
|
static char *read_input_line(void)
|
|
{
|
|
static char buf[256];
|
|
if (gw_hal) gw_hal->disable_raw();
|
|
if (fgets(buf, sizeof(buf), stdin) == NULL) {
|
|
if (gw_hal) gw_hal->enable_raw();
|
|
return NULL;
|
|
}
|
|
if (gw_hal) gw_hal->enable_raw();
|
|
int len = strlen(buf);
|
|
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
|
|
buf[--len] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
void gw_stmt_input(void)
|
|
{
|
|
gw_skip_spaces();
|
|
|
|
/* Optional semicolon after INPUT (suppresses CR on prompt) */
|
|
/* INPUT [;]["prompt";] var, var, ... */
|
|
bool no_cr = false;
|
|
if (gw_chrgot() == ';') {
|
|
no_cr = true;
|
|
gw_chrget();
|
|
gw_skip_spaces();
|
|
}
|
|
(void)no_cr;
|
|
|
|
/* Check for prompt string */
|
|
if (gw_chrgot() == '"') {
|
|
gw.text_ptr++;
|
|
while (*gw.text_ptr && *gw.text_ptr != '"') {
|
|
if (gw_hal) gw_hal->putch(*gw.text_ptr);
|
|
else putchar(*gw.text_ptr);
|
|
gw.text_ptr++;
|
|
}
|
|
if (*gw.text_ptr == '"')
|
|
gw.text_ptr++;
|
|
|
|
gw_skip_spaces();
|
|
if (gw_chrgot() == ';') {
|
|
gw_chrget();
|
|
} else if (gw_chrgot() == ',') {
|
|
gw_chrget();
|
|
}
|
|
}
|
|
|
|
retry:;
|
|
/* Print "? " */
|
|
if (gw_hal) gw_hal->puts("? ");
|
|
else fputs("? ", stdout);
|
|
fflush(stdout);
|
|
|
|
char *line = read_input_line();
|
|
if (!line) return;
|
|
|
|
const char *p = line;
|
|
|
|
/* Parse variable list and assign values */
|
|
int var_idx = 0;
|
|
for (;;) {
|
|
gw_skip_spaces();
|
|
uint8_t ch = gw_chrgot();
|
|
if (ch == 0 || ch == ':')
|
|
break;
|
|
|
|
char name[2];
|
|
gw_valtype_t type = gw_parse_varname(name);
|
|
|
|
gw_skip_spaces();
|
|
var_entry_t *var;
|
|
gw_value_t *arr_elem = NULL;
|
|
if (gw_chrgot() == '(') {
|
|
arr_elem = gw_array_element(name, type);
|
|
} else {
|
|
var = gw_var_find_or_create(name, type);
|
|
}
|
|
|
|
/* Skip whitespace in input */
|
|
while (*p == ' ') p++;
|
|
|
|
/* Parse value from input line */
|
|
gw_value_t val;
|
|
if (type == VT_STR) {
|
|
/* Read string: until comma or end of line */
|
|
const char *start = p;
|
|
if (*p == '"') {
|
|
/* Quoted string */
|
|
p++;
|
|
start = p;
|
|
while (*p && *p != '"') p++;
|
|
int len = p - start;
|
|
val.type = VT_STR;
|
|
val.sval = gw_str_alloc(len);
|
|
memcpy(val.sval.data, start, len);
|
|
if (*p == '"') p++;
|
|
} else {
|
|
while (*p && *p != ',') p++;
|
|
int len = p - start;
|
|
/* Trim trailing spaces */
|
|
while (len > 0 && start[len - 1] == ' ') len--;
|
|
val.type = VT_STR;
|
|
val.sval = gw_str_alloc(len);
|
|
memcpy(val.sval.data, start, len);
|
|
}
|
|
} else {
|
|
char *end;
|
|
double d = strtod(p, &end);
|
|
if (end == p) d = 0;
|
|
p = end;
|
|
val.type = VT_DBL;
|
|
val.dval = d;
|
|
}
|
|
|
|
if (arr_elem) {
|
|
if (type == VT_STR) {
|
|
gw_str_free(&arr_elem->sval);
|
|
arr_elem->sval = val.sval;
|
|
arr_elem->type = VT_STR;
|
|
} else {
|
|
switch (type) {
|
|
case VT_INT: arr_elem->ival = gw_to_int(&val); break;
|
|
case VT_SNG: arr_elem->fval = gw_to_sng(&val); break;
|
|
case VT_DBL: arr_elem->dval = gw_to_dbl(&val); break;
|
|
default: break;
|
|
}
|
|
arr_elem->type = type;
|
|
}
|
|
} else {
|
|
gw_var_assign(var, &val);
|
|
}
|
|
|
|
if (*p == ',') p++;
|
|
var_idx++;
|
|
|
|
gw_skip_spaces();
|
|
if (gw_chrgot() == ',') {
|
|
gw_chrget();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If we have more variables than input, redo */
|
|
gw_skip_spaces();
|
|
if (gw_chrgot() == ',') {
|
|
if (gw_hal) gw_hal->puts("?Redo from start\n");
|
|
else fputs("?Redo from start\n", stdout);
|
|
/* Need to re-read, but for simplicity continue */
|
|
goto retry;
|
|
}
|
|
}
|
|
|
|
void gw_stmt_line_input(void)
|
|
{
|
|
gw_skip_spaces();
|
|
|
|
/* LINE INPUT [;]["prompt";] string-variable */
|
|
if (gw_chrgot() == ';') {
|
|
gw_chrget();
|
|
gw_skip_spaces();
|
|
}
|
|
|
|
/* Check for prompt string */
|
|
if (gw_chrgot() == '"') {
|
|
gw.text_ptr++;
|
|
while (*gw.text_ptr && *gw.text_ptr != '"') {
|
|
if (gw_hal) gw_hal->putch(*gw.text_ptr);
|
|
else putchar(*gw.text_ptr);
|
|
gw.text_ptr++;
|
|
}
|
|
if (*gw.text_ptr == '"')
|
|
gw.text_ptr++;
|
|
gw_skip_spaces();
|
|
if (gw_chrgot() == ';')
|
|
gw_chrget();
|
|
}
|
|
|
|
char name[2];
|
|
gw_valtype_t type = gw_parse_varname(name);
|
|
if (type != VT_STR)
|
|
gw_error(ERR_TM);
|
|
|
|
fflush(stdout);
|
|
char *line = read_input_line();
|
|
if (!line) line = "";
|
|
|
|
gw_value_t val;
|
|
val.type = VT_STR;
|
|
val.sval = gw_str_from_cstr(line);
|
|
|
|
gw_skip_spaces();
|
|
var_entry_t *var;
|
|
if (gw_chrgot() == '(') {
|
|
gw_value_t *elem = gw_array_element(name, type);
|
|
gw_str_free(&elem->sval);
|
|
elem->sval = val.sval;
|
|
elem->type = VT_STR;
|
|
} else {
|
|
var = gw_var_find_or_create(name, type);
|
|
gw_var_assign(var, &val);
|
|
}
|
|
}
|