/**
 * @file parser.h
 * @brief Declares the recursive-descent parser.
 *
 * The hierarchy is as-per "Commodore 128 Programmer's Reference Guide" page 23,
 * copied verbatim from the reference because it is the specification:
 *
 *   program        -> line*
 *   line           -> (line_number ( command | expression )) (immediate_command expression)
 *   command        -> command (expression)
 *   expression     -> logicalandor
 *   logicalandor   -> logicalnot ( "OR" "AND" ) logicalnot
 *   logicalnot     -> "NOT" relation
 *   relation       -> subtraction* [ < <= = <> >= > ] subtraction*
 *   subtraction    -> addition* "-" addition*
 *   addition       -> multiplication* "+" multiplication*
 *   multiplication -> division* "*" division*
 *   division       -> unary* "/" unary*
 *   unary          -> "-" exponent
 *   primary        -> IDENTIFIER | LITERAL_INT | LITERAL_FLOAT | LITERAL_STRING | "(" expression ")"
 */

#ifndef _AKBASIC_PARSER_H_
#define _AKBASIC_PARSER_H_

#include <akerror.h>

#include <akbasic/grammar.h>
#include <akbasic/runtime.h>

typedef struct akbasic_Parser
{
    akbasic_Runtime *runtime;
    /**
     * True while parsing something that cannot possibly be an assignment, which
     * makes a lone `=` an equality test rather than one.
     *
     * The scanner reads `==` as EQUAL and a lone `=` as ASSIGNMENT, because at
     * that point it cannot know whether it is looking at a statement or a
     * condition. A condition is the one place the question has an answer: BASIC
     * has no assignment expression, so `IF A# = 2 THEN` can only be a
     * comparison -- and that is how every BASIC manual writes it. Set around the
     * condition and cleared afterwards, because `FOR I# = 1 TO 5` runs through
     * the same expression parser and there `=` really is an assignment.
     */
    bool comparing;
} akbasic_Parser;

/**
 * @brief Bind a parser to a runtime.
 * @param obj Object to initialize, inspect, or modify.
 * @param runtime The runtime whose active environment holds the token stream.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` or `runtime` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_init(akbasic_Parser *obj, akbasic_Runtime *runtime);
/**
 * @brief Reset the token and leaf cursors for a fresh line.
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_zero(akbasic_Parser *obj);

/**
 * @brief Parse one statement from the token stream.
 *
 * A line can hold more than one statement, separated by colons, so callers loop
 * on akbasic_parser_is_at_end(). Leading separators are consumed here.
 *
 * **`*dest` may be NULL on success.** That is a line that ended in a separator,
 * or one that was nothing but separators -- an empty statement, which a C128
 * accepts and so does this. A caller that loops must skip a NULL leaf rather
 * than handing it to the interpreter.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param dest Output destination populated by the function; NULL when the
 *             remaining tokens held no statement.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_SYNTAX When the tokens do not parse.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_parse(akbasic_Parser *obj, akbasic_ASTLeaf **dest);

/**
 * @brief Consume a run of statement separators.
 *
 * Exposed because a verb with its own parse path may need to know where its
 * statement ends. Returns false when nothing but separators remained.
 *
 * @param obj Parser to advance; a NULL or unbound parser reports false.
 * @return `true` when a statement follows the separators.
 */
bool akbasic_parser_skip_separators(akbasic_Parser *obj);

/**
 * @brief True when the token stream for this line is spent.
 * @param obj Parser to inspect; a NULL or unbound parser is at the end.
 * @return `true` when there is nothing left to parse.
 */
bool akbasic_parser_is_at_end(akbasic_Parser *obj);

/* --- Internal API: the grammar rules, reachable from src/parser_commands.c. --- */

/**
 * @brief Grammar rule: a verb and its rval, or an assignment.
 *
 * A verb with its own parse path in the dispatch table delegates to it; anything
 * else takes an expression as its rval, and a line that is not a verb at all
 * falls through to assignment().
 * @param obj Object to initialize, inspect, or modify.
 * @param dest Output destination populated by the function; the leaf this rule produced.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
 * @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_command(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
 * @brief Grammar rule: `IDENTIFIER = expression`, or a bare expression.
 * @param obj Object to initialize, inspect, or modify.
 * @param dest Output destination populated by the function; the leaf this rule produced.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
 * @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_assignment(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
 * @brief Grammar rule: the top of the expression chain.
 * @param obj Object to initialize, inspect, or modify.
 * @param dest Output destination populated by the function; the leaf this rule produced.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
 * @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_expression(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
 * @brief Grammar rule: a comparison between two subtractions.
 * @param obj Object to initialize, inspect, or modify.
 * @param dest Output destination populated by the function; the leaf this rule produced.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
 * @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_relation(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
 * @brief Grammar rule: a literal, an identifier, or a parenthesised expression.
 *
 * An identifier with a type suffix also consumes an array subscript list when
 * one follows it.
 * @param obj Object to initialize, inspect, or modify.
 * @param dest Output destination populated by the function; the leaf this rule produced.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_SYNTAX When the tokens do not match the rule.
 * @throws AKBASIC_ERR_BOUNDS When the per-line leaf pool is exhausted.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_primary(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
 * @brief Grammar rule: comma-separated expressions joined through `.right`.
 *
 * With `requireparens` false and no opening paren this still builds a list,
 * which is how DATA and READ take a bare comma-separated series.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param arglisttype Marks the list as function arguments or array subscripts.
 * @param requireparens When true, parentheses are mandatory and unbalanced ones are an error.
 * @param dest Output destination populated by the function; NULL when parens were required and absent.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_SYNTAX When `arglisttype` is wrong or a paren is unbalanced.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_argument_list(akbasic_Parser *obj, akbasic_TokenType arglisttype, bool requireparens, akbasic_ASTLeaf **dest);

/**
 * @brief Take the next leaf from the active environment's per-line pool.
 * @param obj Object to initialize, inspect, or modify.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_BOUNDS When the line has already used all its leaves.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_new_leaf(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
/**
 * @brief Consume the next token when it is any of the given types.
 * @param obj Object to initialize, inspect, or modify.
 * @param types Token types to accept.
 * @param count How many entries `types` holds.
 * @return `true` when a token was consumed.
 */
bool akbasic_parser_match(akbasic_Parser *obj, const akbasic_TokenType *types, int count);

/**
 * @brief Consume the next token when it is of one given type.
 * @param obj Object to initialize, inspect, or modify.
 * @param type Token type to accept.
 * @return `true` when a token was consumed.
 */
bool akbasic_parser_match1(akbasic_Parser *obj, akbasic_TokenType type);

/**
 * @brief The next token, without consuming it.
 * @param obj Parser to inspect.
 * @return The next token, or NULL at the end of the line.
 */
akbasic_Token *akbasic_parser_peek(akbasic_Parser *obj);
/**
 * @brief The token just consumed.
 * @param obj Object to initialize, inspect, or modify.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_SYNTAX When nothing has been consumed yet.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_previous(akbasic_Parser *obj, akbasic_Token **dest);
/**
 * @brief Raise a parse error naming the token the parser stopped on.
 *
 * Always returns an error; it never succeeds. The reference also dumps a stack
 * trace here, which is not reproduced -- an interpreter library does not print
 * the host's stack, and the akerr trace already carries it.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param message What was expected, appended to the position.
 * @return An error context owned by the caller; never `NULL`.
 * @throws AKBASIC_ERR_SYNTAX Always.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parser_error(akbasic_Parser *obj, const char *message);

#endif // _AKBASIC_PARSER_H_
