"""NSS (NWScript) parser: PLY yacc grammar and AST construction."""

from __future__ import annotations
from pykotor.resource.formats._base import BiowareResource

from pathlib import Path
from typing import TYPE_CHECKING, NoReturn, Sequence, cast

from ply import yacc

from pykotor.resource.formats.ncs.compiler.classes import (
    AdditionAssignment,
    Assignment,
    BinaryOperatorExpression,
    BitwiseAndAssignment,
    BitwiseLeftAssignment,
    BitwiseOrAssignment,
    BitwiseRightAssignment,
    BitwiseUnsignedRightAssignment,
    BitwiseXorAssignment,
    BreakStatement,
    CodeBlock,
    CodeRoot,
    CompileError,
    ConditionAndBlock,
    ConditionalBlock,
    ContinueStatement,
    DeclarationStatement,
    DefaultSwitchLabel,
    DivisionAssignment,
    DoWhileLoopBlock,
    DynamicDataType,
    EmptyStatement,
    EngineCallExpression,
    ExpressionStatement,
    ExpressionSwitchLabel,
    FieldAccess,
    FieldAccessExpression,
    ForLoopBlock,
    FunctionCallExpression,
    FunctionDefinition,
    FunctionDefinitionParam,
    FunctionForwardDeclaration,
    GlobalVariableDeclaration,
    GlobalVariableInitialization,
    Identifier,
    IdentifierExpression,
    IncludeScript,
    ModuloAssignment,
    MultiplicationAssignment,
    NopStatement,
    PostfixDecrementExpression,
    PostfixIncrementExpression,
    PrefixDecrementExpression,
    PrefixIncrementExpression,
    ReturnStatement,
    StructDefinition,
    StructMember,
    SubtractionAssignment,
    SwitchBlock,
    SwitchStatement,
    TernaryConditionalExpression,
    UnaryOperatorExpression,
    VariableDeclarator,
    VariableInitializer,
    VectorExpression,
    WhileLoopBlock,
)
from pykotor.resource.formats.ncs.compiler.lexer import NssLexer

if TYPE_CHECKING:
    from pykotor.common.script import DataType, ScriptConstant, ScriptFunction
    from pykotor.resource.formats.ncs.compiler.classes import (
        Expression,
    )
else:
    from pykotor.common.script import DataType


class NssParser(BiowareResource):
    """NSS (NWScript Source) parser.

    Parses tokenized NSS source code into an abstract syntax tree (AST) using
    recursive descent parsing. Handles includes, function definitions, statements,
    expressions, and control flow constructs.

    References:
    ----------
        Original BioWare engine binaries (from swkotor.exe, swkotor2.exe)
        Original BioWare engine binaries
        Derivations and Other Implementations:
        ----------
        https://github.com/th3w1zard1/KotOR.js/tree/master/src/nwscript/NWScriptCompiler.ts (Parser integration)
        PLY (Python Lex-Yacc) library for parser generation

    """

    def __init__(
        self,
        functions: list[ScriptFunction],
        constants: list[ScriptConstant],
        library: dict[str, bytes],
        library_lookup: Sequence[str | Path] | None = None,
        errorlog: yacc.NullLogger | None = yacc.NullLogger(),  # noqa: B008
        *,
        debug: bool = False,
    ):
        self.parser: yacc.LRParser = yacc.yacc(
            module=self,
            errorlog=errorlog,
            write_tables=False,
            debug=debug,
        )
        self.functions: list[ScriptFunction] = functions
        self.constants: list[ScriptConstant] = constants
        self.library: dict[str, bytes] = library
        library_lookup = [] if library_lookup is None else list(library_lookup)
        self.library_lookup = [Path(item) for item in library_lookup]

    tokens: list[str] = NssLexer.tokens
    literals: list[str] = NssLexer.literals

    precedence: tuple[tuple[str, ...], ...] = (
        (
            "right",
            "=",
            "ADDITION_ASSIGNMENT_OPERATOR",
            "SUBTRACTION_ASSIGNMENT_OPERATOR",
            "MULTIPLICATION_ASSIGNMENT_OPERATOR",
            "DIVISION_ASSIGNMENT_OPERATOR",
            "MOD_ASSIGNMENT_OPERATOR",
            "BITWISE_AND_ASSIGNMENT_OPERATOR",
            "BITWISE_OR_ASSIGNMENT_OPERATOR",
            "BITWISE_XOR_ASSIGNMENT_OPERATOR",
            "BITWISE_LEFT_ASSIGNMENT_OPERATOR",
            "BITWISE_RIGHT_ASSIGNMENT_OPERATOR",
            "BITWISE_UNSIGNED_RIGHT_ASSIGNMENT_OPERATOR",
        ),
        ("right", "?"),
        ("left", "OR"),
        ("left", "AND"),
        ("left", "BITWISE_OR"),
        ("left", "BITWISE_XOR"),
        ("left", "BITWISE_AND"),
        ("left", "EQUALS", "NOT_EQUALS"),
        ("left", "GREATER_THAN", "LESS_THAN", "GREATER_THAN_OR_EQUALS", "LESS_THAN_OR_EQUALS"),
        ("left", "BITWISE_LEFT", "BITWISE_RIGHT", "BITWISE_UNSIGNED_RIGHT"),
        ("left", "ADD", "MINUS"),
        ("left", "MULTIPLY", "DIVIDE", "MOD"),
        ("right", "BITWISE_NOT", "NOT"),
        ("left", "INCREMENT", "DECREMENT"),
    )

    def p_error(self, p) -> NoReturn:
        msg = f"Syntax error at line {p.lineno}, position {p.lexpos}, token='{p.value}'"
        raise CompileError(msg)

    def p_code_root(self, p):
        """
        code_root : code_root code_root_object
                  |
        """  # noqa: D205, D415, D400, D212
        if len(p) == 3:
            code_root = cast("CodeRoot", p[1])
            code_root.objects.append(p[2])
            p[0] = code_root
        else:
            p[0] = CodeRoot(constants=self.constants, functions=self.functions, library_lookup=self.library_lookup, library=self.library)

    def p_code_root_object(self, p):
        """
        code_root_object : function_definition
                         | include_script
                         | function_forward_declaration
                         | global_variable_declaration
                         | global_variable_initialization
                         | struct_definition
        """  # noqa: D205, D415, D400, D212
        p[0] = p[1]

    def p_struct_definition(self, p):
        """
        struct_definition : STRUCT IDENTIFIER '{' struct_members '}' ';'
        """  # noqa: D415, D400, D212, D200
        p[0] = StructDefinition(p[2], p[4])

    def p_struct_members(self, p):
        """
        struct_members : struct_members struct_member
                       |
        """  # noqa: D415, D400, D212, D205
        if len(p) == 3:  # noqa: PLR2004
            cast("list", p[1]).append(p[2])
            p[0] = p[1]
        else:
            p[0] = []

    def p_struct_member(self, p):
        """
        struct_member : data_type IDENTIFIER ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = StructMember(p[1], p[2])

    def p_include_script(self, p):
        """
        include_script : INCLUDE STRING_VALUE
        """  # noqa: D200, D400, D212, D415
        p[0] = IncludeScript(p[2], library=self.library)

    def p_global_variable_initialization(self, p):
        """
        global_variable_initialization : data_type IDENTIFIER '=' expression ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = GlobalVariableInitialization(p[2], p[1], p[4])

    def p_global_variable_initialization_const(self, p):
        """
        global_variable_initialization : CONST data_type IDENTIFIER '=' expression ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = GlobalVariableInitialization(p[3], p[2], p[5], is_const=True)

    def p_global_variable_declaration(self, p):
        """
        global_variable_declaration : data_type IDENTIFIER ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = GlobalVariableDeclaration(p[2], p[1])

    def p_global_variable_declaration_const(self, p):
        """
        global_variable_declaration : CONST data_type IDENTIFIER ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = GlobalVariableDeclaration(p[3], p[2], is_const=True)

    def p_function_forward_declaration(self, p):
        """
        function_forward_declaration : data_type IDENTIFIER '(' function_definition_params ')' ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = FunctionForwardDeclaration(p[1], p[2], p[4])

    def p_function_definition(self, p: yacc.YaccProduction):
        """
        function_definition : data_type IDENTIFIER '(' function_definition_params ')' '{' code_block '}'
        """  # noqa: D200, D400, D212, D415
        rtype = p[1]
        name = p[2]
        params = p[4]
        block = p[7]
        assert isinstance(rtype, DynamicDataType), f"Expected DynamicDataType, got {rtype}"
        assert isinstance(name, Identifier), f"Expected Identifier, got {name}"
        assert isinstance(params, list), f"Expected list[FunctionDefinitionParam], got {params}"
        assert isinstance(block, CodeBlock), f"Expected CodeBlock, got {block}"
        p[0] = FunctionDefinition(rtype, name, params, block, p.lineno(2))

    def p_function_definition_params(self, p):
        """
        function_definition_params : function_definition_params ',' function_definition_param
                                   | function_definition_param
                                   |
        """  # noqa: D400, D212, D415, D205
        if len(p) == 4:
            cast("list", p[1]).append(p[3])
            p[0] = p[1]
        elif len(p) == 2:
            p[0] = [p[1]]
        elif len(p) == 1:
            p[0] = []

    def p_function_definition_param(self, p):
        """
        function_definition_param : data_type IDENTIFIER
        """  # noqa: D200, D400, D212, D415
        p[0] = FunctionDefinitionParam(p[1], p[2])

    def p_function_definition_param_with_default(self, p):
        """
        function_definition_param : data_type IDENTIFIER '=' expression
        """  # noqa: D200, D400, D212, D415
        p[0] = FunctionDefinitionParam(p[1], p[2], p[4])

    def p_code_block(self, p):
        """
        code_block : code_block statement
                   | statement
                   |
        """  # noqa: D400, D212, D415, D205
        if len(p) == 3:
            block: CodeBlock = p[1]
            block.add(p[2])
            p[0] = block
        elif len(p) == 2:
            block = CodeBlock()
            block.add(p[1])
            p[0] = block
        elif len(p) == 1:
            p[0] = CodeBlock()

    def p_while_loop(self, p):
        """
        while_loop : WHILE_CONTROL '(' expression ')' '{' code_block '}'
        """  # noqa: D200, D400, D212, D415
        p[0] = WhileLoopBlock(p[3], p[6])

    def p_do_while_loop(self, p):
        """
        do_while_loop : DO_CONTROL '{' code_block '}' WHILE_CONTROL '(' expression ')' ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = DoWhileLoopBlock(p[7], p[3])

    def p_for_loop(self, p):
        """
        for_loop : FOR_CONTROL '(' expression ';' expression ';' expression ')' '{' code_block '}'
                 | FOR_CONTROL '(' declaration_statement expression ';' expression ')' '{' code_block '}'
        """  # noqa: D200, D400, D212, D415
        if len(p) == 12:
            # for (expression; expression; expression)
            p[0] = ForLoopBlock(p[3], p[5], p[7], p[10])
        else:
            # for (declaration_statement; expression; expression) - len is 13
            p[0] = ForLoopBlock(p[3], p[5], p[7], p[11])

    def p_scoped_block(self, p):
        """
        scoped_block : '{' code_block '}'
        """  # noqa: D200, D400, D212, D415
        p[0] = p[2]

    def p_statement(self, p):
        """
        statement : ';'
                  | declaration_statement
                  | condition_statement
                  | return_statement
                  | while_loop
                  | do_while_loop
                  | for_loop
                  | switch_statement
                  | break_statement
                  | continue_statement
                  | scoped_block
        """  # noqa: D400, D212, D403, D415, D205
        if p[1] == ";":
            p[0] = EmptyStatement()
        else:
            p[0] = p[1]

    def p_nop_statement(self, p):
        """
        statement : NOP STRING_VALUE ';'
        """  # noqa: D200, D400, D403, D212, D415
        # p[2] is a StringExpression object from the lexer, access its .value attribute
        string_expr = p[2]
        p[0] = NopStatement(string_expr.value)

    def p_expression_statement(self, p):
        """
        statement : expression ';'
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = ExpressionStatement(p[1])

    def p_break_statement(self, p):
        """
        break_statement : BREAK_CONTROL ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = BreakStatement()

    def p_continue_statement(self, p):
        """
        continue_statement : CONTINUE_CONTROL ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = ContinueStatement()

    def p_declaration_statement(self, p):
        """
        declaration_statement : data_type variable_declarators ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = DeclarationStatement(p[1], p[2])

    def p_declaration_statement_const(self, p):
        """
        declaration_statement : CONST data_type variable_declarators ';'
        """  # noqa: D200, D400, D212, D415
        p[0] = DeclarationStatement(p[2], p[3], is_const=True)

    def p_variable_declarators(self, p):
        """
        variable_declarators : variable_declarators ',' variable_declarator
                             | variable_declarator
        """  # noqa: D400, D212, D415, D205
        if len(p) == 4:
            p[1].append(p[3])
            p[0] = p[1]
        elif len(p) == 2:
            p[0] = [p[1]]

    def p_variable_declarator_no_initializer(self, p):
        """
        variable_declarator : IDENTIFIER
        """  # noqa: D200, D400, D212, D415
        p[0] = VariableDeclarator(p[1])

    def p_variable_declarator_initializer(self, p):
        """
        variable_declarator : IDENTIFIER '=' expression
        """  # noqa: D200, D400, D212, D415
        p[0] = VariableInitializer(p[1], p[3])

    def p_normal_assignment(self, p):
        """
        assignment : field_access '=' expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = Assignment(p[1], p[3])

    def p_addition_assignment(self, p):
        """
        assignment : field_access ADDITION_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = AdditionAssignment(p[1], p[3])

    def p_subtraction_assignment(self, p):
        """
        assignment : field_access SUBTRACTION_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = SubtractionAssignment(p[1], p[3])

    def p_multiplication_assignment(self, p):
        """
        assignment : field_access MULTIPLICATION_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = MultiplicationAssignment(p[1], p[3])

    def p_division_assignment(self, p):
        """
        assignment : field_access DIVISION_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = DivisionAssignment(p[1], p[3])

    def p_modulo_assignment(self, p):
        """
        assignment : field_access MOD_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = ModuloAssignment(p[1], p[3])

    def p_bitwise_and_assignment(self, p):
        """
        assignment : field_access BITWISE_AND_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = BitwiseAndAssignment(p[1], p[3])

    def p_bitwise_or_assignment(self, p):
        """
        assignment : field_access BITWISE_OR_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = BitwiseOrAssignment(p[1], p[3])

    def p_bitwise_xor_assignment(self, p):
        """
        assignment : field_access BITWISE_XOR_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = BitwiseXorAssignment(p[1], p[3])

    def p_bitwise_left_assignment(self, p):
        """
        assignment : field_access BITWISE_LEFT_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = BitwiseLeftAssignment(p[1], p[3])

    def p_bitwise_right_assignment(self, p):
        """
        assignment : field_access BITWISE_RIGHT_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = BitwiseRightAssignment(p[1], p[3])

    def p_bitwise_unsigned_right_assignment(self, p):
        """
        assignment : field_access BITWISE_UNSIGNED_RIGHT_ASSIGNMENT_OPERATOR expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = BitwiseUnsignedRightAssignment(p[1], p[3])

    # region If Statement
    def p_condition_statement(self, p):
        """
        condition_statement : if_statement else_if_statements else_statement
        """  # noqa: D200, D400, D212, D415
        p[0] = ConditionalBlock(p[1], p[2], p[3])
        # IF_CONTROL '(' expression ')' '{' code_block '}'

    def p_if_statement(self, p):
        """
        if_statement : IF_CONTROL '(' expression ')' '{' code_block '}'
        """  # noqa: D200, D400, D212, D415
        p[0] = ConditionAndBlock(p[3], p[6])

    def p_if_statement_single(self, p):
        """
        if_statement : IF_CONTROL '(' expression ')' statement
        """  # noqa: D200, D400, D212, D415
        block = CodeBlock()
        block.add(p[5])
        p[0] = ConditionAndBlock(p[3], block)

    def p_else_statement(self, p):
        """
        else_statement : ELSE_CONTROL '{' code_block '}'
                       |
        """  # noqa: D400, D212, D415, D205
        p[0] = None if len(p) == 1 else p[3]

    def p_else_statement_single(self, p):
        """
        else_statement : ELSE_CONTROL statement
        """  # noqa: D200, D400, D212, D415
        block = CodeBlock()
        block.add(p[2])
        p[0] = block

    def p_else_if_statement(self, p):
        """
        else_if_statement : ELSE_CONTROL IF_CONTROL '(' expression ')' '{' code_block '}'
        """  # noqa: D200, D400, D212, D415
        p[0] = ConditionAndBlock(p[4], p[7])

    def p_else_if_statement_single(self, p):
        """
        else_if_statement : ELSE_CONTROL IF_CONTROL '(' expression ')' statement
        """  # noqa: D200, D400, D212, D415
        block = CodeBlock()
        block.add(p[6])
        p[0] = ConditionAndBlock(p[4], block)

    def p_else_if_statements(self, p):
        """
        else_if_statements : else_if_statements else_if_statement
                           |
        """  # noqa: D400, D212, D415, D205
        if len(p) == 1:
            p[0] = []
        else:
            p[1].append(p[2])
            p[0] = p[1]

    # endregion

    def p_parenthesis_expression(self, p):
        """
        expression : '(' expression ')'
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = p[2]

    def p_binary_operator(self, p):
        """
        expression : expression GREATER_THAN expression
                   | expression GREATER_THAN_OR_EQUALS expression
                   | expression LESS_THAN expression
                   | expression LESS_THAN_OR_EQUALS expression
                   | expression AND expression
                   | expression NOT_EQUALS expression
                   | expression EQUALS expression
                   | expression OR expression
                   | expression ADD expression
                   | expression MINUS expression
                   | expression MULTIPLY expression
                   | expression DIVIDE expression
                   | expression BITWISE_OR expression
                   | expression BITWISE_XOR expression
                   | expression BITWISE_AND expression
                   | expression BITWISE_LEFT expression
                   | expression BITWISE_RIGHT expression
                   | expression BITWISE_UNSIGNED_RIGHT expression
                   | expression MOD expression
        """  # noqa: D400, D212, D403, D415, D205
        p[0] = BinaryOperatorExpression(p[1], p[3], p[2].binary)

    def p_ternary_expression(self, p):
        """
        expression : expression '?' expression ':' expression
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = TernaryConditionalExpression(p[1], p[3], p[5])

    def p_unary_expression(self, p):
        """
        expression : MINUS expression
                   | BITWISE_NOT expression
                   | NOT expression
        """  # noqa: D400, D212, D403, D415, D205
        p[0] = UnaryOperatorExpression(p[2], p[1].unary)

    def p_return_statement(self, p: yacc.YaccProduction):
        """
        return_statement : RETURN ';'
                         | RETURN expression ';'
        """  # noqa: D400, D212, D415, D205
        if len(p) == 3:
            p[0] = ReturnStatement()
        elif len(p) == 4:
            expr = cast("Expression", p[2])
            p[0] = ReturnStatement(expr)

    def p_expression(self, p):
        """
        expression : function_call
                   | IDENTIFIER
                   | assignment
                   | constant_expression
        """  # noqa: D400, D212, D403, D415, D205
        p[0] = IdentifierExpression(p[1]) if isinstance(p[1], Identifier) else p[1]

    def p_constant_expression(self, p):
        """
        constant_expression : INT_VALUE
                            | FLOAT_VALUE
                            | STRING_VALUE
                            | OBJECTSELF_VALUE
                            | OBJECTINVALID_VALUE
                            | TRUE_VALUE
                            | FALSE_VALUE
                            | INT_HEX_VALUE
        """  # noqa: D400, D212, D415, D205
        p[0] = p[1]

    def p_field_access_expression(self, p):
        """
        expression : field_access
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = FieldAccessExpression(p[1])

    def p_function_call(self, p):
        """
        function_call : IDENTIFIER '(' function_call_params ')'
        """  # noqa: D200, D400, D212, D415
        identifier = p[1]
        args: list[Expression] = p[3]

        # identifier is an Identifier object, need to get its label for comparison
        identifier_label = identifier.label if isinstance(identifier, Identifier) else str(identifier)
        # Single pass: get (index, function) to avoid separate index() call
        routine_id, engine_function = next(
            ((i, x) for i, x in enumerate(self.functions) if x.name == identifier_label),
            (None, None),
        )
        if engine_function is not None and routine_id is not None:
            data_type = DynamicDataType(engine_function.returntype)
            p[0] = EngineCallExpression(engine_function, routine_id, data_type, args)
        else:
            args = p[3]
            p[0] = FunctionCallExpression(identifier, args)

    def p_function_call_params(self, p):
        """
        function_call_params : function_call_params ',' expression
                             | expression
                             |
        """  # noqa: D400, D212, D415, D205
        if len(p) == 4:
            p[1].append(p[3])
            p[0] = p[1]
        elif len(p) == 2:
            p[0] = [p[1]]
        elif len(p) == 1:
            p[0] = []

    def p_data_type(self, p):
        """
        data_type : INT_TYPE
                  | FLOAT_TYPE
                  | OBJECT_TYPE
                  | VOID_TYPE
                  | EVENT_TYPE
                  | EFFECT_TYPE
                  | ITEMPROPERTY_TYPE
                  | LOCATION_TYPE
                  | STRING_TYPE
                  | TALENT_TYPE
                  | VECTOR_TYPE
                  | ACTION_TYPE
                  | STRUCT IDENTIFIER
                  | IDENTIFIER
        """  # noqa: D400, D212, D415, D205
        if len(p) == 3:  # noqa: PLR2004
            # STRUCT IDENTIFIER
            p[0] = DynamicDataType(p[1], p[2].label)
        elif len(p) == 2:
            if isinstance(p[1], Identifier):
                # IDENTIFIER (struct type)
                p[0] = DynamicDataType(DataType.STRUCT, p[1].label)
            else:
                # Built-in type
                p[0] = DynamicDataType(p[1])

    def p_field_access(self, p):
        """
        field_access : IDENTIFIER
                     | IDENTIFIER '.' IDENTIFIER
                     | field_access '.' IDENTIFIER
        """  # noqa: D400, D212, D415, D205
        if len(p) == 2:
            p[0] = FieldAccess([p[1]])
        elif isinstance(p[1], Identifier):
            p[0] = FieldAccess([p[1], p[3]])
        else:
            p[0] = p[1]
            p[0].identifiers.append(p[3])

    def p_prefix_increment_expression(self, p):
        """
        expression : INCREMENT field_access
        """  # noqa: D200, D400, D212, D403, D415
        p[0] = PrefixIncrementExpression(p[2])

    def p_postfix_increment_expression(self, p):
        """
        expression : field_access INCREMENT
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = PostfixIncrementExpression(p[1])

    def p_prefix_decrement_expression(self, p):
        """
        expression : DECREMENT field_access
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = PrefixDecrementExpression(p[2])

    def p_postfix_decrement_expression(self, p):
        """
        expression : field_access DECREMENT
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = PostfixDecrementExpression(p[1])

    def p_vector_expression(self, p):
        """
        expression : '[' FLOAT_VALUE ',' FLOAT_VALUE ',' FLOAT_VALUE ']'
        """  # noqa: D200, D400, D403, D212, D415
        p[0] = VectorExpression(p[2], p[4], p[6])

    # region Switch Statement
    def p_switch_statement(self, p):
        """
        switch_statement : SWITCH_CONTROL '(' expression ')' '{' switch_blocks '}'
        """  # noqa: D200, D400, D212, D415
        p[0] = SwitchStatement(p[3], p[6])

    def p_switch_blocks(self, p):
        """
        switch_blocks : switch_blocks switch_block
                      |
        """  # noqa: D400, D212, D415, D205
        if len(p) == 3:
            p[1].append(p[2])
            p[0] = p[1]
        else:
            p[0] = []

    def p_switch_block(self, p):
        """
        switch_block : switch_labels block_statements
        """  # noqa: D200, D400, D212, D415
        p[0] = SwitchBlock(p[1], p[2])

    def p_switch_labels(self, p):
        """
        switch_labels : switch_labels switch_label
                      |
        """  # noqa: D400, D212, D415, D205
        if len(p) == 3:
            p[1].append(p[2])
            p[0] = p[1]
        else:
            p[0] = []

    def p_expression_switch_label(self, p):
        """
        switch_label : CASE_CONTROL expression ':'
        """  # noqa: D200, D400, D212, D415
        p[0] = ExpressionSwitchLabel(p[2])

    def p_default_switch_label(self, p):
        """
        switch_label : DEFAULT_CONTROL ':'
        """  # noqa: D200, D400, D212, D415
        p[0] = DefaultSwitchLabel()

    def p_block_statements(self, p):
        """
        block_statements : block_statements statement
                         |
        """  # noqa: D400, D212, D415, D205
        if len(p) == 3:
            p[1].append(p[2])
            p[0] = p[1]
        else:
            p[0] = []

    # endregion
