/**
 * @file grammar.h
 * @brief Declares the token and AST leaf types the scanner and parser share.
 *
 * The reference splits these across basicscanner.go (BasicTokenType),
 * basicparser.go (BasicToken) and basicgrammar.go (BasicASTLeafType,
 * BasicASTLeaf). They are one header here because a leaf's operator *is* a token
 * type and separating them buys nothing in C. The numeric values are preserved
 * from the reference so a debugging session against either implementation reads
 * the same.
 */

#ifndef _AKBASIC_GRAMMAR_H_
#define _AKBASIC_GRAMMAR_H_

#include <akerror.h>

#include <akbasic/types.h>

typedef enum
{
    AKBASIC_TOK_UNDEFINED = 0,
    AKBASIC_TOK_EQUAL,			/* 1 */
    AKBASIC_TOK_LESS_THAN,		/* 2 */
    AKBASIC_TOK_LESS_THAN_EQUAL,	/* 3 */
    AKBASIC_TOK_GREATER_THAN,		/* 4 */
    AKBASIC_TOK_GREATER_THAN_EQUAL,	/* 5 */
    AKBASIC_TOK_COMMA,			/* 6 */
    AKBASIC_TOK_HASH,			/* 7 */
    AKBASIC_TOK_NOT_EQUAL,		/* 8 */
    AKBASIC_TOK_LEFT_PAREN,		/* 9 */
    AKBASIC_TOK_RIGHT_PAREN,		/* 10 */
    AKBASIC_TOK_PLUS,			/* 11 */
    AKBASIC_TOK_MINUS,			/* 12 */
    AKBASIC_TOK_LEFT_SLASH,		/* 13 */
    AKBASIC_TOK_STAR,			/* 14 */
    AKBASIC_TOK_CARAT,			/* 15 */
    AKBASIC_TOK_LITERAL_STRING,		/* 16 */
    AKBASIC_TOK_LITERAL_INT,		/* 17 */
    AKBASIC_TOK_LITERAL_FLOAT,		/* 18 */
    AKBASIC_TOK_IDENTIFIER,		/* 19 */
    AKBASIC_TOK_IDENTIFIER_STRING,	/* 20 */
    AKBASIC_TOK_IDENTIFIER_FLOAT,	/* 21 */
    AKBASIC_TOK_IDENTIFIER_INT,		/* 22 */
    AKBASIC_TOK_COLON,			/* 23 */
    AKBASIC_TOK_AND,			/* 24 */
    AKBASIC_TOK_NOT,			/* 25 */
    AKBASIC_TOK_OR,			/* 26 */
    AKBASIC_TOK_REM,			/* 27 */
    AKBASIC_TOK_EOL,			/* 28 */
    AKBASIC_TOK_EOF,			/* 29 */
    AKBASIC_TOK_LINE_NUMBER,		/* 30 -- an integer literal in token position 0 */
    AKBASIC_TOK_COMMAND,		/* 31 */
    AKBASIC_TOK_COMMAND_IMMEDIATE,	/* 32 */
    AKBASIC_TOK_FUNCTION,		/* 33 */
    AKBASIC_TOK_ASSIGNMENT,		/* 34 */
    AKBASIC_TOK_LEFT_SQUAREBRACKET,	/* 35 */
    AKBASIC_TOK_RIGHT_SQUAREBRACKET,	/* 36 */
    AKBASIC_TOK_ARRAY_SUBSCRIPT,	/* 37 */
    AKBASIC_TOK_FUNCTION_ARGUMENT,	/* 38 */
    AKBASIC_TOK_ATSYMBOL,		/* 39 */
    AKBASIC_TOK_IDENTIFIER_STRUCT,	/* 40 */
    AKBASIC_TOK_DOT,			/* 41 -- field of a structure value */
    AKBASIC_TOK_ARROW,			/* 42 -- field through a pointer */
    /*
     * 41, 42 -- MOVSPR's two separators, and nothing else in the language uses
     * either. Appended rather than filed in with the other punctuation because
     * the numbering is part of this enum's published shape.
     */
    AKBASIC_TOK_SEMICOLON,		/* 41 */
    AKBASIC_TOK_HASHMARK		/* 42 */
} akbasic_TokenType;

typedef enum
{
    AKBASIC_LEAF_UNDEFINED = 0,
    AKBASIC_LEAF_LITERAL_INT,		/* 1 */
    AKBASIC_LEAF_LITERAL_FLOAT,		/* 2 */
    AKBASIC_LEAF_LITERAL_STRING,	/* 3 */
    AKBASIC_LEAF_IDENTIFIER,		/* 4 */
    AKBASIC_LEAF_IDENTIFIER_INT,	/* 5 */
    AKBASIC_LEAF_IDENTIFIER_FLOAT,	/* 6 */
    AKBASIC_LEAF_IDENTIFIER_STRING,	/* 7 */
    AKBASIC_LEAF_UNARY,			/* 8 */
    AKBASIC_LEAF_BINARY,		/* 9 */
    AKBASIC_LEAF_GROUPING,		/* 10 */
    AKBASIC_LEAF_EQUALITY,		/* 11 */
    AKBASIC_LEAF_COMPARISON,		/* 12 */
    AKBASIC_LEAF_TERM,			/* 13 */
    AKBASIC_LEAF_PRIMARY,		/* 14 */
    AKBASIC_LEAF_COMMAND,		/* 15 */
    AKBASIC_LEAF_COMMAND_IMMEDIATE,	/* 16 */
    AKBASIC_LEAF_FUNCTION,		/* 17 */
    AKBASIC_LEAF_BRANCH,		/* 18 */
    AKBASIC_LEAF_ARGUMENTLIST,		/* 19 */
    AKBASIC_LEAF_IDENTIFIER_STRUCT,	/* 20 */
    AKBASIC_LEAF_FIELD			/* 21 -- `base . name`; base on .left */
} akbasic_LeafType;

typedef struct
{
    akbasic_TokenType tokentype;
    int64_t lineno;
    char lexeme[AKBASIC_MAX_LINE_LENGTH];
} akbasic_Token;

typedef struct akbasic_ASTLeaf
{
    akbasic_LeafType leaftype;
    int64_t literal_int;
    char literal_string[AKBASIC_MAX_STRING_LENGTH];
    double literal_float;
    char identifier[AKBASIC_MAX_STRING_LENGTH];
    akbasic_TokenType operator_;
    struct akbasic_ASTLeaf *parent;
    struct akbasic_ASTLeaf *left;
    struct akbasic_ASTLeaf *right;
    struct akbasic_ASTLeaf *expr;
    /**
     * The next argument or subscript in a list, and nothing else.
     *
     * **This field exists because reusing `.right` for it was a bug.** The
     * reference chains an argument list through each argument's `.right`
     * (`basicparser.go`'s argumentList), which is also where a unary leaf keeps
     * its operand, where a binary leaf keeps its right-hand side, and where an
     * identifier used to keep its subscript list. The three meanings were
     * indistinguishable, and every one of them produced a wrong answer:
     *
     *     ABS(-9)             counted as two arguments, refused
     *     MOD(A#, B# + 12)    counted as three, refused
     *     MOD(-7, 3)          the second argument overwrote the first's operand
     *
     * -- TODO.md section 6 item 13, and the "array references in parameter
     * lists" item in section 4, which turned out to be the same defect twice.
     * An argument list's *head* is still the list leaf's `.right`; only the
     * sibling chain moved here.
     */
    struct akbasic_ASTLeaf *next;
} akbasic_ASTLeaf;

/**
 * @brief A pool of leaves a deep clone can draw from.
 *
 * The reference's BasicASTLeaf.clone() recurses and allocates. Here the caller
 * supplies storage and a deep clone that would exceed it fails rather than
 * growing without bound.
 */
typedef struct
{
    int next;
    int capacity;
    akbasic_ASTLeaf *leaves;
} akbasic_LeafPool;

/**
 * @brief Reset a token to the undefined state.
 * @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_token_init(akbasic_Token *obj);

/**
 * @brief Reset a leaf, clearing every payload and link.
 * @param obj Object to initialize, inspect, or modify.
 * @param leaftype Type to stamp on the leaf.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_init(akbasic_ASTLeaf *obj, akbasic_LeafType leaftype);

/**
 * @brief Deep-copy a leaf and everything hanging off it into pool storage.
 * @param self Source leaf.
 * @param pool Storage the copies are drawn from.
 * @param dest Output destination populated by the function.
 * @throws AKBASIC_ERR_BOUNDS When the pool runs out of leaves.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_clone(akbasic_ASTLeaf *self, akbasic_LeafPool *pool, akbasic_ASTLeaf **dest);

/**
 * @brief The first argument of a function-call leaf.
 * @param self Leaf to inspect.
 * @return The first argument, or NULL when the leaf carries no argument list.
 */
akbasic_ASTLeaf *akbasic_leaf_first_argument(akbasic_ASTLeaf *self);

/**
 * @brief The first subscript of an array-reference leaf.
 * @param self Leaf to inspect.
 * @return The first subscript, or NULL when the leaf carries no subscript list.
 */
akbasic_ASTLeaf *akbasic_leaf_first_subscript(akbasic_ASTLeaf *self);

/**
 * @brief True when a leaf is any of the four identifier kinds.
 * @param self Leaf to inspect; NULL is not an identifier.
 * @return `true` when the leaf names a variable or a label.
 */
bool akbasic_leaf_is_identifier(akbasic_ASTLeaf *self);

/**
 * @brief The value type an identifier leaf's suffix asks for.
 *
 * `A#` is an integer, `A%` a float, `A$` a string. A leaf that is not an
 * identifier, or one with no suffix -- a label -- reports
 * #AKBASIC_TYPE_UNDEFINED.
 *
 * @param self Leaf to inspect; NULL reports undefined.
 * @return The type, or #AKBASIC_TYPE_UNDEFINED.
 */
akbasic_Type akbasic_leaf_identifier_type(akbasic_ASTLeaf *self);

/**
 * @brief True when a leaf is an integer, float or string literal.
 * @param self Leaf to inspect; NULL is not a literal.
 * @return `true` when the leaf carries a literal value.
 */
bool akbasic_leaf_is_literal(akbasic_ASTLeaf *self);

/**
 * @brief Build a comparison leaf.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param left Left operand.
 * @param op Comparison operator; must be one of the six relational tokens.
 * @param right Right operand.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `left` or `right` is NULL.
 * @throws AKBASIC_ERR_SYNTAX When `op` is not a comparison operator.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_comparison(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *left, akbasic_TokenType op, akbasic_ASTLeaf *right);
/**
 * @brief Build a binary-operator leaf.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param left Left operand.
 * @param op Operator token.
 * @param right Right operand.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `left` or `right` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_binary(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *left, akbasic_TokenType op, akbasic_ASTLeaf *right);
/**
 * @brief Build a unary-operator leaf.
 *
 * **The operand hangs off `.left`, not `.right`, and that is deliberate.** The
 * reference puts it on `.right` (basicgrammar.go's newUnary), which is also
 * where an argument list chains its arguments -- so `ABS(-9)` counted as two
 * arguments and was refused, and a unary argument followed by a comma had its
 * operand overwritten by the next argument. TODO.md section 6 item 13. A unary
 * leaf has no other use for `.left`, so moving it there separates the two
 * meanings for the cost of one field nobody was using.
 *
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param op Operator token.
 * @param operand The expression the operator applies to.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `operand` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_unary(akbasic_ASTLeaf *obj, akbasic_TokenType op, akbasic_ASTLeaf *operand);
/**
 * @brief Build a function-call leaf.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param fname Function name as written; case is preserved for the error message.
 * @param right Argument list, or NULL.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_VALUE When the name exceeds the length limit.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_function(akbasic_ASTLeaf *obj, const char *fname, akbasic_ASTLeaf *right);
/**
 * @brief Build a verb leaf.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param cmdname Verb name as written.
 * @param right The verb's rval, or NULL for a verb that takes none.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_VALUE When the name exceeds the length limit.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_command(akbasic_ASTLeaf *obj, const char *cmdname, akbasic_ASTLeaf *right);
/**
 * @brief Build a leaf for a verb a REPL may run without a line number.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param cmdname Verb name as written.
 * @param right The verb's rval, or NULL.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_VALUE When the name exceeds the length limit.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_immediate_command(akbasic_ASTLeaf *obj, const char *cmdname, akbasic_ASTLeaf *right);
/**
 * @brief Build a branch leaf, which is what IF parses to.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param expr Condition, evaluated for a BASIC boolean.
 * @param trueleaf Taken when the condition is true.
 * @param falseleaf Taken when it is false; may be NULL, as ELSE is optional.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `expr` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_branch(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *expr, akbasic_ASTLeaf *trueleaf, akbasic_ASTLeaf *falseleaf);
/**
 * @brief Build a parenthesised-expression leaf.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param expr The grouped expression.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `expr` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_grouping(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *expr);
/**
 * @brief Build an integer literal leaf from its lexeme.
 *
 * A `0x` prefix selects base 16 and a bare leading `0` selects base 8, so `010`
 * is 8 and `08` will not parse. Commodore BASIC has no octal literals; this is
 * reproduced from the reference and filed as TODO.md section 6 item 10.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param lexeme Digits as the scanner captured them.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_VALUE When the lexeme is empty or not a valid number in its base.
 * @throws ERANGE When the value does not fit in an int64_t.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_literal_int(akbasic_ASTLeaf *obj, const char *lexeme);
/**
 * @brief Build a float literal leaf from its lexeme.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param lexeme Digits as the scanner captured them.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_VALUE When the lexeme is not a valid float.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_literal_float(akbasic_ASTLeaf *obj, const char *lexeme);
/**
 * @brief Build a string literal leaf.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param lexeme String contents, without the surrounding quotes.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_VALUE When the literal exceeds the length limit.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_literal_string(akbasic_ASTLeaf *obj, const char *lexeme);
/**
 * @brief Build an identifier leaf.
 * @param obj Leaf to initialize; drawn from a pool by the caller.
 * @param leaftype Which identifier type the suffix selected.
 * @param lexeme Identifier name, including its type suffix.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_VALUE When the name exceeds the length limit.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_identifier(akbasic_ASTLeaf *obj, akbasic_LeafType leaftype, const char *lexeme);

/**
 * @brief Build a field access: `base . name`, or `base -> name`.
 *
 * @param obj Leaf to initialize.
 * @param base The expression the field is read from; stored on `.left`.
 * @param name The field name, suffix included.
 * @param operator_ AKBASIC_TOK_DOT or AKBASIC_TOK_ARROW, which is what says
 *        whether the base must be a structure or a pointer to one.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` or `base` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When the name is too long.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_new_field(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *base, const char *name, akbasic_TokenType operator_);

/**
 * @brief Render a leaf the way the reference's toString() does.
 *
 * Prefix form, so `1 + 2` reads `(+ 1 2)`. Note that an assignment renders with
 * an empty operator -- the reference's operator table has a case for `=` meaning
 * equality but none for assignment -- so `A# = 1` reads `( A# 1)`.
 *
 * @param self Leaf to render.
 * @param dest Output destination populated by the function.
 * @param len Size of `dest`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `self` or `dest` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When `len` is zero.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, size_t len);

#endif // _AKBASIC_GRAMMAR_H_
