92 lines
3.2 KiB
C
92 lines
3.2 KiB
C
|
|
/**
|
||
|
|
* @file verbs_table.c
|
||
|
|
* @brief Tests the dispatch table's invariants.
|
||
|
|
*
|
||
|
|
* The sorting assertion is the important one: bsearch on a mis-sorted table
|
||
|
|
* silently fails to find a verb rather than producing a compile error, and the
|
||
|
|
* symptom would be "Unknown command PRINT" a long way from the cause.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/verbs.h>
|
||
|
|
|
||
|
|
#include "testutil.h"
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
const akbasic_Verb *table = NULL;
|
||
|
|
const akbasic_Verb *found = NULL;
|
||
|
|
int count = 0;
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(akbasic_error_register());
|
||
|
|
|
||
|
|
table = akbasic_verb_table(&count);
|
||
|
|
TEST_REQUIRE(table != NULL, "the verb table must exist");
|
||
|
|
TEST_REQUIRE(count > 0, "the verb table must not be empty");
|
||
|
|
|
||
|
|
/* Sorted, strictly: bsearch requires it and a duplicate name is a bug. */
|
||
|
|
for ( i = 1; i < count; i++ ) {
|
||
|
|
TEST_REQUIRE(strcmp(table[i - 1].name, table[i].name) < 0,
|
||
|
|
"table is not sorted at index %d: \"%s\" then \"%s\"",
|
||
|
|
i, table[i - 1].name, table[i].name);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Every row is reachable by its own name. */
|
||
|
|
for ( i = 0; i < count; i++ ) {
|
||
|
|
TEST_REQUIRE_OK(akbasic_verb_lookup(table[i].name, &found));
|
||
|
|
TEST_REQUIRE(found == &table[i], "row \"%s\" is not findable", table[i].name);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Verbs and function names are case-insensitive; variable names are not. */
|
||
|
|
TEST_REQUIRE_OK(akbasic_verb_lookup("print", &found));
|
||
|
|
TEST_REQUIRE(found != NULL && strcmp(found->name, "PRINT") == 0, "lowercase print missed");
|
||
|
|
TEST_REQUIRE_OK(akbasic_verb_lookup("PrInT", &found));
|
||
|
|
TEST_REQUIRE(found != NULL && strcmp(found->name, "PRINT") == 0, "mixed-case print missed");
|
||
|
|
|
||
|
|
/* A miss is a NULL, not an error. */
|
||
|
|
TEST_REQUIRE_OK(akbasic_verb_lookup("NOTAVERB", &found));
|
||
|
|
TEST_REQUIRE(found == NULL, "an unknown name must miss");
|
||
|
|
TEST_REQUIRE_OK(akbasic_verb_lookup("", &found));
|
||
|
|
TEST_REQUIRE(found == NULL, "an empty name must miss");
|
||
|
|
|
||
|
|
/* Every function row carries a real arity and a handler. */
|
||
|
|
for ( i = 0; i < count; i++ ) {
|
||
|
|
if ( table[i].tokentype != AKBASIC_TOK_FUNCTION ) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
TEST_REQUIRE(table[i].arity >= 1,
|
||
|
|
"function \"%s\" has arity %d", table[i].name, table[i].arity);
|
||
|
|
TEST_REQUIRE(table[i].exec != NULL,
|
||
|
|
"function \"%s\" has no exec handler", table[i].name);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The four rows with no exec handler are consumed by another verb's parse
|
||
|
|
* path and are never evaluated on their own. Any other missing handler is a
|
||
|
|
* verb that would report "Unknown command" at runtime.
|
||
|
|
*/
|
||
|
|
for ( i = 0; i < count; i++ ) {
|
||
|
|
if ( table[i].exec != NULL ) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
TEST_REQUIRE(strcmp(table[i].name, "AND") == 0 ||
|
||
|
|
strcmp(table[i].name, "ELSE") == 0 ||
|
||
|
|
strcmp(table[i].name, "NOT") == 0 ||
|
||
|
|
strcmp(table[i].name, "OR") == 0 ||
|
||
|
|
strcmp(table[i].name, "REM") == 0 ||
|
||
|
|
strcmp(table[i].name, "STEP") == 0 ||
|
||
|
|
strcmp(table[i].name, "THEN") == 0 ||
|
||
|
|
strcmp(table[i].name, "TO") == 0,
|
||
|
|
"verb \"%s\" has no exec handler and is not a known passive token",
|
||
|
|
table[i].name);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_verb_lookup(NULL, &found), AKERR_NULLPOINTER);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_verb_lookup("PRINT", NULL), AKERR_NULLPOINTER);
|
||
|
|
|
||
|
|
return akbasic_test_failures;
|
||
|
|
}
|