136 lines
8.1 KiB
C
136 lines
8.1 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 },
|
||
|
|
{ "ATN", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_atn },
|
||
|
|
{ "AUTO", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_auto },
|
||
|
|
{ "CHR", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_chr },
|
||
|
|
{ "COS", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_cos },
|
||
|
|
{ "DATA", AKBASIC_TOK_COMMAND, -1, akbasic_parse_data, akbasic_cmd_data },
|
||
|
|
{ "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 },
|
||
|
|
{ "DLOAD", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_dload },
|
||
|
|
{ "DSAVE", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_dsave },
|
||
|
|
{ "ELSE", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
|
||
|
|
{ "EXIT", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_exit },
|
||
|
|
{ "FOR", AKBASIC_TOK_COMMAND, -1, akbasic_parse_for, akbasic_cmd_for },
|
||
|
|
{ "GOSUB", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_gosub },
|
||
|
|
{ "GOTO", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_goto },
|
||
|
|
{ "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 },
|
||
|
|
{ "INSTR", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_instr },
|
||
|
|
{ "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 },
|
||
|
|
{ "LOG", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_log },
|
||
|
|
{ "MID", AKBASIC_TOK_FUNCTION, 3, NULL, akbasic_fn_mid },
|
||
|
|
{ "MOD", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_mod },
|
||
|
|
{ "NEXT", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_next },
|
||
|
|
{ "NOT", AKBASIC_TOK_NOT, -1, NULL, NULL },
|
||
|
|
{ "OR", AKBASIC_TOK_OR, -1, NULL, NULL },
|
||
|
|
{ "PEEK", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_peek },
|
||
|
|
{ "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, NULL, akbasic_cmd_print },
|
||
|
|
{ "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 },
|
||
|
|
{ "REM", AKBASIC_TOK_REM, -1, NULL, NULL },
|
||
|
|
{ "RETURN", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_return },
|
||
|
|
{ "RIGHT", AKBASIC_TOK_FUNCTION, 2, NULL, akbasic_fn_right },
|
||
|
|
{ "RUN", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_run },
|
||
|
|
{ "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 },
|
||
|
|
{ "SPC", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_spc },
|
||
|
|
{ "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 },
|
||
|
|
{ "TAN", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_tan },
|
||
|
|
{ "THEN", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
|
||
|
|
{ "TO", AKBASIC_TOK_COMMAND, -1, NULL, NULL },
|
||
|
|
{ "VAL", AKBASIC_TOK_FUNCTION, 1, NULL, akbasic_fn_val },
|
||
|
|
{ "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);
|
||
|
|
}
|