public function reduce(): void
    {
        $rule = $this->stateRow->reduceRule;
        if (!$rule) {
            throw new NoReduceException();
        }
        $reduce_count = count($rule->getDefinition());
        $total_count = count($this->items);
        if ($total_count < $reduce_count) {
            throw new InternalException('Not enough items in stack');
        }
        $nodes = [];
        $offset = null;
        $reduce_items = array_slice($this->items, -$reduce_count);
        foreach ($rule->getDefinition() as $i => $symbol) {
            $item = $reduce_items[$i];
            if ($item->node->getNodeName() !== $symbol->getName()) {
                throw new InternalException('Unexpected stack content');
            }

            if (!$symbol->isHidden()) {
                $nodes[] = $item->node;
            }

            if (null === $offset) {
                $offset = $item->node->getOffset();
            }
        }

        $base_state_index = ($total_count > $reduce_count)
            ? $this->items[$total_count - 1 - $reduce_count]->state
            : 0;
        $base_state_row = $this->table->getRow($base_state_index);

        $new_symbol_name = $rule->getSubject()->getName();

        $new_node = new NonTerminal($new_symbol_name, $nodes, $rule->getTag(), $offset);

        $goto = $base_state_row->gotoSwitches;
        if (!isset($goto[$new_symbol_name])) {
            throw new InternalException('No required state in GOTO table');
        }
        $next_state = $goto[$new_symbol_name];

        array_splice($this->items, -$reduce_count);
        $this->shift($new_node, $next_state);
    }