/**
 * @file parser.c
 * @brief Implements the recursive-descent parser.
 *
 * A faithful port of basicparser.go, with two changes. The reflection lookup for
 * a verb's special parse path becomes a table lookup, and the debug.PrintStack()
 * the reference calls on a parse failure is gone: an interpreter library does not
 * dump the host's stack to stderr, and the akerr stack trace already carries what
 * that call was for.
 */

#include <inttypes.h>

#include <akerror.h>
#include <akstdlib.h>

#include <akbasic/error.h>
#include <akbasic/parser.h>
#include <akbasic/verbs.h>

akerr_ErrorContext *akbasic_parser_init(akbasic_Parser *obj, akbasic_Runtime *runtime)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL parser in init");
    FAIL_ZERO_RETURN(errctx, (runtime != NULL), AKERR_NULLPOINTER, "nil runtime argument");
    obj->runtime = runtime;
    obj->comparing = false;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_parser_zero(akbasic_Parser *obj)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "nil self reference!");
    PASS(errctx, akbasic_environment_zero_parser(obj->runtime->environment));
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_parser_new_leaf(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);

    PASS(errctx, akbasic_environment_new_leaf(obj->runtime->environment, dest));
    SUCCEED_RETURN(errctx);
}

bool akbasic_parser_is_at_end(akbasic_Parser *obj)
{
    akbasic_Environment *env = NULL;

    if ( obj == NULL || obj->runtime == NULL || obj->runtime->environment == NULL ) {
	return true;
    }
    env = obj->runtime->environment;
    return (env->curtoken >= (AKBASIC_MAX_TOKENS - 1) || env->curtoken >= env->nexttoken);
}

akbasic_Token *akbasic_parser_peek(akbasic_Parser *obj)
{
    if ( akbasic_parser_is_at_end(obj) ) {
	return NULL;
    }
    return &obj->runtime->environment->tokens[obj->runtime->environment->curtoken];
}

akerr_ErrorContext *akbasic_parser_previous(akbasic_Parser *obj, akbasic_Token **dest)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
		     "NULL argument in previous");
    FAIL_ZERO_RETURN(errctx, (obj->runtime->environment->curtoken != 0), AKBASIC_ERR_SYNTAX,
		     "Current token is index 0, no previous token");
    *dest = &obj->runtime->environment->tokens[obj->runtime->environment->curtoken - 1];
    SUCCEED_RETURN(errctx);
}

static bool check(akbasic_Parser *obj, akbasic_TokenType tokentype)
{
    akbasic_Token *next = NULL;

    if ( akbasic_parser_is_at_end(obj) ) {
	return false;
    }
    next = akbasic_parser_peek(obj);
    return (next != NULL && next->tokentype == tokentype);
}

static void advance(akbasic_Parser *obj)
{
    if ( !akbasic_parser_is_at_end(obj) ) {
	obj->runtime->environment->curtoken += 1;
    }
}

bool akbasic_parser_match(akbasic_Parser *obj, const akbasic_TokenType *types, int count)
{
    int i = 0;

    for ( i = 0; i < count; i++ ) {
	if ( check(obj, types[i]) ) {
	    advance(obj);
	    return true;
	}
    }
    return false;
}

bool akbasic_parser_match1(akbasic_Parser *obj, akbasic_TokenType type)
{
    return akbasic_parser_match(obj, &type, 1);
}

akerr_ErrorContext *akbasic_parser_error(akbasic_Parser *obj, const char *message)
{
    PREPARE_ERROR(errctx);
    akbasic_Token *token = NULL;

    token = akbasic_parser_peek(obj);
    obj->runtime->environment->errorToken = token;
    FAIL_ZERO_RETURN(errctx, (token != NULL), AKBASIC_ERR_SYNTAX, "peek() returned nil token!");
    if ( token->tokentype == AKBASIC_TOK_EOF ) {
	FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "%" PRId64 " at end %s", token->lineno, message);
    }
    FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "%" PRId64 " at '%s', %s",
		token->lineno, token->lexeme, message);
}

static akerr_ErrorContext *consume(akbasic_Parser *obj, akbasic_TokenType tokentype, const char *message)
{
    PREPARE_ERROR(errctx);

    if ( check(obj, tokentype) ) {
	advance(obj);
	SUCCEED_RETURN(errctx);
    }
    PASS(errctx, akbasic_parser_error(obj, message));
    SUCCEED_RETURN(errctx);
}

/* Forward declarations for the grammar chain. */
static akerr_ErrorContext *logicalandor(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *logicalnot(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *subtraction(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *addition(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *multiplication(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *division(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *unary(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *exponent(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *function_call(akbasic_Parser *obj, akbasic_ASTLeaf **dest);

bool akbasic_parser_skip_separators(akbasic_Parser *obj)
{
    while ( akbasic_parser_match1(obj, AKBASIC_TOK_COLON) ) {
	/*
	 * A run of them, so `10 PRINT 1 :: PRINT 2` and a trailing colon both
	 * work. An empty statement is not an error on a C128 and is not one
	 * here.
	 */
    }
    return !akbasic_parser_is_at_end(obj);
}

akerr_ErrorContext *akbasic_parser_parse(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
		     "NULL argument in parse");
    *dest = NULL;
    /*
     * Statements are separated by colons. The scanner has emitted the token
     * since the port began and nothing consumed it, so a line with two
     * statements on it was a parse error -- TODO.md section 4.
     *
     * Nothing after the separators is a line that ended in one. That is not an
     * error, so the caller gets a NULL leaf and skips it.
     */
    if ( !akbasic_parser_skip_separators(obj) ) {
	SUCCEED_RETURN(errctx);
    }
    PASS(errctx, akbasic_parser_command(obj, dest));
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_parser_command(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    static const akbasic_TokenType COMMANDS[] = {
	AKBASIC_TOK_COMMAND, AKBASIC_TOK_COMMAND_IMMEDIATE
    };
    akbasic_ASTLeaf *expr = NULL;
    akbasic_ASTLeaf *right = NULL;
    akbasic_Token *operator_ = NULL;
    akbasic_Token *righttoken = NULL;
    const akbasic_Verb *verb = NULL;
    akbasic_TokenType optype = AKBASIC_TOK_UNDEFINED;
    char opname[AKBASIC_MAX_LINE_LENGTH];

    if ( !akbasic_parser_match(obj, COMMANDS, 2) ) {
	PASS(errctx, akbasic_parser_assignment(obj, dest));
	SUCCEED_RETURN(errctx);
    }

    PASS(errctx, akbasic_parser_previous(obj, &operator_));
    optype = operator_->tokentype;
    PASS(errctx, aksl_strcpy(opname, sizeof(opname), operator_->lexeme));

    /* Does this verb need its own parse path? */
    PASS(errctx, akbasic_verb_lookup(opname, &verb));
    if ( verb != NULL && verb->parse != NULL ) {
	PASS(errctx, verb->parse(obj, dest));
	SUCCEED_RETURN(errctx);
    }

    /*
     * Some verbs take no rval. Do not fail when there is not one -- but do fail
     * when there is one and it will not parse.
     */
    righttoken = akbasic_parser_peek(obj);
    if ( righttoken != NULL && righttoken->tokentype != AKBASIC_TOK_UNDEFINED &&
	 righttoken->tokentype != AKBASIC_TOK_COLON ) {
	/*
	 * A colon ends the statement, so `PRINT : PRINT "X"` is a bare PRINT
	 * followed by another statement rather than a PRINT of whatever a colon
	 * evaluates to.
	 */
	PASS(errctx, akbasic_parser_expression(obj, &right));
    }

    PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
    if ( optype == AKBASIC_TOK_COMMAND_IMMEDIATE ) {
	PASS(errctx, akbasic_leaf_new_immediate_command(expr, opname, right));
    } else {
	PASS(errctx, akbasic_leaf_new_command(expr, opname, right));
    }
    *dest = expr;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_parser_assignment(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *identifier = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_ASTLeaf *right = NULL;

    PASS(errctx, akbasic_parser_expression(obj, &identifier));
    /*
	 * A field access is assignable too, and it is not an identifier: the
	 * name on the left of the = is reached by walking a chain rather than by
	 * looking one name up. Both spellings continue into the = below.
	 */
    if ( identifier == NULL ||
	 (identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_INT &&
	  identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_FLOAT &&
	  identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_STRING &&
	  identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_STRUCT &&
	  identifier->leaftype != AKBASIC_LEAF_FIELD) ) {
	*dest = identifier;
	SUCCEED_RETURN(errctx);
    }
    if ( akbasic_parser_match1(obj, AKBASIC_TOK_ASSIGNMENT) ) {
	PASS(errctx, akbasic_parser_expression(obj, &right));
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_binary(expr, identifier, AKBASIC_TOK_ASSIGNMENT, right));
	*dest = expr;
	SUCCEED_RETURN(errctx);
    }
    *dest = identifier;
    SUCCEED_RETURN(errctx);
}

/*
 * An argument list is just .right-joined expressions continuing ad infinitum.
 * When requireparens is false and there is no opening paren, this still builds a
 * list -- that is how DATA and READ take a bare comma-separated series.
 */
akerr_ErrorContext *akbasic_parser_argument_list(akbasic_Parser *obj, akbasic_TokenType arglisttype, bool requireparens, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *arglist = NULL;
    akbasic_ASTLeaf *expr = NULL;

    *dest = NULL;
    if ( !akbasic_parser_match1(obj, AKBASIC_TOK_LEFT_PAREN) && requireparens ) {
	SUCCEED_RETURN(errctx);
    }
    FAIL_ZERO_RETURN(errctx,
		     (arglisttype == AKBASIC_TOK_ARRAY_SUBSCRIPT ||
		      arglisttype == AKBASIC_TOK_FUNCTION_ARGUMENT),
		     AKBASIC_ERR_SYNTAX,
		     "argumentList expects argListType [ARRAY_SUBSCRIPT || FUNCTION_ARGUMENT]");

    PASS(errctx, akbasic_parser_new_leaf(obj, &arglist));
    arglist->leaftype = AKBASIC_LEAF_ARGUMENTLIST;
    arglist->operator_ = arglisttype;
    PASS(errctx, akbasic_parser_expression(obj, &arglist->right));

    /*
     * Siblings chain through .next, never through .right. Every leaf type that
     * can be an argument already uses .right for something of its own -- a
     * unary's operand, a binary's right-hand side -- and reusing it here is what
     * made ABS(-9) count as two arguments. See akbasic_ASTLeaf.next.
     */
    expr = arglist->right;
    while ( expr != NULL && akbasic_parser_match1(obj, AKBASIC_TOK_COMMA) ) {
	PASS(errctx, akbasic_parser_expression(obj, &expr->next));
	expr = expr->next;
    }
    if ( !akbasic_parser_match1(obj, AKBASIC_TOK_RIGHT_PAREN) && requireparens ) {
	FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "Unbalanced parenthesis");
    }
    *dest = arglist;
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_parser_expression(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);

    PASS(errctx, logicalandor(obj, dest));
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext *logicalandor(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    static const akbasic_TokenType OPS[] = { AKBASIC_TOK_AND, AKBASIC_TOK_OR };
    akbasic_ASTLeaf *left = NULL;
    akbasic_ASTLeaf *right = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_Token *operator_ = NULL;

    PASS(errctx, logicalnot(obj, &left));
    if ( akbasic_parser_match(obj, OPS, 2) ) {
	PASS(errctx, akbasic_parser_previous(obj, &operator_));
	PASS(errctx, logicalnot(obj, &right));
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
	*dest = expr;
	SUCCEED_RETURN(errctx);
    }
    *dest = left;
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext *logicalnot(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *right = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_Token *operator_ = NULL;

    if ( akbasic_parser_match1(obj, AKBASIC_TOK_NOT) ) {
	PASS(errctx, akbasic_parser_previous(obj, &operator_));
	PASS(errctx, akbasic_parser_relation(obj, &right));
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_unary(expr, operator_->tokentype, right));
	*dest = expr;
	SUCCEED_RETURN(errctx);
    }
    PASS(errctx, akbasic_parser_relation(obj, dest));
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_parser_relation(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    /*
     * A lone `=` is an equality test here when the caller has said a condition
     * is what it is parsing. `==` keeps working either way, which is what the Go
     * reference used and what the whole checked-in corpus is written in. See
     * akbasic_Parser::comparing and TODO.md section 5.
     */
    static const akbasic_TokenType OPS[] = {
	AKBASIC_TOK_LESS_THAN, AKBASIC_TOK_LESS_THAN_EQUAL, AKBASIC_TOK_EQUAL,
	AKBASIC_TOK_NOT_EQUAL, AKBASIC_TOK_GREATER_THAN, AKBASIC_TOK_GREATER_THAN_EQUAL,
	AKBASIC_TOK_ASSIGNMENT
    };
    akbasic_ASTLeaf *left = NULL;
    akbasic_ASTLeaf *right = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_Token *operator_ = NULL;
    akbasic_TokenType op = AKBASIC_TOK_UNDEFINED;
    /*
     * ASSIGNMENT is the last entry, and it is only offered while `comparing` --
     * see akbasic_Parser. Outside a condition `=` has to stay an assignment, or
     * `A# = 2` stops storing anything and `FOR I# = 1 TO 5` never initializes
     * its counter.
     */
    int opcount = (obj->comparing ? 7 : 6);

    PASS(errctx, subtraction(obj, &left));
    if ( akbasic_parser_match(obj, OPS, opcount) ) {
	PASS(errctx, akbasic_parser_previous(obj, &operator_));
	op = operator_->tokentype;
	if ( op == AKBASIC_TOK_ASSIGNMENT ) {
	    op = AKBASIC_TOK_EQUAL;
	}
	PASS(errctx, subtraction(obj, &right));
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_binary(expr, left, op, right));
	*dest = expr;
	SUCCEED_RETURN(errctx);
    }
    *dest = left;
    SUCCEED_RETURN(errctx);
}

/*
 * Loops, where the reference returns after one operator (basicparser.go:329).
 * That asymmetry was reproduced faithfully at first and is TODO.md section 6
 * item 12: `1 - 2 - 3` computed `1 - 2` and abandoned the rest of the line,
 * which the statement loop then picked up as a second statement and discarded.
 * A wrong answer, silently -- the highest-impact item on that list.
 *
 * Left-associative, matching addition, multiplication and division here and
 * matching a C128, which evaluates a run of same-precedence operators left to
 * right.
 */
static akerr_ErrorContext *subtraction(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *left = NULL;
    akbasic_ASTLeaf *right = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_Token *operator_ = NULL;

    PASS(errctx, addition(obj, &left));
    while ( akbasic_parser_match1(obj, AKBASIC_TOK_MINUS) ) {
	PASS(errctx, akbasic_parser_previous(obj, &operator_));
	PASS(errctx, addition(obj, &right));
	if ( expr != NULL ) {
	    left = expr;
	}
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
    }
    *dest = (expr != NULL ? expr : left);
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext *addition(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *left = NULL;
    akbasic_ASTLeaf *right = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_Token *operator_ = NULL;

    PASS(errctx, multiplication(obj, &left));
    while ( akbasic_parser_match1(obj, AKBASIC_TOK_PLUS) ) {
	PASS(errctx, akbasic_parser_previous(obj, &operator_));
	PASS(errctx, multiplication(obj, &right));
	if ( expr != NULL ) {
	    left = expr;
	}
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
    }
    *dest = (expr != NULL ? expr : left);
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext *multiplication(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *left = NULL;
    akbasic_ASTLeaf *right = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_Token *operator_ = NULL;

    PASS(errctx, division(obj, &left));
    while ( akbasic_parser_match1(obj, AKBASIC_TOK_STAR) ) {
	PASS(errctx, akbasic_parser_previous(obj, &operator_));
	PASS(errctx, division(obj, &right));
	if ( expr != NULL ) {
	    left = expr;
	}
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
    }
    *dest = (expr != NULL ? expr : left);
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext *division(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *left = NULL;
    akbasic_ASTLeaf *right = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_Token *operator_ = NULL;

    PASS(errctx, unary(obj, &left));
    while ( akbasic_parser_match1(obj, AKBASIC_TOK_LEFT_SLASH) ) {
	PASS(errctx, akbasic_parser_previous(obj, &operator_));
	PASS(errctx, unary(obj, &right));
	if ( expr != NULL ) {
	    left = expr;
	}
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
    }
    *dest = (expr != NULL ? expr : left);
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext *unary(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *right = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_Token *operator_ = NULL;

    if ( akbasic_parser_match1(obj, AKBASIC_TOK_MINUS) ) {
	PASS(errctx, akbasic_parser_previous(obj, &operator_));
	PASS(errctx, akbasic_parser_primary(obj, &right));
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_unary(expr, operator_->tokentype, right));
	*dest = expr;
	SUCCEED_RETURN(errctx);
    }
    PASS(errctx, exponent(obj, dest));
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext *exponent(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *left = NULL;
    akbasic_ASTLeaf *right = NULL;
    akbasic_ASTLeaf *expr = NULL;
    akbasic_Token *operator_ = NULL;

    PASS(errctx, function_call(obj, &left));
    /*
     * Loops for the same reason subtraction does -- `2 ^ 3 ^ 2` abandoned the
     * second operator before this. Left-associative, which is a C128: 2^3^2 is
     * 64 there, not the 512 a right-associative reading gives.
     */
    while ( akbasic_parser_match1(obj, AKBASIC_TOK_CARAT) ) {
	PASS(errctx, akbasic_parser_previous(obj, &operator_));
	PASS(errctx, function_call(obj, &right));
	if ( expr != NULL ) {
	    left = expr;
	}
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
    }
    *dest = (expr != NULL ? expr : left);
    SUCCEED_RETURN(errctx);
}

/* Count the arguments hanging off an argument list. */
static int arglist_length(akbasic_ASTLeaf *arglist)
{
    akbasic_ASTLeaf *leaf = NULL;
    int count = 0;

    if ( arglist == NULL ) {
	return 0;
    }
    for ( leaf = arglist->right; leaf != NULL; leaf = leaf->next ) {
	count += 1;
    }
    return count;
}

/*
 * Called for function *calls* only, never for a DEF. A FUNCTION token is either
 * a table builtin or a user DEF; both check their argument count here, so a
 * wrong-arity call is a parse error rather than a runtime surprise.
 */
static akerr_ErrorContext *function_call(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_ASTLeaf *arglist = NULL;
    akbasic_ASTLeaf *leaf = NULL;
    akbasic_Token *operator_ = NULL;
    const akbasic_Verb *verb = NULL;
    akbasic_FunctionDef *fndef = NULL;
    void *fnptr = NULL;
    char fname[AKBASIC_MAX_LINE_LENGTH];
    int wanted = 0;
    int given = 0;
    bool found = false;

    if ( !akbasic_parser_match1(obj, AKBASIC_TOK_FUNCTION) ) {
	PASS(errctx, akbasic_parser_primary(obj, dest));
	SUCCEED_RETURN(errctx);
    }

    PASS(errctx, akbasic_parser_previous(obj, &operator_));
    PASS(errctx, aksl_strcpy(fname, sizeof(fname), operator_->lexeme));

    PASS(errctx, akbasic_verb_lookup(fname, &verb));
    if ( verb != NULL && verb->tokentype == AKBASIC_TOK_FUNCTION ) {
	wanted = verb->arity;
	found = true;
    } else {
	ATTEMPT {
	    CATCH(errctx, akbasic_environment_get_function(obj->runtime->environment, fname, &fnptr));
	    found = true;
	} CLEANUP {
	} PROCESS(errctx) {
	} HANDLE(errctx, AKERR_KEY) {
	    found = false;
	} FINISH(errctx, true);
	if ( found ) {
	    fndef = (akbasic_FunctionDef *)fnptr;
	    wanted = arglist_length(fndef->arglist);
	}
    }
    FAIL_ZERO_RETURN(errctx, found, AKBASIC_ERR_UNDEFINED, "No such function %s", fname);

    PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_FUNCTION_ARGUMENT, true, &arglist));
    given = arglist_length(arglist);
    FAIL_ZERO_RETURN(errctx, (given == wanted), AKBASIC_ERR_SYNTAX,
		     "function %s takes %d arguments, received %d", fname, wanted, given);

    PASS(errctx, akbasic_parser_new_leaf(obj, &leaf));
    PASS(errctx, akbasic_leaf_new_function(leaf, fname, arglist));
    *dest = leaf;
    SUCCEED_RETURN(errctx);
}

/**
 * @brief Consume a run of `.field` and `->field` after a primary.
 *
 * Left-associative, so `A@.B@.C#` reads as `(A@.B@).C#` and each step's base is
 * the leaf before it. The two operators are kept apart all the way to the
 * runtime rather than folded into one "field access", because which one was
 * written is the whole of the strict-pointer rule: `.` requires a structure and
 * `->` requires a pointer to one, and getting it wrong is an error rather than a
 * convenience the interpreter silently absorbs.
 */
static akerr_ErrorContext *parse_field_chain(akbasic_Parser *obj, akbasic_ASTLeaf **expr)
{
    PREPARE_ERROR(errctx);
    static const akbasic_TokenType ACCESSORS[] = { AKBASIC_TOK_DOT, AKBASIC_TOK_ARROW };
    /*
     * A field name carries its own type suffix, exactly as a variable does, so
     * the four suffixed identifier tokens are the whole set. A bare identifier
     * is not among them: that spelling is a label everywhere else and a field
     * with no suffix would have no type.
     */
    static const akbasic_TokenType FIELDNAMES[] = {
	AKBASIC_TOK_IDENTIFIER_INT, AKBASIC_TOK_IDENTIFIER_FLOAT,
	AKBASIC_TOK_IDENTIFIER_STRING, AKBASIC_TOK_IDENTIFIER_STRUCT
    };
    akbasic_ASTLeaf *field = NULL;
    akbasic_Token *accessor = NULL;
    akbasic_Token *name = NULL;
    akbasic_TokenType op = AKBASIC_TOK_DOT;

    while ( akbasic_parser_match(obj, ACCESSORS, 2) ) {
	PASS(errctx, akbasic_parser_previous(obj, &accessor));
	op = accessor->tokentype;
	FAIL_ZERO_RETURN(errctx, akbasic_parser_match(obj, FIELDNAMES, 4), AKBASIC_ERR_SYNTAX,
			 "Expected a field name after %s", (op == AKBASIC_TOK_ARROW ? "->" : "."));
	PASS(errctx, akbasic_parser_previous(obj, &name));
	PASS(errctx, akbasic_parser_new_leaf(obj, &field));
	PASS(errctx, akbasic_leaf_new_field(field, *expr, name->lexeme, op));
	/*
	 * A field may itself be an array element -- `E@.DROPS#(2)` -- and the
	 * subscript list belongs to the field, not to the base, so it is
	 * collected here and onto the field leaf's own `.expr`.
	 */
	PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &field->expr));
	*expr = field;
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_parser_primary(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    static const akbasic_TokenType PRIMARIES[] = {
	AKBASIC_TOK_LITERAL_INT, AKBASIC_TOK_LITERAL_FLOAT, AKBASIC_TOK_LITERAL_STRING,
	AKBASIC_TOK_IDENTIFIER, AKBASIC_TOK_IDENTIFIER_STRING, AKBASIC_TOK_IDENTIFIER_FLOAT,
	AKBASIC_TOK_IDENTIFIER_INT, AKBASIC_TOK_FUNCTION, AKBASIC_TOK_IDENTIFIER_STRUCT
    };
    akbasic_ASTLeaf *expr = NULL;
    akbasic_ASTLeaf *groupexpr = NULL;
    akbasic_Token *previous = NULL;

    if ( akbasic_parser_match(obj, PRIMARIES, 9) ) {
	PASS(errctx, akbasic_parser_previous(obj, &previous));
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	switch ( previous->tokentype ) {
	case AKBASIC_TOK_LITERAL_INT:
	    PASS(errctx, akbasic_leaf_new_literal_int(expr, previous->lexeme));
	    break;
	case AKBASIC_TOK_LITERAL_FLOAT:
	    PASS(errctx, akbasic_leaf_new_literal_float(expr, previous->lexeme));
	    break;
	case AKBASIC_TOK_LITERAL_STRING:
	    PASS(errctx, akbasic_leaf_new_literal_string(expr, previous->lexeme));
	    break;
	case AKBASIC_TOK_IDENTIFIER_INT:
	    PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_INT, previous->lexeme));
	    PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
	    break;
	case AKBASIC_TOK_IDENTIFIER_FLOAT:
	    PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_FLOAT, previous->lexeme));
	    PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
	    break;
	case AKBASIC_TOK_IDENTIFIER_STRING:
	    PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_STRING, previous->lexeme));
	    PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
	    break;
	case AKBASIC_TOK_IDENTIFIER_STRUCT:
	    PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_STRUCT, previous->lexeme));
	    PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
	    break;
	case AKBASIC_TOK_FUNCTION:
	case AKBASIC_TOK_IDENTIFIER:
	    PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER, previous->lexeme));
	    break;
	default:
	    FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "Invalid literal type, command or function name");
	}
	PASS(errctx, parse_field_chain(obj, &expr));
	*dest = expr;
	SUCCEED_RETURN(errctx);
    }

    if ( akbasic_parser_match1(obj, AKBASIC_TOK_LEFT_PAREN) ) {
	PASS(errctx, akbasic_parser_expression(obj, &groupexpr));
	PASS(errctx, consume(obj, AKBASIC_TOK_RIGHT_PAREN, "Missing ) after expression"));
	PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
	PASS(errctx, akbasic_leaf_new_grouping(expr, groupexpr));
	*dest = expr;
	SUCCEED_RETURN(errctx);
    }

    PASS(errctx, akbasic_parser_error(obj, "Expected expression or literal"));
    SUCCEED_RETURN(errctx);
}
