From 9ca23e5d107ac6e18498bf0d77da01fa83d1f16b Mon Sep 17 00:00:00 2001 From: Eremey Valetov Date: Tue, 10 Feb 2026 17:01:50 -0500 Subject: [PATCH] Fix variable names with digits, ERL, update docs - Tokenizer now copies entire identifier (letters+digits) when no keyword matches, fixing variables like K2, V1, AB3% - ERL returns actual error line number instead of 0 - Update README and Sphinx docs for v0.5.0 --- README.md | 42 ++++++++++++++++++++++---------- src/eval.c | 2 +- src/tokenizer.c | 11 +++++++++ tests/programs/error_handler.bas | 2 +- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e0366a9..30d8bac 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Interactive mode: ``` $ ./gwbasic -GW-BASIC 0.4.0 +GW-BASIC 0.5.0 (C) Eremey Valetov 2026. MIT License. Based on Microsoft GW-BASIC assembly source. Ok @@ -37,7 +37,7 @@ Ok Run a program file: ```bash -./gwbasic examples/prime_sieve.bas +./gwbasic tests/programs/prime_sieve.bas ``` Pipe input: @@ -56,7 +56,7 @@ echo '10 FOR I=1 TO 10:PRINT I*I;:NEXT' | ./gwbasic RND, FIX, CINT, CSNG, CDBL **String functions:** LEN, ASC, CHR$, VAL, STR$, LEFT$, RIGHT$, MID$, -SPACE$, STRING$, HEX$, OCT$, INSTR +SPACE$, STRING$, HEX$, OCT$, INSTR, INPUT$ **Statements:** @@ -65,18 +65,36 @@ SPACE$, STRING$, HEX$, OCT$, INSTR | Output | PRINT, LPRINT, PRINT USING, WRITE, CLS | | Variables | LET, DIM, ERASE, SWAP, DEFINT/SNG/DBL/STR | | Control flow | GOTO, GOSUB/RETURN, FOR/NEXT, IF/THEN/ELSE, WHILE/WEND, ON...GOTO/GOSUB | -| Input | INPUT, LINE INPUT, DATA/READ/RESTORE | +| Input | INPUT, LINE INPUT, DATA/READ/RESTORE, INKEY$ | | Program | RUN, RUN "file", CONT, STOP, END, NEW, LIST, CLEAR | | Sequential I/O | OPEN, CLOSE, PRINT#, WRITE#, INPUT#, LINE INPUT# | | Random-access I/O | FIELD, LSET, RSET, PUT, GET, CVI/CVS/CVD, MKI$/MKS$/MKD$ | | Program I/O | SAVE, LOAD, MERGE, CHAIN | -| Error handling | ON ERROR GOTO, RESUME, ERROR | +| Error handling | ON ERROR GOTO, RESUME, ERROR, ERR, ERL | | User functions | DEF FN, RANDOMIZE | | File management | KILL, NAME | -| Screen | LOCATE, COLOR, WIDTH | +| Screen | LOCATE, COLOR, WIDTH, SCREEN | +| Graphics | PSET, PRESET, LINE, CIRCLE, DRAW, PAINT | -Graphics statements (SCREEN, PSET, LINE, CIRCLE, DRAW, PAINT, etc.) are -parsed but produce no output — programs that use them won't crash. +### Graphics + +Graphics mode is activated with `SCREEN 1` (320×200, 4 colors) or +`SCREEN 2` (640×200, monochrome). Drawing commands render to a virtual +framebuffer and output via [Sixel graphics](https://en.wikipedia.org/wiki/Sixel), +which works in terminals like xterm, mlterm, foot, and WezTerm. + +``` +SCREEN 1 +LINE (0,0)-(319,199), 1 +CIRCLE (160,100), 80, 2 +PAINT (160,100), 3, 2 +``` + +### Terminal I/O + +When stdin is a terminal, the interpreter enters raw mode for real-time +keyboard polling with `INKEY$` and character-at-a-time input with `INPUT$`. +Piped input is handled normally without raw mode. ## Architecture @@ -97,6 +115,7 @@ Source text → Tokenizer (CRUNCH) → Token stream | Tokenizer | tokenizer.c | GWMAIN.ASM | | Evaluator | eval.c | GWEVAL.ASM | | Interpreter | interp.c | BINTRP.ASM | +| Graphics | graphics.c | — | | Tokens | tokens.c | IBMRES.ASM | | Errors | error.c | GWDATA.ASM | | Math | math_int.c, math_float.c, math_transcend.c | MATH1/2.ASM | @@ -113,13 +132,10 @@ Key design differences from the original: ## Tests -27 test programs in `tests/programs/`: +39 test programs in `tests/programs/`, with CI via GitHub Actions: ```bash -for f in tests/programs/*.bas; do - echo "$(basename $f):" - timeout 5 ./build/gwbasic "$f" -done +bash tests/run_tests.sh ``` ## License diff --git a/src/eval.c b/src/eval.c index c9c6065..86962bd 100644 --- a/src/eval.c +++ b/src/eval.c @@ -833,7 +833,7 @@ static gw_value_t eval_atom(void) gw_chrget(); gw_value_t v; v.type = VT_INT; - v.ival = 0; + v.ival = gw.err_line_num; return v; } if (tok == TOK_ERR) { diff --git a/src/tokenizer.c b/src/tokenizer.c index 10d3bb0..39e1b38 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -137,6 +137,17 @@ int gw_crunch(const char *text, uint8_t *out, int outsize) in_data = 1; continue; } + + /* Not a keyword: copy entire variable name (letters + digits) */ + while (isalnum((unsigned char)text[ip]) || text[ip] == '.') { + out[op++] = text[ip++]; + if (op >= outsize - 2) break; + } + /* Copy type suffix if present */ + if (text[ip] == '%' || text[ip] == '!' || + text[ip] == '#' || text[ip] == '$') + out[op++] = text[ip++]; + continue; } /* &H hex, &O octal, & octal */ diff --git a/tests/programs/error_handler.bas b/tests/programs/error_handler.bas index d5ae1f1..d5aa82d 100644 --- a/tests/programs/error_handler.bas +++ b/tests/programs/error_handler.bas @@ -7,5 +7,5 @@ 70 X = 1 / 0 80 PRINT "After second resume" 90 PRINT "Error handler OK" : END -100 PRINT "Caught error"; ERR +100 PRINT "Caught error"; ERR; "at line"; ERL 110 RESUME NEXT