static function actioncmp(PHP_ParserGenerator_Action $ap1, PHP_ParserGenerator_Action $ap2)
    {
        $rc = $ap1->sp->index - $ap2->sp->index;
        if ($rc === 0) {
            $rc = $ap1->type - $ap2->type;
        }
        if ($rc === 0) {
            if ($ap1->type == self::SHIFT) {
                if ($ap1->x->statenum != $ap2->x->statenum) {
                    throw new Exception('Shift conflict: ' . $ap1->sp->name .
                        ' shifts both to state ' . $ap1->x->statenum . ' (rule ' .
                        $ap1->x->cfp->rp->lhs->name . ' on line ' .
                        $ap1->x->cfp->rp->ruleline . ') and to state ' .
                        $ap2->x->statenum . ' (rule ' .
                        $ap2->x->cfp->rp->lhs->name . ' on line ' .
                        $ap2->x->cfp->rp->ruleline . ')');
                }
            }
            if ($ap1->type != self::REDUCE
                && $ap1->type != self::RD_RESOLVED
                && $ap1->type != self::CONFLICT
            ) {
                throw new Exception('action has not been processed: ' .
                $ap1->sp->name . ' on line ' . $ap1->x->cfp->rp->ruleline .
                ', rule ' . $ap1->x->cfp->rp->lhs->name);
            }
            if ($ap2->type != self::REDUCE
                && $ap2->type != self::RD_RESOLVED
                && $ap2->type != self::CONFLICT
            ) {
                throw new Exception('action has not been processed: ' .
                $ap2->sp->name . ' on line ' . $ap2->x->cfp->rp->ruleline .
                ', rule ' . $ap2->x->cfp->rp->lhs->name);
            }
            $rc = $ap1->x->index - $ap2->x->index;
        }
        return $rc;
    }