/**
 * @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);
    }

    /*
     * These rows have no exec handler because another verb's parse path consumes
     * them; they are never evaluated on their own. `WHILE` and `UNTIL` belong to
     * DO and LOOP the way `TO` and `STEP` belong to FOR, and `EACH`/`IN` belong
     * to FOR EACH and DO EACH the same way. 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, "EACH") == 0 ||
		     strcmp(table[i].name, "ELSE") == 0 ||
		     strcmp(table[i].name, "IN") == 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, "UNTIL") == 0 ||
		     strcmp(table[i].name, "USING") == 0 ||
		     strcmp(table[i].name, "WHILE") == 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;
}
