/** * @file * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) * @copyright Simplified BSD * * @cond * This program is free software: you can redistribute it and/or modify * it under the terms of the FreeBSD license as published by the FreeBSD * project. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the FreeBSD license along with this * program. If not, see . * @endcond */ #ifndef RUNTIME_H_SQ1MBKGN #define RUNTIME_H_SQ1MBKGN // this has to be the first include or MSVC will run amok #include "uscxml/Common.h" #include "getopt.h" #include "uscxml/URL.h" #include #include #include #include #include #include #include #include #include #include #include #include "uscxml/concurrency/tinythread.h" #include "uscxml/concurrency/eventqueue/DelayedEventQueue.h" #include "uscxml/concurrency/BlockingQueue.h" #include "uscxml/Message.h" #include "uscxml/Factory.h" #include "uscxml/server/InterpreterServlet.h" #define USCXML_MONITOR_CATCH_BLOCK(callback)\ catch (Event e) {\ LOG(ERROR) << "Syntax error when calling " #callback " on monitors: " << std::endl << e << std::endl;\ } catch (boost::bad_weak_ptr e) {\ LOG(ERROR) << "Unclean shutdown " << std::endl << std::endl;\ } catch (...) {\ LOG(ERROR) << "An exception occured when calling " #callback " on monitors";\ }\ if (_destroyed) {\ throw boost::bad_weak_ptr();\ } namespace uscxml { class HTTPServletInvoker; class InterpreterMonitor; class USCXML_API NumAttr { public: NumAttr(const std::string& str) { size_t valueStart = str.find_first_of("0123456789."); if (valueStart != std::string::npos) { size_t valueEnd = str.find_last_of("0123456789."); if (valueEnd != std::string::npos) { value = str.substr(valueStart, (valueEnd - valueStart) + 1); size_t unitStart = str.find_first_not_of(" \t", valueEnd + 1); if (unitStart != std::string::npos) { size_t unitEnd = str.find_last_of(" \t"); if (unitEnd != std::string::npos && unitEnd > unitStart) { unit = str.substr(unitStart, unitEnd - unitStart); } else { unit = str.substr(unitStart, str.length() - unitStart); } } } } } std::string value; std::string unit; }; enum Capabilities { CAN_NOTHING = 0, CAN_BASIC_HTTP = 1, CAN_GENERIC_HTTP = 2, }; class USCXML_API InterpreterOptions { public: InterpreterOptions() : withDebugger(false), verbose(false), withHTTP(true), withHTTPS(true), withWS(true), logLevel(0), httpPort(0), httpsPort(0), wsPort(0) {} bool withDebugger; bool verbose; bool withHTTP; bool withHTTPS; bool withWS; int logLevel; unsigned short httpPort; unsigned short httpsPort; unsigned short wsPort; std::string pluginPath; std::string certificate; std::string privateKey; std::string publicKey; std::map interpreters; std::map additionalParameters; std::string error; operator bool() { return error.length() == 0; } static void printUsageAndExit(const char* progName); static InterpreterOptions fromCmdLine(int argc, char** argv); unsigned int getCapabilities(); }; class NameSpaceInfo { public: NameSpaceInfo() : nsContext(NULL) { init(std::map()); } NameSpaceInfo(const std::map& nsInfo) : nsContext(NULL) { init(nsInfo); } NameSpaceInfo(const NameSpaceInfo& other) : nsContext(NULL) { init(other.nsInfo); } virtual ~NameSpaceInfo() { if (nsContext) delete nsContext; } NameSpaceInfo& operator=( const NameSpaceInfo& other ) { init(other.nsInfo); return *this; } void setPrefix(Arabica::DOM::Element element) { if (nsURL.size() > 0) element.setPrefix(nsToPrefix[nsURL]); } void setPrefix(Arabica::DOM::Attr attribute) { if (nsURL.size() > 0) attribute.setPrefix(nsToPrefix[nsURL]); } const Arabica::XPath::StandardNamespaceContext* getNSContext() { return nsContext; } std::string nsURL; // ough to be "http://www.w3.org/2005/07/scxml" but maybe empty std::string xpathPrefix; // prefix mapped for xpath, "scxml" is _xmlNSPrefix is empty but _nsURL set std::string xmlNSPrefix; // the actual prefix for elements in the xml file std::map nsToPrefix; // prefixes for a given namespace std::map nsInfo; // all xmlns mappings private: Arabica::XPath::StandardNamespaceContext* nsContext; void init(const std::map& nsInfo); }; // values larger than 0 indicate that you ought to step again enum InterpreterState { FINISHED = 0, // machine reached a final configuration INIT_FAILED = -1, // could not initialize interpreter INTERRUPTED = -2, // machine was interrupted NOTHING_TODO = 4, // when non-blocking returns nothing to do PROCESSED = 8, // an event was processed INITIALIZED = 16 // initial stable configuration was assumed }; class USCXML_API InterpreterImpl : public boost::enable_shared_from_this { public: typedef std::set::iterator monIter_t; enum Binding { EARLY = 0, LATE = 1 }; virtual ~InterpreterImpl(); void copyTo(InterpreterImpl* other); void copyTo(boost::shared_ptr other); void start(); void stop(); static void run(void*); void join() { if (_thread != NULL) _thread->join(); }; bool isRunning() { return _running || !_done; } /// This one ought to be pure, but SWIG will generate gibberish if it is virtual void interpret() = 0; virtual InterpreterState step(bool blocking = false) = 0; void addMonitor(InterpreterMonitor* monitor) { _monitors.insert(monitor); } void removeMonitor(InterpreterMonitor* monitor) { _monitors.erase(monitor); } void setSourceURI(std::string sourceURI) { _sourceURI = URL(sourceURI); URL baseURI(sourceURI); URL::toBaseURL(baseURI); _baseURI = baseURI; } URL getBaseURI() { return _baseURI; } URL getSourceURI() { return _sourceURI; } void setCmdLineOptions(std::map params); Data getCmdLineOptions() { return _cmdLineOptions; } InterpreterHTTPServlet* getHTTPServlet() { return _httpServlet; } DataModel getDataModel() { return _dataModel; } void setParentQueue(uscxml::concurrency::BlockingQueue* parentQueue) { _parentQueue = parentQueue; } void setFactory(Factory* factory) { _factory = factory; } Factory* getFactory() { return _factory; } Arabica::XPath::NodeSet getNodeSetForXPath(const std::string& xpathExpr) { return _xpath.evaluate(xpathExpr, _scxml).asNodeSet(); } std::string getXMLPrefixForNS(const std::string& ns) const { if (_nsInfo.nsToPrefix.find(ns) != _nsInfo.nsToPrefix.end() && _nsInfo.nsToPrefix.at(ns).size()) return _nsInfo.nsToPrefix.at(ns) + ":"; return ""; } void setNameSpaceInfo(const NameSpaceInfo& nsInfo) { _nsInfo = nsInfo; _xpath.setNamespaceContext(*_nsInfo.getNSContext()); } NameSpaceInfo getNameSpaceInfo() const { return _nsInfo; } void receiveInternal(const Event& event); void receive(const Event& event, bool toFront = false); Event getCurrentEvent() { tthread::lock_guard lock(_mutex); return _currEvent; } virtual bool isInState(const std::string& stateId); Arabica::XPath::NodeSet getConfiguration() { tthread::lock_guard lock(_mutex); return _configuration; } Arabica::XPath::NodeSet getBasicConfiguration() { tthread::lock_guard lock(_mutex); Arabica::XPath::NodeSet basicConfig; for (int i = 0; i < _configuration.size(); i++) { if (isAtomic(_configuration[i])) basicConfig.push_back(_configuration[i]); } return basicConfig; } void setConfiguration(const std::vector& states) { _userDefinedStartConfiguration = states; } void setInvokeRequest(const InvokeRequest& req) { _invokeReq = req; } Arabica::DOM::Node getState(const std::string& stateId); Arabica::XPath::NodeSet getStates(const std::list& stateIds); Arabica::XPath::NodeSet getAllStates(); virtual Arabica::DOM::Document getDocument() const { return _document; } void setCapabilities(unsigned int capabilities) { _capabilities = capabilities; } void setName(const std::string& name); const std::string& getName() { return _name; } const std::string& getSessionId() { return _sessionId; } DelayedEventQueue* getDelayQueue() { return _sendQueue; } const std::map& getIOProcessors() { return _ioProcessors; } const std::map& getInvokers() { return _invokers; } bool runOnMainThread(int fps, bool blocking = true); static bool isMember(const Arabica::DOM::Node& node, const Arabica::XPath::NodeSet& set); void dump(); bool hasLegalConfiguration(); bool isLegalConfiguration(const Arabica::XPath::NodeSet&); bool isLegalConfiguration(const std::vector&); static bool isState(const Arabica::DOM::Node& state); static bool isPseudoState(const Arabica::DOM::Node& state); static bool isTransitionTarget(const Arabica::DOM::Node& elem); static bool isTargetless(const Arabica::DOM::Node& transition); static bool isAtomic(const Arabica::DOM::Node& state); static bool isFinal(const Arabica::DOM::Node& state); static bool isHistory(const Arabica::DOM::Node& state); static bool isParallel(const Arabica::DOM::Node& state); static bool isCompound(const Arabica::DOM::Node& state); static bool isDescendant(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2); static std::list tokenizeIdRefs(const std::string& idRefs); static std::string spaceNormalize(const std::string& text); static bool nameMatch(const std::string& transitionEvent, const std::string& event); bool isInEmbeddedDocument(const Arabica::DOM::Node& node); bool isInitial(const Arabica::DOM::Node& state); Arabica::XPath::NodeSet getInitialStates(Arabica::DOM::Node state = Arabica::DOM::Node()); static Arabica::XPath::NodeSet getChildStates(const Arabica::DOM::Node& state); static Arabica::XPath::NodeSet getChildStates(const Arabica::XPath::NodeSet& state); static Arabica::DOM::Node getParentState(const Arabica::DOM::Node& element); static Arabica::DOM::Node getAncestorElement(const Arabica::DOM::Node& node, const std::string tagName); virtual Arabica::XPath::NodeSet getTargetStates(const Arabica::DOM::Node& transition); virtual Arabica::XPath::NodeSet getTargetStates(const Arabica::XPath::NodeSet& transitions); virtual Arabica::DOM::Node getSourceState(const Arabica::DOM::Node& transition); static Arabica::XPath::NodeSet filterChildElements(const std::string& tagname, const Arabica::DOM::Node& node, bool recurse = false); static Arabica::XPath::NodeSet filterChildElements(const std::string& tagName, const Arabica::XPath::NodeSet& nodeSet, bool recurse = false); static Arabica::XPath::NodeSet filterChildType(const Arabica::DOM::Node_base::Type type, const Arabica::DOM::Node& node, bool recurse = false); static Arabica::XPath::NodeSet filterChildType(const Arabica::DOM::Node_base::Type type, const Arabica::XPath::NodeSet& nodeSet, bool recurse = false); Arabica::DOM::Node findLCCA(const Arabica::XPath::NodeSet& states); virtual Arabica::XPath::NodeSet getProperAncestors(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2); protected: class DOMEventListener : public Arabica::DOM::Events::EventListener { public: void handleEvent(Arabica::DOM::Events::Event& event); InterpreterImpl* _interpreter; }; InterpreterImpl(); void init(); void normalize(Arabica::DOM::Element& scxml); void initializeData(const Arabica::DOM::Element& data); virtual void setupIOProcessors(); bool _stable; tthread::thread* _thread; tthread::recursive_mutex _mutex; tthread::condition_variable _condVar; tthread::recursive_mutex _pluginMutex; URL _baseURI; URL _sourceURI; Arabica::DOM::Document _document; Arabica::DOM::Element _scxml; Arabica::XPath::XPath _xpath; NameSpaceInfo _nsInfo; bool _running; bool _done; bool _destroyed; // see comment in destructor bool _isInitialized; InterpreterImpl::Binding _binding; Arabica::XPath::NodeSet _configuration; Arabica::XPath::NodeSet _alreadyEntered; Arabica::XPath::NodeSet _statesToInvoke; std::vector _userDefinedStartConfiguration; InvokeRequest _invokeReq; DataModel _dataModel; std::map > _historyValue; std::list _internalQueue; uscxml::concurrency::BlockingQueue _externalQueue; uscxml::concurrency::BlockingQueue* _parentQueue; DelayedEventQueue* _sendQueue; DOMEventListener _domEventListener; Event _currEvent; Factory* _factory; InterpreterHTTPServlet* _httpServlet; InterpreterWebSocketServlet* _wsServlet; std::set _monitors; virtual void executeContent(const Arabica::DOM::Node& content, bool rethrow = false); virtual void executeContent(const Arabica::DOM::NodeList& content, bool rethrow = false); virtual void executeContent(const Arabica::XPath::NodeSet& content, bool rethrow = false); void processContentElement(const Arabica::DOM::Node& element, Arabica::DOM::Node& dom, std::string& text, std::string& expr); void processParamChilds(const Arabica::DOM::Node& element, std::multimap& params); void processDOMorText(const Arabica::DOM::Node& element, Arabica::DOM::Node& dom, std::string& text); virtual void send(const Arabica::DOM::Node& element); virtual void invoke(const Arabica::DOM::Node& element); virtual void cancelInvoke(const Arabica::DOM::Node& element); virtual void internalDoneSend(const Arabica::DOM::Node& state); static void delayedSend(void* userdata, std::string eventName); void returnDoneEvent(const Arabica::DOM::Node& state); bool hasConditionMatch(const Arabica::DOM::Node& conditional); bool isInFinalState(const Arabica::DOM::Node& state); bool parentIsScxmlState(const Arabica::DOM::Node& state); long _lastRunOnMainThread; std::string _name; std::string _sessionId; unsigned int _capabilities; Data _cmdLineOptions; IOProcessor getIOProcessor(const std::string& type); std::map _ioProcessors; std::map > _sendIds; std::map _invokers; std::map, ExecutableContent> _executableContent; /// TODO: We need to remember to adapt them when the DOM is operated upon std::map > _cachedStates; std::map _cachedURLs; friend class USCXMLInvoker; friend class SCXMLIOProcessor; friend class Interpreter; }; class USCXML_API Interpreter { public: static Interpreter fromDOM(const Arabica::DOM::Document& dom, const NameSpaceInfo& nameSpaceInfo); static Interpreter fromXML(const std::string& xml); static Interpreter fromURI(const std::string& uri); static Interpreter fromInputSource(Arabica::SAX::InputSource& source); static Interpreter fromClone(const Interpreter& other); Interpreter() : _impl() {} // the empty, invalid interpreter Interpreter(boost::shared_ptr const impl) : _impl(impl) { } Interpreter(const Interpreter& other) : _impl(other._impl) { } virtual ~Interpreter() {}; operator bool() const { return _impl; } bool operator< (const Interpreter& other) const { return _impl < other._impl; } bool operator==(const Interpreter& other) const { return _impl == other._impl; } bool operator!=(const Interpreter& other) const { return _impl != other._impl; } Interpreter& operator= (const Interpreter& other) { _impl = other._impl; return *this; } void start() { return _impl->start(); } void stop() { return _impl->stop(); } void join() { return _impl->join(); }; bool isRunning() { return _impl->isRunning(); } void interpret() { _impl->interpret(); }; InterpreterState step(bool blocking = false) { return _impl->step(blocking); }; void addMonitor(InterpreterMonitor* monitor) { return _impl->addMonitor(monitor); } void removeMonitor(InterpreterMonitor* monitor) { return _impl->removeMonitor(monitor); } void setSourceURI(std::string sourceURI) { return _impl->setSourceURI(sourceURI); } URL getSourceURI() { return _impl->getSourceURI(); } URL getBaseURI() { return _impl->getBaseURI(); } std::string getXMLPrefixForNS(const std::string& ns) const { return _impl->getXMLPrefixForNS(ns); } void setNameSpaceInfo(const NameSpaceInfo& nsInfo) { _impl->setNameSpaceInfo(nsInfo); } NameSpaceInfo getNameSpaceInfo() const { return _impl->getNameSpaceInfo(); } void setCmdLineOptions(std::map params) { return _impl->setCmdLineOptions(params); } Data getCmdLineOptions() { return _impl->getCmdLineOptions(); } InterpreterHTTPServlet* getHTTPServlet() { return _impl->getHTTPServlet(); } DataModel getDataModel() { return _impl->getDataModel(); } void setParentQueue(uscxml::concurrency::BlockingQueue* parentQueue) { return _impl->setParentQueue(parentQueue); } void setFactory(Factory* factory) { return _impl->setFactory(factory); } Factory* getFactory() { return _impl->getFactory(); } Arabica::XPath::NodeSet getNodeSetForXPath(const std::string& xpathExpr) { return _impl->getNodeSetForXPath(xpathExpr); } void inline receiveInternal(const Event& event) { return _impl->receiveInternal(event); } void receive(const Event& event, bool toFront = false) { return _impl->receive(event, toFront); } Event getCurrentEvent() { return _impl->getCurrentEvent(); } bool isInState(const std::string& stateId) { return _impl->isInState(stateId); } Arabica::XPath::NodeSet getConfiguration() { return _impl->getConfiguration(); } Arabica::XPath::NodeSet getBasicConfiguration() { return _impl->getBasicConfiguration(); } void setConfiguration(const std::vector& states) { return _impl->setConfiguration(states); } void setInvokeRequest(const InvokeRequest& req) { return _impl->setInvokeRequest(req); } Arabica::DOM::Node getState(const std::string& stateId) { return _impl->getState(stateId); } Arabica::XPath::NodeSet getStates(const std::list& stateIds) { return _impl->getStates(stateIds); } Arabica::XPath::NodeSet getAllStates() { return _impl->getAllStates(); } Arabica::DOM::Document getDocument() const { return _impl->getDocument(); } void setCapabilities(unsigned int capabilities) { return _impl->setCapabilities(capabilities); } void setName(const std::string& name) { return _impl->setName(name); } const std::string& getName() { return _impl->getName(); } const std::string& getSessionId() { return _impl->getSessionId(); } DelayedEventQueue* getDelayQueue() { return _impl->getDelayQueue(); } const std::map& getIOProcessors() { return _impl->getIOProcessors(); } const std::map& getInvokers() { return _impl->getInvokers(); } bool runOnMainThread(int fps, bool blocking = true) { return _impl->runOnMainThread(fps, blocking); } static bool isMember(const Arabica::DOM::Node& node, const Arabica::XPath::NodeSet& set) { return InterpreterImpl::isMember(node, set); } void dump() { return _impl->dump(); } bool hasLegalConfiguration() { return _impl->hasLegalConfiguration(); } bool isLegalConfiguration(const Arabica::XPath::NodeSet& config) { return _impl->isLegalConfiguration(config); } bool isLegalConfiguration(const std::vector& config) { return _impl->isLegalConfiguration(config); } #if 0 static bool isState(const Arabica::DOM::Node& state) { return InterpreterImpl::isState(state); } static bool isPseudoState(const Arabica::DOM::Node& state) { return InterpreterImpl::isPseudoState(state); } static bool isTransitionTarget(const Arabica::DOM::Node& elem) { return InterpreterImpl::isTransitionTarget(elem); } static bool isTargetless(const Arabica::DOM::Node& transition) { return InterpreterImpl::isTargetless(transition); } static bool isAtomic(const Arabica::DOM::Node& state) { return InterpreterImpl::isAtomic(state); } static bool isFinal(const Arabica::DOM::Node& state) { return InterpreterImpl::isFinal(state); } static bool isHistory(const Arabica::DOM::Node& state) { return InterpreterImpl::isHistory(state); } static bool isParallel(const Arabica::DOM::Node& state) { return InterpreterImpl::isParallel(state); } static bool isCompound(const Arabica::DOM::Node& state) { return InterpreterImpl::isCompound(state); } static bool isDescendant(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2) { return InterpreterImpl::isDescendant(s1, s2); } static std::list tokenizeIdRefs(const std::string& idRefs) { return InterpreterImpl::tokenizeIdRefs(idRefs); } static bool nameMatch(const std::string& transitionEvent, const std::string& event) { return InterpreterImpl::nameMatch(transitionEvent, event); } static std::string spaceNormalize(const std::string& text) { return InterpreterImpl::spaceNormalize(text); } bool isInitial(const Arabica::DOM::Node& state) { return _impl->isInitial(state); } Arabica::XPath::NodeSet getInitialStates(Arabica::DOM::Node state = Arabica::DOM::Node()) { return _impl->getInitialStates(state); } static Arabica::XPath::NodeSet getChildStates(const Arabica::DOM::Node& state) { return InterpreterImpl::getChildStates(state); } static Arabica::XPath::NodeSet getChildStates(const Arabica::XPath::NodeSet& state) { return InterpreterImpl::getChildStates(state); } static Arabica::DOM::Node getParentState(const Arabica::DOM::Node& element) { return InterpreterImpl::getParentState(element); } static Arabica::DOM::Node getAncestorElement(const Arabica::DOM::Node& node, const std::string tagName) { return InterpreterImpl::getAncestorElement(node, tagName); } Arabica::XPath::NodeSet getTargetStates(const Arabica::DOM::Node& transition) { return _impl->getTargetStates(transition); } Arabica::XPath::NodeSet getTargetStates(const Arabica::XPath::NodeSet& transitions) { return _impl->getTargetStates(transitions); } Arabica::DOM::Node getSourceState(const Arabica::DOM::Node& transition) { return _impl->getSourceState(transition); } static Arabica::XPath::NodeSet filterChildElements(const std::string& tagname, const Arabica::DOM::Node& node, bool recurse = false) { return InterpreterImpl::filterChildElements(tagname, node, recurse); } static Arabica::XPath::NodeSet filterChildElements(const std::string& tagName, const Arabica::XPath::NodeSet& nodeSet, bool recurse = false) { return InterpreterImpl::filterChildElements(tagName, nodeSet, recurse); } static Arabica::XPath::NodeSet filterChildType(const Arabica::DOM::Node_base::Type type, const Arabica::DOM::Node& node, bool recurse = false) { return InterpreterImpl::filterChildType(type, node, recurse); } static Arabica::XPath::NodeSet filterChildType(const Arabica::DOM::Node_base::Type type, const Arabica::XPath::NodeSet& nodeSet, bool recurse = false) { return InterpreterImpl::filterChildType(type, nodeSet, recurse); } Arabica::DOM::Node findLCCA(const Arabica::XPath::NodeSet& states) { return _impl->findLCCA(states); } Arabica::XPath::NodeSet getProperAncestors(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2) { return _impl->getProperAncestors(s1, s2); } #endif boost::shared_ptr getImpl() const { return _impl; } static std::map > getInstances(); protected: boost::shared_ptr _impl; static std::map > _instances; static tthread::recursive_mutex _instanceMutex; }; class USCXML_API InterpreterMonitor { public: virtual ~InterpreterMonitor() {} virtual void beforeProcessingEvent(Interpreter interpreter, const Event& event) {} virtual void beforeMicroStep(Interpreter interpreter) {} virtual void beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) {} virtual void afterExitingState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) {} virtual void beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Element& element) {} virtual void afterExecutingContent(Interpreter interpreter, const Arabica::DOM::Element& element) {} virtual void beforeUninvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} virtual void afterUninvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} virtual void beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) {} virtual void afterTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) {} virtual void beforeEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) {} virtual void afterEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) {} virtual void beforeInvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} virtual void afterInvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} virtual void afterMicroStep(Interpreter interpreter) {} virtual void onStableConfiguration(Interpreter interpreter) {} virtual void beforeCompletion(Interpreter interpreter) {} virtual void afterCompletion(Interpreter interpreter) {} }; } #endif