/* * Copyright 2001-2018 Adrian Thurston * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "ragel.h" #include "ipgoto.h" #include "redfsm.h" #include "gendata.h" #include "bstmap.h" #include "parsedata.h" #include using std::ostringstream; void IpGoto::tableDataPass() { taNfaTargs(); taNfaOffsets(); taNfaPushActions(); taNfaPopTrans(); } void IpGoto::genAnalysis() { /* For directly executable machines there is no required state * ordering. Choose a depth-first ordering to increase the * potential for fall-throughs. */ redFsm->depthFirstOrdering(); /* Choose default transitions and the single transition. */ redFsm->chooseDefaultSpan(); /* Choose single. */ redFsm->moveSelectTransToSingle(); /* If any errors have occured in the input file then don't write anything. */ if ( red->id->errorCount > 0 ) return; redFsm->setInTrans(); /* Anlayze Machine will find the final action reference counts, among other * things. We will use these in reporting the usage of fsm directives in * action code. */ red->analyzeMachine(); /* Run the analysis pass over the table data. */ setTableState( TableArray::AnalyzePass ); tableDataPass(); /* Switch the tables over to the code gen mode. */ setTableState( TableArray::GeneratePass ); } bool IpGoto::useAgainLabel() { return redFsm->anyActionRets() || redFsm->anyActionByValControl() || redFsm->anyRegNextStmt(); } void IpGoto::GOTO( ostream &ret, int gotoDest, bool inFinish ) { ret << OPEN_GEN_BLOCK(); ret << "goto " << stLabel[gotoDest].reference() << ";"; ret << CLOSE_GEN_BLOCK(); } void IpGoto::GOTO_EXPR( ostream &ret, GenInlineItem *ilItem, bool inFinish ) { ret << OPEN_GEN_BLOCK() << vCS() << " = " << OPEN_HOST_EXPR(); INLINE_LIST( ret, ilItem->children, 0, inFinish, false ); ret << CLOSE_HOST_EXPR() << ";"; ret << " goto " << _again << ";"; ret << CLOSE_GEN_BLOCK(); } void IpGoto::CALL( ostream &ret, int callDest, int targState, bool inFinish ) { ret << OPEN_GEN_BLOCK(); if ( red->prePushExpr != 0 ) { ret << OPEN_HOST_BLOCK( red->prePushExpr ); INLINE_LIST( ret, red->prePushExpr->inlineList, 0, false, false ); ret << CLOSE_HOST_BLOCK(); } ret << STACK() << "[" << TOP() << "] = " << targState << "; " << TOP() << "+= 1; "; ret << "goto " << stLabel[callDest].reference() << ";"; ret << CLOSE_GEN_BLOCK(); } void IpGoto::NCALL( ostream &ret, int callDest, int targState, bool inFinish ) { ret << OPEN_GEN_BLOCK(); if ( red->prePushExpr != 0 ) { ret << OPEN_HOST_BLOCK( red->prePushExpr ); INLINE_LIST( ret, red->prePushExpr->inlineList, 0, false, false ); ret << CLOSE_HOST_BLOCK(); } ret << STACK() << "[" << TOP() << "] = " << targState << "; " << TOP() << "+= 1; " << vCS() << " = " << callDest << "; " << CLOSE_GEN_BLOCK(); } void IpGoto::CALL_EXPR( ostream &ret, GenInlineItem *ilItem, int targState, bool inFinish ) { ret << OPEN_GEN_BLOCK(); if ( red->prePushExpr != 0 ) { ret << OPEN_HOST_BLOCK( red->prePushExpr ); INLINE_LIST( ret, red->prePushExpr->inlineList, 0, false, false ); ret << CLOSE_HOST_BLOCK(); } ret << STACK() << "[" << TOP() << "] = " << targState << "; " << TOP() << "+= 1;" << vCS() << " = " << OPEN_HOST_EXPR(); INLINE_LIST( ret, ilItem->children, 0, inFinish, false ); ret << CLOSE_HOST_EXPR() << ";"; ret << " goto " << _again << ";"; ret << CLOSE_GEN_BLOCK(); } void IpGoto::NCALL_EXPR( ostream &ret, GenInlineItem *ilItem, int targState, bool inFinish ) { ret << OPEN_GEN_BLOCK(); if ( red->prePushExpr != 0 ) { ret << OPEN_HOST_BLOCK( red->prePushExpr ); INLINE_LIST( ret, red->prePushExpr->inlineList, 0, false, false ); ret << CLOSE_HOST_BLOCK(); } ret << STACK() << "[" << TOP() << "] = " << targState << "; " << TOP() << "+= 1;" << vCS() << " = " << OPEN_HOST_EXPR(); INLINE_LIST( ret, ilItem->children, 0, inFinish, false ); ret << CLOSE_HOST_EXPR() << "; " << CLOSE_GEN_BLOCK(); } void IpGoto::RET( ostream &ret, bool inFinish ) { ret << OPEN_GEN_BLOCK() << TOP() << " -= 1;" << vCS() << " = " << STACK() << "[" << TOP() << "];"; if ( red->postPopExpr != 0 ) { ret << OPEN_HOST_BLOCK( red->postPopExpr ); INLINE_LIST( ret, red->postPopExpr->inlineList, 0, false, false ); ret << CLOSE_HOST_BLOCK(); } ret << "goto " << _again << ";" << CLOSE_GEN_BLOCK(); } void IpGoto::NRET( ostream &ret, bool inFinish ) { ret << OPEN_GEN_BLOCK() << TOP() << " -= 1;" << vCS() << " = " << STACK() << "[" << TOP() << "];"; if ( red->postPopExpr != 0 ) { ret << OPEN_HOST_BLOCK( red->postPopExpr ); INLINE_LIST( ret, red->postPopExpr->inlineList, 0, false, false ); ret << CLOSE_HOST_BLOCK(); } ret << CLOSE_GEN_BLOCK(); } void IpGoto::NEXT( ostream &ret, int nextDest, bool inFinish ) { ret << vCS() << " = " << nextDest << ";"; } void IpGoto::NEXT_EXPR( ostream &ret, GenInlineItem *ilItem, bool inFinish ) { ret << vCS() << " = ("; INLINE_LIST( ret, ilItem->children, 0, inFinish, false ); ret << ");"; } void IpGoto::CURS( ostream &ret, bool inFinish ) { ret << "(" << ps << ")"; } void IpGoto::TARGS( ostream &ret, bool inFinish, int targState ) { ret << targState; } void IpGoto::BREAK( ostream &ret, int targState, bool csForced ) { ret << OPEN_GEN_BLOCK() << P() << "+= 1; "; if ( !csForced ) ret << vCS() << " = " << targState << "; "; ret << "goto " << _out << ";" << CLOSE_GEN_BLOCK(); } void IpGoto::NBREAK( ostream &ret, int targState, bool csForced ) { ret << OPEN_GEN_BLOCK() << P() << "+= 1; "; if ( !csForced ) ret << vCS() << " = " << targState << "; "; ret << nbreak << " = 1;" << CLOSE_GEN_BLOCK(); } void IpGoto::NFA_PUSH_ACTION( RedNfaTarg *targ ) { int act = 0; if ( targ->push != 0 ) act = targ->push->actListId+1; nfaPushActions.value( act ); } void IpGoto::NFA_POP_TEST( RedNfaTarg *targ ) { int act = 0; if ( targ->popTest != 0 ) act = targ->popTest->actListId+1; nfaPopTrans.value( act ); } bool IpGoto::IN_TRANS_ACTIONS( RedStateAp *state ) { bool anyWritten = false; /* Emit any transitions that have actions and that go to this state. */ for ( int it = 0; it < state->numInConds; it++ ) { RedCondPair *trans = state->inConds[it]; if ( trans->action != 0 ) { /* Remember that we wrote an action so we know to write the * line directive for going back to the output. */ anyWritten = true; /* Write the label for the transition so it can be jumped to. */ if ( ctrLabel[trans->id].isReferenced ) out << "_ctr" << trans->id << ":\n"; /* If the action contains a next, then we must preload the current * state since the action may or may not set it. */ if ( trans->action->anyNextStmt() ) out << " " << vCS() << " = " << trans->targ->id << ";\n"; if ( redFsm->anyRegNbreak() ) out << nbreak << " = 0;\n"; /* Write each action in the list. */ for ( GenActionTable::Iter item = trans->action->key; item.lte(); item++ ) { ACTION( out, item->value, IlOpts( trans->targ->id, false, trans->action->anyNextStmt() ) ); out << "\n"; } if ( redFsm->anyRegNbreak() ) { out << "if ( " << nbreak << " == 1 )\n" " goto " << _out << ";\n"; } /* If the action contains a next then we need to reload, otherwise * jump directly to the target state. */ if ( trans->action->anyNextStmt() ) out << "goto " << _again << ";\n"; else out << "goto " << stLabel[trans->targ->id].reference() << ";\n"; } } return anyWritten; } void IpGoto::GOTO_HEADER( RedStateAp *state ) { } void IpGoto::STATE_GOTO_ERROR() { } /* Emit the goto to take for a given transition. */ std::ostream &IpGoto::TRANS_GOTO( RedTransAp *trans ) { if ( trans->condSpace == 0 || trans->condSpace->condSet.length() == 0 ) { /* Existing. */ assert( trans->numConds() == 1 ); RedCondPair *cond = trans->outCond( 0 ); if ( cond->action != 0 ) { /* Go to the transition which will go to the state. */ out << "goto " << ctrLabel[trans->p.id].reference() << ";"; } else { /* Go directly to the target state. */ out << "goto " << stLabel[cond->targ->id].reference() << ";"; } } else { out << ck << " = 0;\n"; for ( GenCondSet::Iter csi = trans->condSpace->condSet; csi.lte(); csi++ ) { out << "if ( "; CONDITION( out, *csi ); Size condValOffset = (1 << csi.pos()); out << " )\n" << ck << " += " << condValOffset << ";\n"; } CondKey lower = 0; CondKey upper = trans->condFullSize() - 1; COND_B_SEARCH( trans, lower, upper, 0, trans->numConds() - 1 ); if ( trans->errCond() != 0 ) { COND_GOTO( trans->errCond() ) << "\n"; } } return out; } /* Emit the goto to take for a given transition. */ std::ostream &IpGoto::COND_GOTO( RedCondPair *cond ) { /* Existing. */ if ( cond->action != 0 ) { /* Go to the transition which will go to the state. */ out << "goto " << ctrLabel[cond->id].reference() << ";"; } else { /* Go directly to the target state. */ out << "goto " << stLabel[cond->targ->id].reference() << ";"; } return out; } std::ostream &IpGoto::EXIT_STATES() { for ( RedStateList::Iter st = redFsm->stateList; st.lte(); st++ ) { if ( outLabel[st->id].isReferenced ) { out << outLabel[st->id].define() << ": " << vCS() << " = " << st->id << "; goto " << _out << "; \n"; } if ( popLabel[st->id].isReferenced ) { out << popLabel[st->id].define() << ": " << vCS() << " = " << st->id << "; goto " << _pop << "; \n"; } } return out; } std::ostream &IpGoto::AGAIN_CASES() { for ( RedStateList::Iter st = redFsm->stateList; st.lte(); st++ ) { out << "case " << st->id << ": goto " << stLabel[st->id].reference() << ";\n"; } return out; } std::ostream &IpGoto::STATE_GOTO_CASES() { for ( RedStateList::Iter st = redFsm->stateList; st.lte(); st++ ) { out << "case " << st->id << ":\n"; out << "goto st_case_" << st->id << ";\n"; } return out; } void IpGoto::NFA_PUSH_ST( RedStateAp *state ) { std::stringstream ss; ss << state->id; std::string _state = ss.str(); if ( redFsm->anyNfaStates() ) { if ( state->nfaTargs != 0 ) { out << "if ( " << ARR_REF( nfaOffsets ) << "[" << _state << "] != 0 ) {\n"; if ( red->nfaPrePushExpr != 0 ) { out << new_recs << " = " << state->nfaTargs->length() << ";\n"; } if ( red->nfaPrePushExpr != 0 ) { out << OPEN_HOST_BLOCK( red->nfaPrePushExpr ); INLINE_LIST( out, red->nfaPrePushExpr->inlineList, 0, false, false ); out << CLOSE_HOST_BLOCK(); } int alt = 0; for ( RedNfaTargs::Iter nt = *state->nfaTargs; nt.lte(); nt++ ) { out << " nfa_bp[nfa_len].state = " << nt->state->id << ";\n" " nfa_bp[nfa_len].p = " << P() << ";\n"; if ( nt->popTest != 0 ) { out << " nfa_bp[nfa_len].popTrans = " << (nt->popTest->actListId+1) << ";\n"; } else if ( redFsm->bAnyNfaPops ) { out << " nfa_bp[nfa_len].popTrans = 0;\n"; } if ( nt->push != 0 ) { for ( GenActionTable::Iter item = nt->push->key; item.lte(); item++ ) ACTION( out, item->value, IlOpts( 0, false, false ) ); } out << " nfa_len += 1;\n"; alt += 1; } out << "}\n"; } } } std::ostream &IpGoto::STATE_GOTOS() { bool eof = redFsm->anyEofActivity() || redFsm->anyNfaStates() || redFsm->anyFromStateActions(); for ( RedStateList::Iter st = redFsm->stateList; st.lte(); st++ ) { IN_TRANS_ACTIONS( st ); if ( stLabel[st->id].isReferenced ) out << "_st" << st->id << ":\n"; /* Need to do this if the transition is an eof transition, or if * the action contains fexec. Otherwise, no need. */ if ( eof ) { out << "if ( " << P() << " == " << vEOF() << " )\n"; if ( st->isFinal || !redFsm->anyNfaStates() ) out << "goto " << outLabel[st->id].reference() << ";\n"; else out << "goto " << popLabel[st->id].reference() << ";\n"; } if ( st->toStateAction != 0 ) { /* Write every action in the list. */ for ( GenActionTable::Iter item = st->toStateAction->key; item.lte(); item++ ) { ACTION( out, item->value, IlOpts( st->id, false, st->toStateAction->anyNextStmt() ) ); out << "\n"; } } if ( st == redFsm->errState ) { out << "st_case_" << st->id << ":\n"; /* Break out here. */ if ( !redFsm->anyNfaStates() ) out << "goto " << outLabel[st->id].reference() << ";\n"; else out << "goto " << popLabel[st->id].reference() << ";\n"; } else { /* Advance and test buffer pos. */ if ( st->labelNeeded ) { out << P() << "+= 1;\n"; } /* Give the st a switch case. */ out << "st_case_" << st->id << ":\n"; if ( !noEnd ) { if ( eof ) { out << "if ( " << P() << " == " << PE() << " && " << P() << " != " << vEOF() << " )\n" " goto " << outLabel[st->id].reference() << ";\n"; } else { out << "if ( " << P() << " == " << PE() << " )\n" " goto " << outLabel[st->id].reference() << ";\n"; } } NFA_PUSH_ST( st ); if ( st->fromStateAction != 0 ) { /* Write every action in the list. */ for ( GenActionTable::Iter item = st->fromStateAction->key; item.lte(); item++ ) { ACTION( out, item->value, IlOpts( st->id, false, st->fromStateAction->anyNextStmt() ) ); out << "\n"; } } if ( !noEnd && eof ) { out << "if ( " << P() << " == " << vEOF() << " ) {\n"; if ( st->eofTrans != 0 ) TRANS_GOTO( st->eofTrans ); else { if ( st->isFinal || !redFsm->anyNfaStates() ) out << "goto " << outLabel[st->id].reference() << ";\n"; else out << "goto " << popLabel[st->id].reference() << ";\n"; } out << "}\n" "else {\n"; } /* Record the prev st if necessary. */ if ( st->anyRegCurStateRef() ) out << ps << " = " << st->id << ";\n"; /* Try singles. */ if ( st->outSingle.length() > 0 ) SINGLE_SWITCH( st ); /* Default case is to binary search for the ranges, if that fails then */ if ( st->outRange.length() > 0 ) { RANGE_B_SEARCH( st, keyOps->minKey, keyOps->maxKey, 0, st->outRange.length() - 1 ); } /* Write the default transition. */ TRANS_GOTO( st->defTrans ) << "\n"; if ( !noEnd && eof ) { out << "}\n"; } } } return out; } std::ostream &IpGoto::FINISH_CASES() { for ( RedStateList::Iter st = redFsm->stateList; st.lte(); st++ ) { if ( st->eofTrans != 0 ) { out << "case " << st->id << ":\n"; TRANS_GOTO( st->eofTrans ); } } return out; } void IpGoto::setLabelsNeeded( GenInlineList *inlineList ) { for ( GenInlineList::Iter item = *inlineList; item.lte(); item++ ) { switch ( item->type ) { case GenInlineItem::Goto: case GenInlineItem::Call: case GenInlineItem::Ncall: { /* Mark the target as needing a label. */ item->targState->labelNeeded = true; break; } default: break; } if ( item->children != 0 ) setLabelsNeeded( item->children ); } } void IpGoto::setLabelsNeeded( RedCondPair *pair ) { /* If there is no action with a next statement, then the label will be * needed. */ if ( pair->action == 0 || !pair->action->anyNextStmt() ) pair->targ->labelNeeded = true; /* Need labels for states that have goto or calls in action code * invoked on characters (ie, not from out action code). */ if ( pair->action != 0 ) { /* Loop the actions. */ for ( GenActionTable::Iter act = pair->action->key; act.lte(); act++ ) { /* Get the action and walk it's tree. */ setLabelsNeeded( act->value->inlineList ); } } } /* Set up labelNeeded flag for each state. */ void IpGoto::setLabelsNeeded() { /* If we use the _again label, then we generate the _again switch, which * uses all labels. */ if ( useAgainLabel() ) { for ( RedStateList::Iter st = redFsm->stateList; st.lte(); st++ ) st->labelNeeded = true; } else { /* Do not use all labels by default, init all labelNeeded vars to false. */ for ( RedStateList::Iter st = redFsm->stateList; st.lte(); st++ ) st->labelNeeded = false; for ( TransApSet::Iter trans = redFsm->transSet; trans.lte(); trans++ ) { if ( trans->condSpace == 0 ) setLabelsNeeded( &trans->p ); } for ( CondApSet::Iter cond = redFsm->condSet; cond.lte(); cond++ ) setLabelsNeeded( &cond->p ); for ( RedStateList::Iter st = redFsm->stateList; st.lte(); st++ ) { if ( st->eofAction != 0 ) { for ( GenActionTable::Iter item = st->eofAction->key; item.lte(); item++ ) setLabelsNeeded( item->value->inlineList ); } } } } void IpGoto::writeData() { STATE_IDS(); taNfaTargs(); taNfaOffsets(); taNfaPushActions(); taNfaPopTrans(); } void IpGoto::NFA_FROM_STATE_ACTION_EXEC() { // if ( redFsm->anyFromStateActions() ) { // /* Unimplemented feature. Don't have the from state actions array in // * this mode. Need to add it, or to alter the NFA pop codegen to be // * consistent with the mode. */ // assert( false ); // } } void IpGoto::writeExec() { int maxCtrId = redFsm->nextCondId > redFsm->nextTransId ? redFsm->nextCondId : redFsm->nextTransId; stLabel = allocateLabels( stLabel, IpLabel::St, redFsm->nextStateId ); ctrLabel = allocateLabels( ctrLabel, IpLabel::Ctr, maxCtrId ); outLabel = allocateLabels( outLabel, IpLabel::Out, redFsm->nextStateId ); popLabel = allocateLabels( popLabel, IpLabel::Pop, redFsm->nextStateId ); /* Must set labels immediately before writing because we may depend on the * noend write option. */ setLabelsNeeded(); out << "{\n"; DECLARE( INT(), cpc ); DECLARE( INT(), ck ); DECLARE( INT(), pop_test ); DECLARE( INT(), nbreak ); DECLARE( INT(), ps ); DECLARE( INT(), new_recs ); DECLARE( INT(), alt ); if ( _again.isReferenced ) { out << " goto " << _resume << ";\n" "\n"; out << EMIT_LABEL( _again ); out << " switch ( " << vCS() << " ) {\n"; AGAIN_CASES() << " }\n" "\n"; } out << EMIT_LABEL( _resume ); out << "switch ( " << vCS() << " ) {\n"; STATE_GOTO_CASES(); out << "}\n"; STATE_GOTOS(); EXIT_STATES(); out << EMIT_LABEL( _pop ); if ( redFsm->anyNfaStates() ) { out << "if ( nfa_len == 0 )\n" " goto " << _out << ";\n" "\n"; out << "nfa_count += 1;\n" "nfa_len -= 1;\n" << P() << " = nfa_bp[nfa_len].p;\n"; if ( redFsm->bAnyNfaPops ) { NFA_FROM_STATE_ACTION_EXEC(); NFA_POP_TEST_EXEC(); out << "if ( " << pop_test << " )\n" " " << vCS() << " = nfa_bp[nfa_len].state;\n" "else\n" " " << vCS() << " = " << ERROR_STATE() << ";\n"; } else { out << vCS() << " = nfa_bp[nfa_len].state;\n"; } NFA_POST_POP(); out << "goto " << _resume << ";\n"; } out << EMIT_LABEL( _out ); out << "}\n"; }