Files
akbasic/src/verbs.c
Andrew Kesterson 5c5bf63356 Draw into the whole window, not its top-left 320x200
The graphics verbs documented a coordinate transform that did not exist. With
SCALE off a coordinate went straight to akgl_draw_* as a pixel address, so an
800x600 window drew a C128 listing into its corner and left the rest unused --
while the chapter said coordinates were 320x200 and stretching to fit was the
host's business.

akbasic_GraphicsBackend gains a size entry point, require_graphics() asks it
before every verb that draws so a resized window is honoured between two
statements, and 320x200 becomes the fallback for a backend that leaves it NULL.
It is the record's one optional member, so a host written against the old header
keeps the behaviour it had.

SCALE now maps onto the device, and RGR(1)/RGR(2) report the drawing surface so
a program can use a window whose size it did not choose. RGR(0) is BASIC 7.0's
own field, the GRAPHIC mode.

SCALE also mapped xmax onto the width rather than onto the last pixel, so
SCALE 1, 319, 199 followed by DRAW 1, 319, 199 drew nothing at all -- one pixel
past the surface. Fixed in the same line, because it is what makes "SCALE gives
a C128 listing the whole window" true rather than nearly true.

The akgl test renders against a 128x128 target, deliberately smaller than the
old constants: a SCALE still dividing by them misses it entirely rather than
landing somewhere plausible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:35:20 -04:00

226 lines
17 KiB
C

/**
* @file verbs.c
* @brief The dispatch table: every verb, function and reserved word in one place.
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <akerror.h>
#include <akbasic/error.h>
#include <akbasic/verbs.h>
#include "verbs.h"
/*
* Sorted by name so bsearch() is valid -- tests/verbs_table.c asserts the
* ordering, because a mis-sorted row silently becomes an unfindable verb rather
* than a compile error.
*
* A NULL parse handler means the rval parses as a plain expression. A NULL exec
* handler means the token is consumed by some other verb's parse handler and is
* never evaluated on its own: ELSE, STEP, THEN and TO are all in that class, as
* are the four reserved words, which exist here only so the scanner can give
* them their token type.
*
* The reference bootstraps MOD, SPC and STR by running a BASIC program of DEF
* statements through the interpreter at startup and keeping their expressions
* (basicruntime_functions.go:14). That hack is not reproduced: they are ordinary
* native handlers here, which removes the need to run the interpreter before the
* interpreter is ready.
*/
static const akbasic_Verb VERBS[] = {
/* name token type arity parse handler exec handler */
{ "ABS", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_abs },
{ "AND", AKBASIC_TOK_AND, -1, NULL, NULL },
{ "APPEND", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_append },
{ "ATN", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_atn },
{ "AUTO", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_auto },
{ "BACKUP", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_backup },
{ "BEGIN", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_begin },
{ "BEND", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_bend },
{ "BLOAD", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_bload },
{ "BOOT", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_boot },
{ "BOX", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_box },
{ "BSAVE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_bsave },
{ "BUMP", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_bump },
{ "CATALOG", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, akbasic_parse_optional_arglist, akbasic_cmd_directory },
{ "CHAR", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_char },
{ "CHR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_chr },
{ "CIRCLE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_circle },
{ "CLR", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_clr },
{ "COLLECT", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_collect },
{ "COLLISION", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_collision },
{ "COLOR", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_color },
{ "CONCAT", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_concat },
{ "CONT", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_cont },
{ "COPY", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_copy },
{ "COS", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_cos },
{ "DATA", AKBASIC_TOK_COMMAND, -1, akbasic_parse_data, akbasic_cmd_data },
{ "DCLEAR", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_dclear },
{ "DCLOSE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_dclose },
{ "DEF", AKBASIC_TOK_COMMAND, -1, akbasic_parse_def, akbasic_cmd_def },
{ "DELETE", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_delete },
{ "DIM", AKBASIC_TOK_COMMAND, -1, akbasic_parse_dim, akbasic_cmd_dim },
{ "DIRECTORY", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, akbasic_parse_optional_arglist, akbasic_cmd_directory },
{ "DLOAD", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_dload },
{ "DO", AKBASIC_TOK_COMMAND, -1, akbasic_parse_do, akbasic_cmd_do },
{ "DOPEN", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_dopen },
{ "DRAW", AKBASIC_TOK_COMMAND, -1, akbasic_parse_draw, akbasic_cmd_draw },
{ "DSAVE", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_dsave },
{ "DVERIFY", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, akbasic_parse_arglist, akbasic_cmd_dverify },
{ "ELSE", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
{ "END", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_end },
{ "ENVELOPE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_envelope },
{ "ERR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_err },
{ "EXIT", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_exit },
{ "FETCH", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_fetch },
{ "FILTER", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_filter },
{ "FOR", AKBASIC_TOK_COMMAND, -1, akbasic_parse_for, akbasic_cmd_for },
{ "GET", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_get },
{ "GETKEY", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_getkey },
{ "GOSUB", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_gosub },
{ "GOTO", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_goto },
{ "GRAPHIC", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_graphic },
{ "GSHAPE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_gshape },
{ "HEADER", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_header },
{ "HELP", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_help },
{ "HEX", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_hex },
{ "IF", AKBASIC_TOK_COMMAND, -1, akbasic_parse_if, akbasic_cmd_if },
{ "INPUT", AKBASIC_TOK_COMMAND, -1, akbasic_parse_input, akbasic_cmd_input },
/*
* `INPUT#` and `PRINT#` are never scanned as verb names -- the scanner reads
* a `#` as a type suffix and refuses a verb carrying one -- so these rows are
* reached only from akbasic_parse_input() and akbasic_parse_print(), which
* build a leaf with the name directly. They still have to be here, and in
* order, because dispatch is a bsearch on the leaf's name.
*/
{ "INPUT#", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_input_channel },
{ "INSTR", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_instr },
{ "KEY", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_key },
{ "LABEL", AKBASIC_TOK_COMMAND, -1, akbasic_parse_label, akbasic_cmd_label },
{ "LEFT", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_left },
{ "LEN", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_len },
{ "LET", AKBASIC_TOK_COMMAND, -1, akbasic_parse_let, akbasic_cmd_let },
{ "LIST", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_list },
{ "LOAD", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_dload },
{ "LOCATE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_locate },
{ "LOG", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_log },
{ "LOOP", AKBASIC_TOK_COMMAND, -1, akbasic_parse_loop, akbasic_cmd_loop },
{ "MID", AKBASIC_TOK_FUNCTION, 3, NULL, akbasic_fn_mid },
{ "MOD", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_mod },
{ "MOVSPR", AKBASIC_TOK_COMMAND, -1, akbasic_parse_movspr, akbasic_cmd_movspr },
{ "NEW", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_new },
{ "NEXT", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_next },
{ "NOT", AKBASIC_TOK_NOT, -1, NULL, NULL },
{ "ON", AKBASIC_TOK_COMMAND, -1, akbasic_parse_on, akbasic_cmd_on },
{ "OR", AKBASIC_TOK_OR, -1, NULL, NULL },
{ "PAINT", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_paint },
{ "PEEK", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_peek },
{ "PLAY", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_play },
{ "POINTER", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_pointer },
{ "POINTERVAR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_pointervar },
{ "POKE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_poke, akbasic_cmd_poke },
{ "PRINT", AKBASIC_TOK_COMMAND, -1, akbasic_parse_print, akbasic_cmd_print },
{ "PRINT#", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_print_channel },
{ "PUDEF", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_pudef },
{ "QUIT", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_quit },
{ "RAD", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_rad },
{ "READ", AKBASIC_TOK_COMMAND, -1, akbasic_parse_read, akbasic_cmd_read },
{ "RECORD", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_record },
{ "REM", AKBASIC_TOK_REM, -1, NULL, NULL },
{ "RENAME", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_rename },
{ "RENUMBER", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, akbasic_parse_optional_arglist, akbasic_cmd_renumber },
{ "RESTORE", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_restore },
{ "RESUME", AKBASIC_TOK_COMMAND, -1, akbasic_parse_resume, akbasic_cmd_resume },
{ "RETURN", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_return },
{ "RGR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_rgr },
{ "RIGHT", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_right },
{ "RSPCOLOR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_rspcolor },
{ "RSPPOS", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_rsppos },
{ "RSPRITE", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_rsprite },
{ "RUN", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_run },
{ "SAVE", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_dsave },
{ "SCALE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_scale },
{ "SCNCLR", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_scnclr },
{ "SCRATCH", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_scratch },
{ "SGN", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_sgn },
{ "SHL", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_shl },
{ "SHR", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_shr },
{ "SIN", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_sin },
{ "SLEEP", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_sleep },
{ "SOUND", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_sound },
{ "SPC", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_spc },
{ "SPRCOLOR", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_sprcolor },
{ "SPRITE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_sprite },
{ "SPRSAV", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_sprsav },
{ "SSHAPE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_sshape },
{ "STASH", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_stash },
{ "STEP", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
{ "STOP", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_stop },
{ "STR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_str },
{ "SWAP", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_swap },
{ "SYS", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_sys },
{ "TAN", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_tan },
{ "TEMPO", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_tempo },
{ "THEN", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
{ "TO", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
{ "TRAP", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_trap },
{ "TROFF", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_troff },
{ "TRON", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_tron },
{ "UNTIL", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
{ "USING", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
{ "VAL", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_val },
{ "VERIFY", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, akbasic_parse_arglist, akbasic_cmd_dverify },
{ "VOL", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_vol },
{ "WAIT", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_wait },
{ "WHILE", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
{ "WIDTH", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_width },
{ "WINDOW", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_window },
{ "XOR", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_xor },
};
#define VERB_COUNT ((int)(sizeof(VERBS) / sizeof(VERBS[0])))
static int verb_compare(const void *key, const void *element)
{
const akbasic_Verb *verb = (const akbasic_Verb *)element;
return strcmp((const char *)key, verb->name);
}
const akbasic_Verb *akbasic_verb_table(int *count)
{
if ( count != NULL ) {
*count = VERB_COUNT;
}
return VERBS;
}
akerr_ErrorContext *akbasic_verb_lookup(const char *name, const akbasic_Verb **dest)
{
PREPARE_ERROR(errctx);
char upper[AKBASIC_MAX_STRING_LENGTH];
size_t i = 0;
size_t len = 0;
FAIL_ZERO_RETURN(errctx, (name != NULL), AKERR_NULLPOINTER, "NULL name in verb lookup");
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in verb lookup");
*dest = NULL;
len = strlen(name);
if ( len == 0 || len >= sizeof(upper) ) {
/* Too long to be any verb we know. Not an error, just a miss. */
SUCCEED_RETURN(errctx);
}
for ( i = 0; i < len; i++ ) {
upper[i] = (char)toupper((unsigned char)name[i]);
}
upper[len] = '\0';
*dest = (const akbasic_Verb *)bsearch(upper, VERBS, VERB_COUNT, sizeof(VERBS[0]), verb_compare);
SUCCEED_RETURN(errctx);
}