From 6926b730e79e1e7bbb5080c4182d8c2862b08fdc Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Sun, 16 Feb 2014 13:06:12 +0100 Subject: Started uscxml debugger --- CMakeLists.txt | 10 +- apps/uscxml-debug.cpp | 317 +++++++++++++++++++++++++++++++++++++++ src/uscxml/Interpreter.cpp | 13 -- src/uscxml/Message.cpp | 18 ++- src/uscxml/server/HTTPServer.cpp | 4 +- 5 files changed, 341 insertions(+), 21 deletions(-) create mode 100644 apps/uscxml-debug.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 968e4d9..a0377bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -923,10 +923,18 @@ if (NOT CMAKE_CROSSCOMPILING) set_target_properties(uscxml-browser PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) # cotire(uscxml-browser) endif() - set_target_properties(uscxml-browser PROPERTIES FOLDER "Apps") install_executable(TARGETS uscxml-browser COMPONENT tools) + add_executable(uscxml-debug apps/uscxml-debug.cpp) + target_link_libraries(uscxml-debug uscxml) + if (NOT CMAKE_CROSSCOMPILING) + set_target_properties(uscxml-debug PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) +# cotire(uscxml-browser) + endif() + set_target_properties(uscxml-debug PROPERTIES FOLDER "Apps") + install_executable(TARGETS uscxml-debug COMPONENT tools) + if (PROTOBUF_FOUND AND OFF) file(GLOB W3C-MMI-COMMON ${PROJECT_SOURCE_DIR}/apps/w3c-mmi/*.cpp ${PROJECT_SOURCE_DIR}/apps/w3c-mmi/*.h) diff --git a/apps/uscxml-debug.cpp b/apps/uscxml-debug.cpp new file mode 100644 index 0000000..5b9c989 --- /dev/null +++ b/apps/uscxml-debug.cpp @@ -0,0 +1,317 @@ +#include "uscxml/config.h" +#include "uscxml/Interpreter.h" +#include "uscxml/DOMUtils.h" +#include "uscxml/UUID.h" +#include "uscxml/debug/SCXMLDotWriter.h" +#include +#include // mktime + +#include + +#ifdef HAS_SIGNAL_H +#include +#endif + +#ifdef HAS_EXECINFO_H +#include +#endif + +#ifdef HAS_DLFCN_H +#include +#endif + +using namespace uscxml; + +class Debugger : public HTTPServlet, public InterpreterMonitor, public google::LogSink { +public: + class BreakPoint { + public: + + enum When { + UNDEF_WHEN, AFTER, BEFORE, ON + }; + + enum Subject { + UNDEF_SUBJECT, STATE, TRANSITION, CONFIGURATION, EVENT + }; + + enum Action { + UNDEF_ACTION, ENTER, EXIT + }; + + BreakPoint(const Data& data) { + subject = UNDEF_SUBJECT; + when = UNDEF_WHEN; + action = UNDEF_ACTION; + + if (data.hasKey("action")) { + if (false) { + } else if (iequals(data["action"], "enter")) { + action = ENTER; + } else if (iequals(data["action"], "exit")) { + action = EXIT; + } + } + if (data.hasKey("time")) { + if (false) { + } else if (iequals(data["time"], "before")) { + when = BEFORE; + } else if (iequals(data["time"], "after")) { + when = AFTER; + } else if (iequals(data["time"], "on")) { + when = ON; + } + } + if (data.hasKey("subject")) { + if (false) { + } else if (iequals(data["subject"], "state")) { + subject = STATE; + if (data.hasKey("stateId")) + state = data["stateId"].atom; + } else if (iequals(data["subject"], "transition")) { + subject = TRANSITION; + if (data.hasKey("fromStateId")) + fromState = data["fromStateId"].atom; + if (data.hasKey("toStateId")) + fromState = data["toStateId"].atom; + } else if (iequals(data["subject"], "event")) { + subject = EVENT; + if (data.hasKey("eventName")) + eventName = data["eventName"].atom; + } else if (iequals(data["subject"], "configuration")) { + subject = CONFIGURATION; + } else if (iequals(data["subject"], "microstep")) { + subject = CONFIGURATION; + } + } + + if (data.hasKey("condition")) { + condition = data["condition"].atom; + } + } + + bool isValid() { + return true; + } + + protected: + When when; + Subject subject; + Action action; + + std::string state; + std::string toState; + std::string fromState; + std::string eventName; + + std::string condition; + + }; + + class LogMessage { + public: + google::LogSeverity severity; + std::string full_filename; + std::string base_filename; + int line; + const struct ::tm* tm_time; + std::string message; + std::string formatted; + + Data toData() { + Data data; + data.compound["severity"] = severity; + data.compound["fullFilename"] = Data(full_filename, Data::VERBATIM); + data.compound["baseFilename"] = Data(base_filename, Data::VERBATIM); + data.compound["line"] = line; + data.compound["message"] = Data(message, Data::VERBATIM); + data.compound["time"] = mktime((struct ::tm*)tm_time); + data.compound["formatted"] = Data(formatted, Data::VERBATIM); + return data; + } + }; + + std::string _url; + Interpreter _interpreter; + HTTPServer::Request _debugReq; + tthread::recursive_mutex _mutex; + std::list _logMessages; + std::map _breakPoints; + + virtual ~Debugger() { + } + + // callbacks from http requests + + void debug(const HTTPServer::Request& request) { + tthread::lock_guard lock(_mutex); + + // save request and run until we reach a breakpoint + _debugReq = request; + _interpreter.interpret(); + } + + void connect(const HTTPServer::Request& request) { + Data replyData; + replyData.compound["status"] = Data("success", Data::VERBATIM); + returnData(request, replyData); + } + + void disconnect(const HTTPServer::Request& request) { + Data replyData; + replyData.compound["status"] = Data("success", Data::VERBATIM); + returnData(request, replyData); + } + + void prepare(const HTTPServer::Request& request) { + tthread::lock_guard lock(_mutex); + + _interpreter = Interpreter::fromXML(request.data["content"].atom); + + Data replyData; + if (_interpreter) { + // register ourself as a monitor + _interpreter.addMonitor(this); + replyData.compound["status"] = Data("success", Data::VERBATIM); + } else { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } + returnData(request, replyData); + } + + void addBreakPoint(const HTTPServer::Request& request) { + BreakPoint breakPoint(request.data["content"]); + Data replyData; + if (breakPoint.isValid()) { + replyData.compound["status"] = Data("success", Data::VERBATIM); + } else { + replyData.compound["status"] = Data("failure", Data::VERBATIM); + } + returnData(request, replyData); + } + + // helpers + + void returnData(const HTTPServer::Request& request, Data replyData) { + //always include latest log + while(_logMessages.size() > 0) { + replyData.compound["log"].array.push_back(_logMessages.front().toData()); + _logMessages.pop_front(); + } + + HTTPServer::Reply reply(request); + reply.headers["Content-type"] = "application/json"; + reply.headers["Access-Control-Allow-Origin"] = "*"; + reply.content = Data::toJSON(replyData); + HTTPServer::reply(reply); + } + + bool isCORS(const HTTPServer::Request& request) { + return (request.data["type"].atom == "options" && + request.data["header"].hasKey("Origin") && + request.data["header"].hasKey("Access-Control-Request-Method")); + } + + void handleCORS(const HTTPServer::Request& request) { + HTTPServer::Reply corsReply(request); + if (request.data["header"].hasKey("Origin")) { + corsReply.headers["Access-Control-Allow-Origin"] = request.data["header"]["Origin"].atom; + } else { + corsReply.headers["Access-Control-Allow-Origin"] = "*"; + } + if (request.data["header"].hasKey("Access-Control-Request-Method")) + corsReply.headers["Access-Control-Allow-Methods"] = request.data["header"]["Access-Control-Request-Method"].atom; + if (request.data["header"].hasKey("Access-Control-Request-Headers")) + corsReply.headers["Access-Control-Allow-Headers"] = request.data["header"]["Access-Control-Request-Headers"].atom; + +// std::cout << "CORS!" << std::endl << request << std::endl; + HTTPServer::reply(corsReply); + } + + // HTTPServlet + + bool httpRecvRequest(const HTTPServer::Request& request) { + if (isCORS(request)) { + handleCORS(request); + return true; + } + + std::cout << Data::toJSON(request.data) << std::endl; + + if (false) { + } else if (boost::istarts_with(request.data["path"].atom, "/connect")) { + connect(request); + } else if (boost::istarts_with(request.data["path"].atom, "/disconnect")) { + disconnect(request); + } else if (boost::istarts_with(request.data["path"].atom, "/prepare")) { + prepare(request); + } else if (boost::istarts_with(request.data["path"].atom, "/debug")) { + debug(request); + } else if (boost::istarts_with(request.data["path"].atom, "/breakpoint/add")) { + addBreakPoint(request); + } + return true; + } + void setURL(const std::string& url) { + _url = url; + } + + // InterpreterMonitor + void onStableConfiguration(Interpreter interpreter) { + } + + void beforeCompletion(Interpreter interpreter) {} + void afterCompletion(Interpreter interpreter) {} + void beforeMicroStep(Interpreter interpreter) {} + void beforeTakingTransitions(Interpreter interpreter, const Arabica::XPath::NodeSet& transitions) {} + void beforeEnteringStates(Interpreter interpreter, const Arabica::XPath::NodeSet& statesToEnter) {} + void afterEnteringStates(Interpreter interpreter) {} + void beforeExitingStates(Interpreter interpreter, const Arabica::XPath::NodeSet& statesToExit) {} + void afterExitingStates(Interpreter interpreter) {} + + // google::LogSink + + virtual void send(google::LogSeverity severity, const char* full_filename, + const char* base_filename, int line, + const struct ::tm* tm_time, + const char* message, size_t message_len) { + + tthread::lock_guard lock(_mutex); + + LogMessage msg; + msg.severity = severity; + msg.full_filename = full_filename; + msg.base_filename = base_filename; + msg.line = line; + msg.tm_time = tm_time; + msg.message = std::string(message, message_len); + msg.formatted = ToString(severity, base_filename, line, tm_time, message, message_len); + + _logMessages.push_back(msg); + } + +}; + + +int main(int argc, char** argv) { + using namespace uscxml; + + InterpreterOptions options = InterpreterOptions::fromCmdLine(argc, argv); + Debugger debugger; + + // setup logging + google::InitGoogleLogging(argv[0]); + google::AddLogSink(&debugger); + + // setup HTTP server + HTTPServer::getInstance(18088, 18089, NULL); + + + HTTPServer::getInstance()->registerServlet("/", &debugger); + + while(true) + tthread::this_thread::sleep_for(tthread::chrono::seconds(1)); + + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index fa3e227..6334065 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -707,24 +707,11 @@ void InterpreterImpl::processDOMorText(const Arabica::DOM::Node& el Arabica::SAX::InputSource inputSource; inputSource.setByteStream(ssPtr); -// parser.setFeature(Arabica::SAX::FeatureNames().external_general, true); - if (parser.parse(inputSource) && parser.getDocument()) { Document doc = parser.getDocument(); dom = doc.getDocumentElement(); -#if 0 - Node content = doc.getDocumentElement(); - assert(content.getNodeType() == Node_base::ELEMENT_NODE); - Node container = doc.createElement("container"); - dom.replaceChild(container, content); - container.appendChild(content); -// std::cout << dom << std::endl; -#endif return; } else { - if (parser.errorsReported()) { - LOG(ERROR) << parser.errors(); - } text = srcContent.str(); return; } diff --git a/src/uscxml/Message.cpp b/src/uscxml/Message.cpp index b664fd1..bbd7f28 100644 --- a/src/uscxml/Message.cpp +++ b/src/uscxml/Message.cpp @@ -318,8 +318,7 @@ Data Data::fromXML(const std::string& xmlString) { Data Data::fromJSON(const std::string& jsonString) { Data data; - std::string trimmed = jsonString; - boost::trim(trimmed); + std::string trimmed = boost::trim_copy(jsonString); if (trimmed.length() == 0) return data; @@ -387,23 +386,29 @@ Data Data::fromJSON(const std::string& jsonString) { size_t currTok = 0; do { + // used for debugging // jsmntok_t t2 = t[currTok]; // std::string value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); switch (t[currTok].type) { case JSMN_STRING: dataStack.back()->type = Data::VERBATIM; - case JSMN_PRIMITIVE: - dataStack.back()->atom = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); + case JSMN_PRIMITIVE: { + std::string value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); + if (dataStack.back()->type == Data::VERBATIM) { + boost::replace_all(value, "\\\"", "\""); + } + dataStack.back()->atom = value; dataStack.pop_back(); currTok++; break; + } case JSMN_OBJECT: case JSMN_ARRAY: tokenStack.push_back(t[currTok]); currTok++; break; } - + // used for debugging // t2 = t[currTok]; // value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); @@ -419,7 +424,8 @@ Data Data::fromJSON(const std::string& jsonString) { if (tokenStack.back().type == JSMN_OBJECT && (t[currTok].type == JSMN_PRIMITIVE || t[currTok].type == JSMN_STRING)) { // grab key and push new data - dataStack.push_back(&(dataStack.back()->compound[trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start)])); + std::string value = trimmed.substr(t[currTok].start, t[currTok].end - t[currTok].start); + dataStack.push_back(&(dataStack.back()->compound[value])); currTok++; } if (tokenStack.back().type == JSMN_ARRAY) { diff --git a/src/uscxml/server/HTTPServer.cpp b/src/uscxml/server/HTTPServer.cpp index d9fbc77..6c26811 100644 --- a/src/uscxml/server/HTTPServer.cpp +++ b/src/uscxml/server/HTTPServer.cpp @@ -440,7 +440,9 @@ void HTTPServer::processByMatchingServlet(const Request& request) { while(servletIter != _httpServlets.end()) { // is the servlet path a prefix of the actual path? std::string servletPath = "/" + servletIter->first; - if (iequals(actualPath.substr(0, servletPath.length()), servletPath) && // servlet path is a prefix + if (servletIter->first.length() == 0) { + matches.insert(std::make_pair(servletPath, servletIter->second)); // single servlet at root + } else if (iequals(actualPath.substr(0, servletPath.length()), servletPath) && // servlet path is a prefix iequals(actualPath.substr(servletPath.length(), 1), "/")) { // and next character is a '/' matches.insert(std::make_pair(servletPath, servletIter->second)); } -- cgit v0.12