/**
 * @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 <akstdlib.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 },
    { "ASC",          AKBASIC_TOK_FUNCTION,              1, NULL,                  akbasic_fn_asc },
    { "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 },
    { "DIALOG",       AKBASIC_TOK_COMMAND,              -1, akbasic_parse_optional_arglist, akbasic_cmd_dialog },
    { "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 },
    /*
     * EACH is never dispatched on its own -- akbasic_parse_for() and
     * akbasic_parse_do() consume it directly, the same way TO, STEP, WHILE and
     * UNTIL are. It exists here only so the scanner gives it a COMMAND token
     * rather than letting it scan as a plain identifier.
     */
    { "EACH",         AKBASIC_TOK_COMMAND,              -1, NULL,                  NULL },
    { "ELSE",         AKBASIC_TOK_COMMAND,              -1, NULL,                  NULL },
    { "EMIT",         AKBASIC_TOK_COMMAND,              -1, NULL,                  akbasic_cmd_emit },
    { "END",          AKBASIC_TOK_COMMAND,              -1, akbasic_parse_end,     akbasic_cmd_end },
    /*
     * `END GEN` is never scanned as one token -- END and GEN are ordinary
     * COMMAND tokens on the same line -- so this row is reached only from
     * akbasic_parse_end(), which builds a leaf carrying this exact name after
     * it sees GEN follow END. It still has to be here, and in order, because
     * dispatch is a bsearch on the leaf's name; the same reason INPUT# and
     * PRINT# are.
     */
    { "END GEN",      AKBASIC_TOK_COMMAND,              -1, NULL,                  akbasic_cmd_end_gen },
    { "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 },
    { "GEN",          AKBASIC_TOK_COMMAND,              -1, akbasic_parse_gen,     akbasic_cmd_gen },
    { "GET",          AKBASIC_TOK_COMMAND,              -1, NULL,                  akbasic_cmd_get },
    { "GETKEY",       AKBASIC_TOK_COMMAND,              -1, NULL,                  akbasic_cmd_getkey },
    { "GETMENU",      AKBASIC_TOK_COMMAND,              -1, akbasic_parse_arglist, akbasic_cmd_getmenu },
    { "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_graphic, 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 },
    { "HUD",          AKBASIC_TOK_COMMAND,              -1, akbasic_parse_optional_arglist, akbasic_cmd_hud },
    { "IF",           AKBASIC_TOK_COMMAND,              -1, akbasic_parse_if,      akbasic_cmd_if },
    /* IN is consumed directly by akbasic_parse_for()/akbasic_parse_do()'s EACH
       clause, the same way EACH itself is. */
    { "IN",           AKBASIC_TOK_COMMAND,              -1, NULL,                  NULL },
    { "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 },
    { "MENU",         AKBASIC_TOK_COMMAND,              -1, akbasic_parse_optional_arglist, akbasic_cmd_menu },
    { "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 },
    { "POINT",        AKBASIC_TOK_COMMAND,              -1, akbasic_parse_point,   akbasic_cmd_point },
    { "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 },
    { "RCOLLISION",   AKBASIC_TOK_FUNCTION,              2, NULL,                  akbasic_fn_rcollision },
    { "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 },
    { "RMENU",        AKBASIC_TOK_FUNCTION,              2, NULL,                  akbasic_fn_rmenu },
    { "RND",          AKBASIC_TOK_FUNCTION,              1, NULL,                  akbasic_fn_rnd },
    { "RSPCOLOR",     AKBASIC_TOK_FUNCTION,              1, NULL,                  akbasic_fn_rspcolor },
    { "RSPHIT",       AKBASIC_TOK_FUNCTION,              2, NULL,                  akbasic_fn_rsphit },
    { "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 },
    { "RWINDOW",      AKBASIC_TOK_FUNCTION,              1, NULL,                  akbasic_fn_rwindow },
    { "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 },
    { "SOLID",        AKBASIC_TOK_COMMAND,              -1, akbasic_parse_optional_arglist, akbasic_cmd_solid },
    { "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 },
    { "SPRHIT",       AKBASIC_TOK_COMMAND,              -1, akbasic_parse_arglist, akbasic_cmd_sprhit },
    { "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 },
    { "TYPE",         AKBASIC_TOK_COMMAND,              -1, akbasic_parse_type,    akbasic_cmd_type },
    { "UISTYLE",      AKBASIC_TOK_COMMAND,              -1, akbasic_parse_optional_arglist, akbasic_cmd_uistyle },
    { "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])))

/**
 * @brief bsearch(3) comparator for the verb table.
 *
 * **This is the one call in `src/` that stays on raw `strcmp` on purpose.**
 * Everything else that reaches past libakstdlib to a function that library wraps
 * has been converted to the wrapper and propagates the error context; the ruling
 * that required it is libakstdlib #38, and the rule is that a function which
 * cannot report an error changes its own signature rather than swallowing one.
 *
 * That remedy is unavailable here, and only here. `bsearch(3)` fixes the
 * comparator's signature at `int (*)(const void *, const void *)`, so there is
 * no out-parameter to hand an `akerr_ErrorContext *` back through and no return
 * value that is not already the ordering. Converting this one site means
 * dropping `bsearch` for an in-house binary search that can propagate -- a
 * separate change to a lookup that runs once per scanned identifier, and a
 * decision that belongs in its own issue rather than riding along with a port.
 *
 * The bypass is safe as written: `key` is the caller's NUL-terminated `upper`
 * buffer and `element->name` is a string literal in the static table above, so
 * neither argument can be NULL and neither can be unterminated -- which is the
 * whole of what `aksl_strcmp` would have checked.
 */
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;
    PASS(errctx, aksl_strlen(name, &len));
    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);
}
