// -*- C++ -*- (c) 2013-2014 Vladimír Štill // original version was created by Jan Havlíček #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef DIVINE_ALGORITHM_SIMULATE #define DIVINE_ALGORITHM_SIMULATE namespace divine { namespace algorithm { struct DfsNext { }; struct RunDfs { long limit; RunDfs( long limit = 0 ) : limit( limit ) { } }; struct Next { int next; Next( int next ) : next( next ) { } }; struct Random { }; struct FollowCE { int limit; FollowCE( int limit ) : limit( limit ) { } }; struct FollowCEToEnd { }; struct Back { }; struct Reset { }; struct Mark { }; struct Unmark { }; struct StopOn { enum SC { Marked = 0x1, Accepting = 0x2, Deadlock = 0x4 }; StopOn( SC v, bool set = true ) : value( v ), set( set ) { }; SC value; bool set; }; struct PrintTrace { }; struct PrintCE { }; struct ListSuccs { }; struct ToggleAutoListing { }; struct Exit { }; struct Help { }; struct NoOp { }; using Command = brick::types::Union< DfsNext, Next, Random, Back, RunDfs, FollowCE, FollowCEToEnd, Reset, Mark, Unmark, StopOn, PrintTrace, PrintCE, ListSuccs, ToggleAutoListing, NoOp, Help, Exit >; enum class Option : uint32_t { TraceOpts = 0xffff, JumpTraceEnd = 0x0001, InteractiveTrace = 0x0002, CE = 0x0004, Lasso = 0x0008, DisplayOpts = 0xff0000, Interactive = 0x010000, AutoSuccs = 0x200000, // all successors are automatically printed whenever the interactive prompt is shown PrintEdges = 0x040000, // edge labels are printed for each successor }; using Options = brick::types::StrongEnumFlags< Option >; struct ApiError : std::logic_error { explicit ApiError( const std::string& error ) noexcept : std::logic_error( error + " while constructing command parser" ) { } ~ApiError() noexcept = default; }; struct ParseError : std::invalid_argument { explicit ParseError( const std::string &error ) noexcept : std::invalid_argument( error ) { } ~ParseError() noexcept = default; }; /* describes command, parse should return command representation, it will be * given string containing command parameters */ struct CommandDesc { CommandDesc() = default; CommandDesc( std::string longOpt, char shortOpt, std::string params, std::string description, std::function< Command( std::string ) > parse ) : parse( std::move( parse ) ), longOpt( longOpt ), shortOpt( shortOpt ), params( params ), description( description ) { if ( !this->parse ) throw ApiError( "Parse function is not callable" ); if ( longOpt.empty() && shortOpt == 0 ) throw ApiError( "Missing both short and long command" ); } CommandDesc( std::string longOpt, char shortOpt, std::string description, Command comm ) : CommandDesc( longOpt, shortOpt, "", description, [comm]( std::string ) { return comm; } ) { } std::function< Command( std::string ) > parse; std::string longOpt; char shortOpt; std::string params; std::string description; }; /* describes commands which do not start with short or long option, parse will * be given raw line (without line terminator), it should return parsed command * or value of Command which is explicitly convertible to false */ struct SpecCommandDesc { SpecCommandDesc() = default; SpecCommandDesc( std::string usage, std::string description, std::function< Command( std::string ) > parse ) : parse( std::move( parse ) ), usage( usage ), description( description ) { if ( !this->parse ) throw ApiError( "Parse function is not callable" ); if ( usage.empty() ) throw ApiError( "usage must be given for SpecCommandDesc" ); } std::function< Command( std::string ) > parse; std::string usage; std::string description; }; /* Command processing engine, use add or addSpec to add commands * * Command matching (function parse) is performed as follows: * 1. if command matches any special option (in order of their addition) * return of this special option is returned * 2. if command length is 1 it is matched by short options * 3. if command length is greater then 1 it is matches against long options * 3a. if that does not succeed it is partially matched against long options * (that is we try to find unique option such that given command is * a prefix of its long option) * 4. if nothing succeeds then help is printed and Command() is returned */ struct CommandEngine { CommandEngine( std::string description ) : _descrition( description ) { } CommandEngine &add( CommandDesc desc ) { if ( _shortOpts.count( desc.shortOpt ) ) throw ApiError( "Short option clash for option '" + std::string( 1, desc.shortOpt ) + "'" ); if ( _longOpts.count( desc.longOpt ) ) throw ApiError( "Long option clash for option '" + desc.longOpt + "'" ); _commands.emplace_back( std::move( desc ) ); auto ptr = &_commands.back(); if ( ptr->shortOpt ) _shortOpts[ ptr->shortOpt ] = ptr; if ( !ptr->longOpt.empty() ) _longOpts[ ptr->longOpt ] = ptr; return *this; } template< typename... Args > CommandEngine &add( Args &&...args ) { return add( CommandDesc( std::forward< Args >( args )... ) ); } CommandEngine &addSpec( SpecCommandDesc spec ) { _specCommands.push_back( spec ); return *this; } template< typename... Args > CommandEngine &addSpec( Args &&...args ) { _specCommands.emplace_back( std::forward< Args >( args )... ); return *this; } std::string help() { std::stringstream str; help( str ); return str.str(); } void help( std::ostream &os ) const { os << _descrition << std::endl << std::endl; int offset = 0; for ( auto &cd : _commands ) offset = std::max( offset, int( _formatOpt( cd ).size() ) ); for ( auto &sc : _specCommands ) offset = std::max( offset, int( sc.usage.size() ) ); offset += 2; for ( auto &sc : _specCommands ) os << sc.usage << std::string( offset - sc.usage.size(), ' ' ) << sc.description << std::endl; for ( auto &cd : _commands ) os << _formatOpt( cd, offset ) << cd.description << std::endl; } Command parse( std::string line, std::ostream &err = std::cerr ) try { if ( line.empty() ) return Command(); Command comm; for ( auto sci = _specCommands.begin(); !comm && sci != _specCommands.end(); ++sci ) comm = sci->parse( line ); if ( comm ) return comm; auto sp = std::find_if( line.begin(), line.end(), []( char c ) { return bool( std::isspace( c ) ); } ) - line.begin(); auto cmd = line.substr( 0, sp ); auto param = sp == long( line.size() ) ? std::string() : line.substr( sp + 1 ); CommandDesc *desc = nullptr; if ( cmd.size() == 1 ) { if ( _shortOpts.count( cmd[0] ) ) desc = _shortOpts[ cmd[0] ]; else { err << "Unknown short option '" << cmd << "'." << std::endl; help( err ); } } else if ( _longOpts.count( cmd ) ) { desc = _longOpts[ cmd ]; } else { // partial matching auto lb = _longOpts.lower_bound( cmd ); std::vector< CommandDesc * > matches; for ( ; lb != _longOpts.end() && lb->second->longOpt.substr( 0, cmd.size() ) == cmd; ++lb ) matches.push_back( lb->second ); if ( matches.empty() ) { err << "Unknown long option '" << cmd << "'." << std::endl; help( err ); } else if ( matches.size() > 1 ) { err << "Ambiguous option '" << cmd << "' can mean one of { "; for ( auto it = matches.begin(); (it + 1) != matches.end(); ++it ) err << (*it)->longOpt << ", "; err << matches.back()->longOpt << " }." << std::endl; } else desc = matches[ 0 ]; } if ( !desc ) return Command(); return desc->parse( param ); } catch ( ParseError &ex ) { err << ex.what() << std::endl; return Command(); } private: std::string _descrition; // we need to store descriptions in container which does not invalidate // pointers to them std::list< SpecCommandDesc > _specCommands; std::list< CommandDesc > _commands; std::map< std::string, CommandDesc * > _longOpts; std::map< char, CommandDesc * > _shortOpts; static std::string _formatOpt( const CommandDesc &cd, int pad = 0 ) { std::string str; if ( cd.shortOpt != 0 && !cd.longOpt.empty() ) str = "{ " + cd.longOpt + " | " + std::string( 1, cd.shortOpt ) + " }"; else if ( !cd.longOpt.empty() ) str = cd.longOpt; else str = std::string( 1, cd.shortOpt ); if ( !cd.params.empty() ) str += " " + cd.params; pad -= str.size(); return pad > 0 ? str + std::string( pad, ' ' ) : str; } }; template< typename Cmd > static Command parseLongParam( std::string s ) { if ( s.empty() ) return Cmd( -1 ); try { return Cmd( std::stol( s ) ); } catch ( std::invalid_argument & ) { throw ParseError( "Expected nothing or positive number, given '" + s + "'" ); } } template< StopOn::SC cond > static Command parseStop( std::string s ) { if ( s == "on" || s == "ON" ) return StopOn( cond, true ); else if ( s == "off" || s == "OFF" ) return StopOn( cond, false ); throw ParseError( "Invalid option '" + s + "' expected 'on' or 'off'" ); } struct ProcessLoop { ProcessLoop() : _engine( "DIVINE simulate -- model simulator and interactive model checker" ), _splitter( "[ \t]*,[ \t]*", REG_EXTENDED ), part( _splitter.end() ), _in( std::cin ), _out( std::cout ), _err( std::cerr ) { _engine .add( "succs", 's', "list successors", ListSuccs() ) .add( "toggle-successor-listing", 'S', "toggle automatic listing of successors", ToggleAutoListing() ) .add( "back", 'b', "back to previous state", Back() ) .add( "show-trace", 't', "show current trace", PrintTrace() ) .add( "show-ce", 'c', "show remaining part of the counterexample", PrintCE() ) .add( "step-dfs", 'n', "go to the next unvisited successor or go up if there is none (DFS)", DfsNext() ) .add( "random-succ", 'r', "go to a random successor", Random() ) .add( "follow-ce", 'f', "[N]", "follow counterexample [for N steps]", &parseLongParam< FollowCE > ) .add( "jump-goal", 'F', "follow counterexample to the end (to the first lasso accepting vertex for cycle)", FollowCEToEnd() ) .add( "run-dfs", 'D', "[N]", "run DFS [for N steps], can be interrupted by ctrl+c/SIGINT", &parseLongParam< RunDfs > ) .add( "initial", 'i', "go back to before-initial state (reset)", Reset() ) .add( "mark", 'm', "mark this state", Mark() ) .add( "unmark", 'M', "unmark this state", Unmark() ) .add( "stop-on-accepting", 0, "{ on | off }", "stop on any accepting or goal state", &parseStop< StopOn::Accepting > ) .add( "stop-on-marked", 0, "{ on | off }", "stop on any marked state", &parseStop< StopOn::Marked > ) .add( "stop-on-deadlock", 0, "{ on | off }", "stop on any deadlock (state without successorts)", &parseStop< StopOn::Deadlock > ) .add( "help", 'h', "show help", Help() ) .add( "quit", 'q', "exit simulation", Exit() ) .addSpec( "1,2,3", "use numbers to select a sucessor", []( std::string str ) -> Command { char *rest; long val = std::strtol( str.c_str(), &rest, 0 ); if ( rest == str.c_str() ) return Command(); for ( int i = 0; rest[ i ]; ++i ) if ( !std::isspace( rest[ i ] ) ) return Command(); return Next( val ); } ); } void help( bool error = false ) const { _engine.help( error ? _err : _out ); } Command command() { return command( getPart() ); } Command command( std::string part ) { if ( !_in.good() ) return Exit(); auto comm = _engine.parse( part, _err ); if ( comm.is< Help >() ) { help(); return NoOp(); } return comm ? comm : NoOp(); } void error( std::string what ) const { _err << what << std::endl; } void show( std::string what ) const { _out << what << std::endl; } using Splitter = brick::string::Splitter; Splitter::const_iterator splitString( std::string str ) { return _splitter.begin( str ); } Splitter::const_iterator splitEnd() { return _splitter.end(); } private: CommandEngine _engine; Splitter _splitter; Splitter::const_iterator part; std::istream &_in; std::ostream &_out; std::ostream &_err; std::string last; bool getLine() { ASSERT( part == _splitter.end() ); std::string line; std::getline( _in, line ); part = _splitter.begin( line ); return !line.empty(); } std::string getPart() { if ( part == _splitter.end() ) { if ( !getLine() ) return last; } last = *part; ++part; return last; } }; static std::atomic< bool > interrupted; static void sigintHandler( int ) { interrupted = true; } template< typename Setup > struct Simulate : Algorithm, AlgorithmUtils< Setup, brick::types::Unit >, Sequential { typedef Simulate< Setup > This; typedef typename Setup::Graph Graph; typedef typename Graph::Node Node; typedef typename Graph::Label Label; typedef typename Setup::Store::Vertex Vertex; struct Extension { bool seen:1; bool intrace:1; bool inCEinit:1; bool inCElasso:1; bool marked:1; uint32_t ceNext:27; bool getInCEinit() { return inCEinit; } bool getInCElasso() { return inCElasso; } }; Options options; std::vector< Vertex > trace; std::vector< std::pair< Vertex, Label > > succs; std::vector< int > iniTrail; std::vector< int > cycleTrail; ProcessLoop loop; std::mt19937 rand{ std::random_device()() }; std::string inputTrace; std::string last; StopOn::SC stopOn; brick::data::Bimap< short, std::string > stateFlagMap; brick::data::SmallVector< short > stateFlagsAll; int id() { return 0; } // expected by AlgorithmUtils Extension &extension( Vertex n ) { return n.template extension< Extension >(); } // clear the successor list void clearSuccs() { succs.clear(); } // add successor to the list void addSucc( Node n, Label l ) { auto v = this->store().store( n ); succs.emplace_back( v, l ); } // fill the successor array void generateSuccessors( Vertex from ) { clearSuccs(); this->graph().successors( LongTerm(), from, [ this ] ( Node n, Label l ) { this->addSucc( n, l ); }); } // fill the successor array by initial states void generateInitials() { ASSERT( trace.empty() ); clearSuccs(); this->graph().initials( LongTerm(), [ this ] ( Node, Node n, Label l ) { this->addSucc( n, l ); }); } // go to i-th successor void goDown( int i ) { ASSERT_LEQ( 0, i ); ASSERT_LEQ( i, int( succs.size() - 1 ) ); Vertex from = trace.empty() ? Vertex() : trace.back(); // store selected successor trace.push_back( succs[ i ].first ); extension( succs[ i ].first ).seen = true; extension( succs[ i ].first ).intrace = true; if ( options.has( Option::PrintEdges ) ) { std::string edge = this->graph().showTransition( from.node(), succs[ i ].first.node(), succs[ i ].second ); if ( !edge.empty() ) loop.show( "=> " + edge ); } generateSuccessors( trace.back() ); } // backtrace by one void goBack() { ASSERT( !trace.empty() ); extension( trace.back() ).intrace = false; trace.pop_back(); if ( trace.empty() ) { generateInitials(); } else { generateSuccessors( trace.back() ); } } // one step of DFS, go down to the first unvisited state, otherwise go up bool stepDfs() { for ( unsigned int i = 0; i < succs.size(); i++ ) { if ( !extension( succs[ i ].first ).seen ) { goDown( i ); return true; } } if ( trace.empty() ) return false; goBack(); return true; } enum class StopReason { Done, EndOfModel, Signal, Deadlock, Accepting, Marked }; using Stop = std::tuple< StopReason, long >; Stop runDfs( long limit ) { bool print = options.has( Option::PrintEdges ); options.clear( Option::PrintEdges ); auto oldsig = std::signal( SIGINT, &sigintHandler ); ASSERT( oldsig != SIG_ERR ); interrupted = false; brick::types::Defer _( [&]() { if ( print ) this->options |= Option::PrintEdges; std::signal( SIGINT, oldsig ); } ); for ( long i = 0; limit <= 0 || i < limit; ++i ) { if ( stepDfs() ) { if ( (stopOn & StopOn::Deadlock) == StopOn::Deadlock && succs.empty() ) return Stop{ StopReason::Deadlock, i }; if ( !trace.empty() ) { if ( (stopOn & StopOn::Accepting) == StopOn::Accepting && this->graph().stateFlags( trace.back().node(), graph::FlagVector{ graph::flags::goal, graph::flags::accepting } ) ) return Stop{ StopReason::Accepting, i }; if ( (stopOn & StopOn::Marked) == StopOn::Marked && extension( trace.back() ).marked ) return Stop{ StopReason::Marked, i }; } if ( interrupted.load( std::memory_order_relaxed ) ) return Stop{ StopReason::Signal, i }; } else return Stop{ StopReason::EndOfModel, i }; } return Stop{ StopReason::Done, limit }; } void markCE() { Vertex prev, cur; if ( iniTrail.empty() ) return; cur = getInitial( iniTrail[ 0 ] ); extension( cur ).inCEinit = true; for ( auto it = iniTrail.begin() + 1; it != iniTrail.end(); ++it ) { prev = cur; cur = getSucc( prev, *it ); extension( prev ).ceNext = *it; extension( cur ).inCEinit = true; } for ( auto it = cycleTrail.begin(); it != cycleTrail.end(); ++it ) { prev = cur; cur = getSucc( prev, *it ); extension( prev ).ceNext = *it; extension( cur ).inCElasso = true; } } Vertex getSucc( Vertex from, int succ ) { int i = 0; Vertex v; this->graph().successors( LongTerm(), from, [&]( Node n, Label ) { ++i; if ( i == succ ) v = this->store().store( n ); else this->pool().free( n ); } ); ASSERT_LEQ( succ, i ); ASSERT( v.valid() ); return v; } Vertex getInitial( int succ ) { int i = 0; Vertex v; this->graph().initials( LongTerm(), [&]( Node, Node n, Label ) { ++i; if ( i == succ ) v = this->store().store( n ); else this->pool().free( n ); } ); ASSERT_LEQ( succ, i ); ASSERT( v.valid() ); return v; } template< typename Yield > void vertexCallback( Yield yield, const Vertex &v ) { yield( v ); } void vertexCallback( void (This::*yield)( const Vertex & ), const Vertex &v ) { (this->*yield)( v ); } template< typename Yield > Vertex followExtension( const Vertex &from, bool (Extension::*what)(), Yield yield ) { Vertex next = from; ASSERT( extension( next ).ceNext ); Vertex head; int firstSeen = 0; do { head = next; ASSERT( (extension( head ).*what)() ); vertexCallback( yield, head ); int target = extension( head ).ceNext; if ( target == 0 ) // end of ce break; next = getSucc( head, target ); // we continue until beginning of new type or arrival at beginning if ( head.handle().asNumber() == from.handle().asNumber() ) ++firstSeen; } while ( (extension( next ).*what)() && firstSeen < 2 ); return head; } void printCE() { if ( !options.has( Option::CE ) ) { loop.error( "No counterexample" ); return; } Vertex from; if ( trace.empty() ) { ASSERT_LEQ( 1, int( iniTrail.size() ) ); from = getInitial( iniTrail[ 0 ] ); ASSERT( from.valid() ); } else { from = trace.back(); } if ( extension( from ).inCEinit ) { if ( options.has( Option::Lasso ) ) loop.show( "-- the trace to cycle" ); else loop.show( "-- the trace to goal" ); auto v = followExtension( from, &Extension::getInCEinit, &This::showNode ); if ( extension( v ).inCElasso ) { loop.show( "-- the cycle" ); followExtension( v, &Extension::getInCElasso, &This::showNode ); } } else if ( extension( from ).inCElasso ) { loop.show( "-- the cycle" ); followExtension( from, &Extension::getInCElasso, &This::showNode ); } else { loop.error( "Current node is not part of counterexample" ); } } // print successors void printSuccessors() { const int LINE = 40; unsigned int id = 0; for ( const auto &s : succs ) { Node n = s.first.node(); ++id; std::stringstream ss; ss << "== # " << id << " "; auto str = ss.str(); int chars = std::max( LINE - int( str.size() ), 0 ); loop.show( str + std::string( chars, '=' ) ); // print transition if ( options.has( Option::PrintEdges ) && !trace.empty() ) { std::string edge = this->graph().showTransition( trace.back().node(), n, s.second ); if ( !edge.empty() ) loop.show( "=> " + edge ); } // print successor showNode( s.first ); } if ( id ) loop.show( std::string( LINE, '=' ) ); loop.show( std::to_string( succs.size() ) + " successors" ); } void printCurrent() { if ( !trace.empty() ) showNode( trace.back() ); } // called whenever the current state is changed void printUpdate() { if ( !trace.empty() ) { loop.error( "-- you are here (and there are " + std::to_string( succs.size() ) + " successors):" ); printCurrent(); } else { loop.error( "-- choose an initial state (" + std::to_string( succs.size() ) + " available)" ); } } int randFromTo( int from, int toExclusive ) { std::uniform_int_distribution< int > dist( from, toExclusive - 1 ); return dist( rand ); } bool assertCEVertex() { if ( !options.has( Option::CE ) ) return false; if ( !trace.empty() && !extension( trace.back() ).inCEinit && !extension( trace.back() ).inCElasso ) return false; ASSERT( !iniTrail.empty() ); return true; } bool followCE() { if ( !assertCEVertex() ) return false; if ( trace.empty() ) { generateInitials(); goDown( iniTrail[ 0 ] - 1 ); return true; } else if ( extension( trace.back() ).ceNext ) { goDown( extension( trace.back() ).ceNext - 1 ); return true; } return false; } bool followCEToEnd() { if ( !assertCEVertex() ) return false; if ( trace.empty() ) { generateInitials(); goDown( iniTrail[ 0 ] - 1 ); } while ( true ) { ASSERT( extension( trace.back() ).inCEinit || extension( trace.back() ).inCElasso ); if ( extension( trace.back() ).ceNext == 0 ) return true; if ( extension( trace.back() ).inCElasso && this->graph().stateFlags( trace.back().node(), graph::flags::isAccepting ) ) return true; goDown( extension( trace.back() ).ceNext - 1 ); } } void showNode( const Vertex &v ) { std::vector< std::string > nodeinfo; if ( extension( v ).seen ) nodeinfo.emplace_back( "visited" ); if ( extension( v ).intrace ) nodeinfo.emplace_back( "in-trace" ); if ( extension( v ).inCEinit ) nodeinfo.emplace_back( "in-ce-init" ); if ( extension( v ).inCElasso ) nodeinfo.emplace_back( "in-ce-lasso" ); if ( extension( v ).marked ) nodeinfo.emplace_back( "marked" ); if ( this->graph().stateFlags( v.node(), graph::flags::isAccepting ) ) nodeinfo.emplace_back( "accepting" ); if ( this->graph().stateFlags( v.node(), graph::flags::isGoal ) ) nodeinfo.emplace_back( "goal" ); for ( auto f : this->graph().stateFlags( v.node(), stateFlagsAll ) ) nodeinfo.emplace_back( stateFlagMap[ f ] ); loop.show( "vertex: " + (nodeinfo.empty() ? "" : brick::string::fmt_container( nodeinfo, '{', '}' )) ); loop.show( this->graph().showNode( v.node() ) ); } enum class CmdResponse { Continue, Ignore, Exit }; void batch( std::string cmds ) { for ( auto p = loop.splitString( cmds ); p != loop.splitEnd(); ++p ) if ( runCommand( loop.command( *p ) ) == CmdResponse::Continue && !trace.empty() ) showNode( trace.back() ); } CmdResponse runCommand() { return runCommand( loop.command() ); } CmdResponse runCommand( Command cmd ) try { cmd.match( [&]( DfsNext ) { if ( !this->stepDfs() ) this->loop.error( "Cannot go back" ); }, [&]( Next n ) { if ( n.next > 0 && n.next <= int( this->succs.size() ) ) this->goDown( n.next - 1 ); else if ( this->succs.empty() ) this->loop.error( "Current state has no successor" ); else this->loop.error( "Enter number between 1 and " + std::to_string( this->succs.size() ) ); }, [&]( Random ) { if ( this->succs.empty() ) this->loop.error( "Current state has no successor" ); else if ( this->succs.size() == 1 ) this->goDown( 0 ); else this->goDown( this->randFromTo( 0, this->succs.size() ) ); }, [&]( FollowCE fc ) { if ( fc.limit <= 0 ) fc.limit = 1; for ( int i = 0; i < fc.limit; ++i ) { if ( !this->followCE() ) { this->loop.error( "There is no further CE continuation" ); break; } } }, [&]( FollowCEToEnd ) { if ( !this->followCEToEnd() ) this->loop.error( "Cannot follow CE from current vertex or no CE at all" ); }, [&]( Reset ) { this->trace.clear(); this->generateInitials(); }, [&]( Back ) { if ( !this->trace.empty() ) this->goBack(); else this->loop.error( "Cannot go back" ); }, [&]( RunDfs dfs ) { auto r = this->runDfs( dfs.limit ); auto n = std::to_string( std::get< 1 >( r ) ); switch ( std::get< 0 >( r ) ) { case StopReason::Signal: this->loop.error( "Interrupted after " + n + " steps" ); break; case StopReason::EndOfModel: this->loop.error( "Model fully explored after " + n + " steps" ); break; case StopReason::Deadlock: this->loop.show( "Reached deadlock after " + n + " steps" ); break; case StopReason::Accepting: this->loop.show( "Reached accepting state after " + n + " steps" ); break; case StopReason::Marked: this->loop.show( "Reached marked state after " + n + " steps" ); break; case StopReason::Done: this->loop.show( "Done" ); break; default: ASSERT_UNREACHABLE( "unhandled case" ); } }, [&]( Mark ) { if ( !this->trace.empty() ) this->extension( this->trace.back() ).marked = true; }, [&]( Unmark ) { if ( !this->trace.empty() ) this->extension( this->trace.back() ).marked = false; }, [&]( StopOn st ) { if ( st.set ) this->stopOn = StopOn::SC( this->stopOn | st.value ); else this->stopOn = StopOn::SC( this->stopOn & ~st.value ); }, [&]( PrintTrace ) { for ( const auto &n : this->trace ) this->showNode( n ); }, [&]( PrintCE ) { this->printCE(); }, [&]( ListSuccs ) { this->printSuccessors(); }, [&]( ToggleAutoListing ) { this->options ^= Option::AutoSuccs; this->loop.show( std::string( "Automatic listing of successors " ) + (this->options.has( Option::AutoSuccs ) ? "enabled" : "disabled" ) ); }, [&]( Exit ) { this->loop.show( "" ); throw CmdResponse::Exit; }, [&]( NoOp ) { throw CmdResponse::Ignore; } ); return CmdResponse::Continue; } catch ( CmdResponse resp ) { return resp; } void evalLoop() { CmdResponse resp = CmdResponse::Continue; do { if ( resp == CmdResponse::Continue && options.has( Option::AutoSuccs ) ) printSuccessors(); if ( options.has( Option::Interactive ) ) printUpdate(); std::cerr << "> " << std::flush; } while ( (resp = runCommand()) != CmdResponse::Exit ); } void run() { if ( inputTrace.empty() ) { generateInitials(); evalLoop(); } else { generateInitials(); batch( inputTrace ); if ( options.has( Option::Interactive ) ) { inputTrace.clear(); evalLoop(); } } } Simulate( Meta m ) : Algorithm( m, sizeof( Extension ) ) { this->init( *this ); options |= Option::PrintEdges; inputTrace = m.input.trace; stopOn = StopOn::SC( 0 ); if ( m.algorithm.interactive ) options |= Option::Interactive; if ( m.result.propertyHolds == meta::Result::R::No ) { options |= Option::Interactive; options |= Option::CE; iniTrail = m.result.iniTrail; if ( m.result.ceType == meta::Result::CET::Cycle ) { options |= Option::Lasso; cycleTrail = m.result.cycleTrail; } markCE(); } stateFlagMap = graph::flags::flagMap( this->graph() ); for ( auto &pair : stateFlagMap.left() ) stateFlagsAll.emplace_back( pair.first ); } }; } } #endif // DIVINE_ALGORITHM_SIMULATE