From 8e62f3801b98bf4b7f7f85b848b2fe6339c99162 Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Sun, 14 Feb 2016 15:14:28 +0100 Subject: More work on ANSI C transformation --- docs/NATIVE_CODE.md | 138 +++++++++++++++------ src/uscxml/plugins/DataModel.h | 13 ++ .../ecmascript/JavaScriptCore/JSCDataModel.cpp | 110 +++++++++------- src/uscxml/transform/ChartToC.cpp | 67 +++++++--- test/src/test-c-machine.cpp | 125 +++++++++---------- test/src/test-c-machine.scxml.c | 29 ++++- 6 files changed, 313 insertions(+), 169 deletions(-) diff --git a/docs/NATIVE_CODE.md b/docs/NATIVE_CODE.md index 91af599..c6f2416 100644 --- a/docs/NATIVE_CODE.md +++ b/docs/NATIVE_CODE.md @@ -9,9 +9,8 @@ native description of the state-chart. To embed the control flow described within an SCXML document in most variances of the C language, we provide a transformation onto ANSI C (C89) as a proper -subset of virtually any more modern C/C++ dialect. There are two general -approaches to achieve this. In any case, you need to transform your SCXML -state-chart onto ANSI C by invoking uscxml-transform: +subset of virtually any more modern C/C++ dialect. First, you need to transform +your SCXML state-chart onto ANSI C by invoking uscxml-transform: $ uscxml-transform -tc -i INPUT_FILE -o OUTPUT_FILE @@ -47,9 +46,21 @@ allow you to influence important characteristics of you state-machine. state and transitions[USCXML_MAX_NR_TRANS_BYTES] for one bit per transition. + * **USCXML_ELEM_X_IS_SET**: + + These macros are defined for DATA, PARAM and + DONEDATA and allow to iterate instances. For all of the + corresponding SCXML elements, a callback might be supplied with a set + of instances (e.g. invoke takes a set of <param> + elements). They are contained in a continuous memory region and can be + iterated by merely increasing the respective pointer. The macros allow + to check whether the pointer is still valid or whether there are no + more instances of the given structure. + * There are some other macros defined, but they are rather for micro-optimizations. Have a look at a generated file. + 2. All compound data **types** (struct) to encode an SCXML state-machine. These will refer to the macros above to require memory for a state-chart's states and transitions, so make sure that the macros are set if you @@ -64,7 +75,8 @@ document twice, you might end up with duplicate symbols, yet again, the state-chart's will be functionally identical as they contained the same content. In order for not having to guess the prefix when referring to any machine - in application code, the tranformation will define three additional macros: + in user-supplied application code, the tranformation will define three + additional macros: #ifndef USCXML_MACHINE # define USCXML_MACHINE _uscxml_BC220711_machine @@ -89,42 +101,41 @@ inclusion by pre-defining respective macros (have a look at a generated source file). Now in order to actually use an SCXML document to manage the control flow among -a set of functions, there are two general approaches. Both use the generated -ANSI C source code above, but require more or less resources at runtime as -detailled below. +a set of functions, you will need to allocate and clear memory for a +uscxml_ctx structure, set its machine field to a +uscxml_machine structure and register user-supplied callbacks. + +### Context Callbacks -### Fully Compliant +An SCXML interpreter does more than to perform a series of microsteps for an +event over a set of states and transitions and there are quite a few +responsibilities not implemented in the generated ANSI C code. These will be +delegated to user-supplied code via callbacks if they are required for the interpretation of a given SCXML file. -An SCXML interpreter does more than to perform a series of microsteps for event -over a set of states and transitions and there are quite a few responsibilities -not implemented in the generated ANSI C code: +There is already a scaffolding providing most of the callbacks implemented in +the [test-c-machine.c](../test/src/test-c-machine.c) test file and you can just +isolate the StateMachine class contained within. It does everything +but custom invocations but requires linking with libuscxml for the +datamodel implementations and several other functions. Depending on the number +of SCXML language features you employ, you can get away with providing +considerably fewer callbacks as detailled below. 1. **Event Queues**: A compliant interpreter is required to maintain two event queues, an - internal and an external one. With the generated ANSI C source, these are - integrated via four callbacks and will need to be implemented in - user-supplied code: + internal and an external one. These queues can grow to arbitrary size and + we made a decision, not to employ malloc for heap allocations in + the generated ANSI-C source. As such, it is the responsibility of the + user-supplied code to manage the queues via the following callbacks: - 1. **uscxml_ctx.dequeue_internal**: This callback is invoked - whenever the interpreter needs an event from the internal event queue. It - is passed an instance of a uscxml_ctx structure and is supposed to - return an opaque pointer to an event. If the internal queue is empty, - NULL is to be returned. + | Callback | Comments | Required For | + |-|-|-| + | **dequeue_internal** | This callback is invoked whenever the interpreter needs an event from the internal event queue. It is passed an instance of a uscxml_ctx structure and is supposed to return an opaque pointer to an event. If the internal queue is empty, NULL is to be returned. | Dequeuing *internal* events | + | **dequeue_external** | This callback is functionally equivalent to uscxml_ctx.dequeue_internal but invoked, when an external event is to be dequeued. | Dequeuing *external* events | + | **exec_content_send** | Whenever there is an <send> element encountered in executable content, the generated ANSI C code will invoke this callback with a context and an uscxml_elem_send instance and the user code registered at the callback is expected to handle the send request as per SCXML recommendation. | Delivering events via <send> | + | **exec_content_raise** | This callback is invoked for any <raise> element processed as part of executable content and is expected to deliver an event to the internal event queue. | Delivering events via <raise> | - 2. **uscxml_ctx.dequeue_external**: This callback is functionally - equivalent to uscxml_ctx.dequeue_internal but invoked, when an - external event is to be dequeued. - - 3. **uscxml_ctx.exec_content_send**: Whenever there is an - <send> element encountered in executable content, the generated - ANSI C code will invoke this callback with a context and an - uscxml_elem_send instance and the user code registered at the - callback is expected to handle the send request as per recommendation. - - 4. **uscxml_ctx.exec_content_raise**: This callback is invoked for - any <raise> element processed as part of executable content and - is expected to deliver an event to the internal event queue. + The events themselves are represented as opaque pointers and the generated ANSI-C code will never access any of its members. 2. **Transition Matching / Enabling** @@ -132,11 +143,62 @@ not implemented in the generated ANSI C code: source will already make sure that only valid sets of transitions can be selected to constitute the optimal transition set for a microstep, but user-supplied code will have to decide whether a transition is matched and - enabled. This is done via the **uscxml_ctx.is_enabled** callback. - It receives a context, a uscxml_transition structure and the - opaque event pointer and will have to return 0 for when the - transition is not matched and enabled by the given event and 1 if - it is. + enabled. + + | Callback | Comments | Required For | + |-|-|-| + | **is_matched** | This callback receives a context, an uscxml_transition structure and the opaque event pointer. It is expected to return 0 for when the transition is not matched by the given event and 1 if it is. You can assume that non-spontaneous transitions are not checked for the null-event and vice versa. | Event name *matching* of a transition. | + | **is_enabled** | This callback receives a context and a uscxml_transition structure. It is expected to return 0 for when the transition is not enabled and 1 if it is. Only transitions with an actual condition attribute will be checked. | Determining *enabled* status of a transition. | + +3. **Invocations** + + The transformation will generate machine structures for all SCXML + state-charts found within a document, but will make no attempt to invoke + them automatically. Instead, the generated ANSI-C code will call upon the respective callback in the uscxml_ctx structure: + + | Callback | Comments | Required For | + |-|-|-| + | **invoke** | The call back is provided with a context and an uscxml_elem_invoke structure. This structure will contain all the information pertaining to the <invoke> element, with an additional optional member machine, which points to the uscxml_machine structure in case another, nested SCXML machine is to be invoked. It is your responsibility to create a uscxml_ctx for this new machine and run it or start any other type of invocation specified in the given structure. | Invoking external components via <invoke> | + +4. **Executable Content** + + In general, every instance of an element for executable content has a respective callback in the uscxml_ctx structure. There are a few examples, wherein an element is transformed onto control flow that will invoke multiple callbacks: + + | Callback | Comments | Required For | + |-|-|-| + | **exec_content_log** | | <log> | + | **exec_content_raise** | | <raise> | + | **exec_content_send** | | <send> | + | **is_true** | | <if> / <elseif> / <else> | + | **exec_content_foreach_init** **exec_content_foreach_next** **exec_content_foreach_done** | | <foreach> | + | **exec_content_assign** | | <assign> | + | **exec_content_init** | | <data> | + | **exec_content_cancel** | | <cancel> | + | **exec_content_script** | | <script> | + +5. **Done Events** + + Finally, there is a callback that is invoked if a <final> state is entered. + + | Callback | Comments | Required For | + |-|-|-| + | **raise_done_event** | The callback is provided with a context, the state for which a done event is to be raised and a uscxml_elem_donedata structure. | <final> | + + +### Inline SCXML + +An alternative to writing an external SCXML file is to embed the document into the actual C code as a comment: + + /** INLINE SCXML BEGIN + + + + enteredFoo(); + + + + INLINE SCXML END */ +If you pass an arbitrary input file to uscxml_transform, it will realize that it does not constitute a proper SCXML document and attempt to isolate an actual SCXML state-chart by searching for the string literals INLINE SCXML BEGIN and INLINE SCXML END. Everything in between is isolated and treated as if it was a proper SCXML document. -### Light-Weight +Here, you can also see a variation with the datamode="native" attribute. If this is given, the transformation will write any text child of executable content as an unescaped, verbatim string literal into the respective function, allowing you to address any of your C functions and variables directly. \ No newline at end of file diff --git a/src/uscxml/plugins/DataModel.h b/src/uscxml/plugins/DataModel.h index df0143d..f229a94 100644 --- a/src/uscxml/plugins/DataModel.h +++ b/src/uscxml/plugins/DataModel.h @@ -91,6 +91,19 @@ public: virtual bool isDeclared(const std::string& expr) = 0; + /** + * test147: + * + * + * test150: + * + * [1,2,3] + * + * + * test277: + * + * + */ virtual void assign(const Arabica::DOM::Element& assignElem, const Arabica::DOM::Node& node, const std::string& content) = 0; diff --git a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp index fd4da3c..86bafd3 100644 --- a/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp +++ b/src/uscxml/plugins/datamodel/ecmascript/JavaScriptCore/JSCDataModel.cpp @@ -625,11 +625,60 @@ JSValueRef JSCDataModel::evalAsValue(const std::string& expr, bool dontThrow) { return result; } +JSValueRef JSCDataModel::getNodeAsValue(const Node& node) { + switch (node.getNodeType()) { + case Node_base::ELEMENT_NODE: { + TO_JSC_DOMVALUE(Element); + } + case Node_base::TEXT_NODE: { + TO_JSC_DOMVALUE(Text); + } + case Node_base::CDATA_SECTION_NODE: { + TO_JSC_DOMVALUE(CDATASection); + } + case Node_base::DOCUMENT_NODE: { + TO_JSC_DOMVALUE(Document); + } + default: { + TO_JSC_DOMVALUE(Node); + } + } +} + +void JSCDataModel::assign(const std::string& location, const Data& data) { + + // flags on attribute are ignored? + if (location.compare("_sessionid") == 0) // test 322 + ERROR_EXECUTION_THROW("Cannot assign to _sessionId"); + if (location.compare("_name") == 0) + ERROR_EXECUTION_THROW("Cannot assign to _name"); + if (location.compare("_ioprocessors") == 0) // test 326 + ERROR_EXECUTION_THROW("Cannot assign to _ioprocessors"); + if (location.compare("_invokers") == 0) + ERROR_EXECUTION_THROW("Cannot assign to _invokers"); + if (location.compare("_event") == 0) + ERROR_EXECUTION_THROW("Cannot assign to _event"); + + JSValueRef exception = NULL; + if (data.node) { + JSObjectSetProperty(_ctx, JSContextGetGlobalObject(_ctx), JSStringCreateWithUTF8CString(location.c_str()), getNodeAsValue(data.node), 0, &exception); + } else { + evalAsValue(location + " = " + Data::toJSON(data)); + } + + /** + * test157: We need to evluate, as this will not throw for 'continue' = Var[5] in + */ +// JSObjectSetProperty(_ctx, JSContextGetGlobalObject(_ctx), JSStringCreateWithUTF8CString(location.c_str()), getDataAsValue(data), 0, &exception); + + if (exception) + handleException(exception); +} + void JSCDataModel::assign(const Element& assignElem, const Node& node, const std::string& content) { std::string key; - JSValueRef exception = NULL; if (HAS_ATTR(assignElem, "id")) { key = ATTR(assignElem, "id"); } else if (HAS_ATTR(assignElem, "location")) { @@ -638,63 +687,30 @@ void JSCDataModel::assign(const Element& assignElem, if (key.length() == 0) { ERROR_EXECUTION_THROW("Assign element has neither id nor location"); } - // flags on attribute are ignored? - if (key.compare("_sessionid") == 0) // test 322 - ERROR_EXECUTION_THROW("Cannot assign to _sessionId"); - if (key.compare("_name") == 0) - ERROR_EXECUTION_THROW("Cannot assign to _name"); - if (key.compare("_ioprocessors") == 0) // test 326 - ERROR_EXECUTION_THROW("Cannot assign to _ioprocessors"); - if (key.compare("_invokers") == 0) - ERROR_EXECUTION_THROW("Cannot assign to _invokers"); - if (key.compare("_event") == 0) - ERROR_EXECUTION_THROW("Cannot assign to _event"); if (HAS_ATTR(assignElem, "expr")) { - evalAsValue(key + " = " + ATTR(assignElem, "expr")); + assign(key, Data(ATTR(assignElem, "expr"), Data::INTERPRETED)); } else if (node) { - JSObjectSetProperty(_ctx, JSContextGetGlobalObject(_ctx), JSStringCreateWithUTF8CString(key.c_str()), getNodeAsValue(node), 0, &exception); - if (exception) - handleException(exception); + Data d; + d.node = node; + assign(key, d); } else if (content.size() > 0) { try { - evalAsValue(key + " = " + content); - } catch (...) { - evalAsValue(key + " = " + "\"" + InterpreterImpl::spaceNormalize(content) + "\""); + Data d = Data::fromJSON(content); + if (d.empty()) + throw Event(); + assign(key, Data(d, Data::INTERPRETED)); + } catch (Event e) { + assign(key, Data("\"" + InterpreterImpl::spaceNormalize(content) + "\"", Data::INTERPRETED)); } } else { + JSValueRef exception = NULL; JSObjectSetProperty(_ctx, JSContextGetGlobalObject(_ctx), JSStringCreateWithUTF8CString(key.c_str()), JSValueMakeUndefined(_ctx), 0, &exception); if (exception) handleException(exception); } } -JSValueRef JSCDataModel::getNodeAsValue(const Node& node) { - switch (node.getNodeType()) { - case Node_base::ELEMENT_NODE: { - TO_JSC_DOMVALUE(Element); - } - case Node_base::TEXT_NODE: { - TO_JSC_DOMVALUE(Text); - } - case Node_base::CDATA_SECTION_NODE: { - TO_JSC_DOMVALUE(CDATASection); - } - case Node_base::DOCUMENT_NODE: { - TO_JSC_DOMVALUE(Document); - } - default: { - TO_JSC_DOMVALUE(Node); - } - } -} - -void JSCDataModel::assign(const std::string& location, const Data& data) { - std::stringstream ssJSON; - ssJSON << data; - evalAsValue(location + " = " + ssJSON.str()); -} - void JSCDataModel::init(const Element& dataElem, const Node& node, const std::string& content) { @@ -708,7 +724,9 @@ void JSCDataModel::init(const Element& dataElem, } else if (HAS_ATTR(dataElem, "location")) { key = ATTR(dataElem, "location"); } - evalAsValue(key + " = undefined", true); + if (key.size() > 0) { + evalAsValue(key + " = undefined", true); + } throw e; } } diff --git a/src/uscxml/transform/ChartToC.cpp b/src/uscxml/transform/ChartToC.cpp index c6be393..7cc50fc 100644 --- a/src/uscxml/transform/ChartToC.cpp +++ b/src/uscxml/transform/ChartToC.cpp @@ -690,13 +690,15 @@ void ChartToC::writeTypes(std::ostream& stream) { stream << "typedef struct uscxml_elem_send uscxml_elem_send;" << std::endl; stream << "typedef struct uscxml_elem_param uscxml_elem_param;" << std::endl; stream << "typedef struct uscxml_elem_data uscxml_elem_data;" << std::endl; + stream << "typedef struct uscxml_elem_assign uscxml_elem_assign;" << std::endl; stream << "typedef struct uscxml_elem_donedata uscxml_elem_donedata;" << std::endl; stream << "typedef struct uscxml_elem_foreach uscxml_elem_foreach;" << std::endl; stream << std::endl; stream << "typedef void* (*dequeue_internal_t)(const uscxml_ctx* ctx);" << std::endl; stream << "typedef void* (*dequeue_external_t)(const uscxml_ctx* ctx);" << std::endl; - stream << "typedef int (*is_enabled_t)(const uscxml_ctx* ctx, const uscxml_transition* transition, const void* event);" << std::endl; + stream << "typedef int (*is_enabled_t)(const uscxml_ctx* ctx, const uscxml_transition* transition);" << std::endl; + stream << "typedef int (*is_matched_t)(const uscxml_ctx* ctx, const uscxml_transition* transition, const void* event);" << std::endl; stream << "typedef int (*is_true_t)(const uscxml_ctx* ctx, const char* expr);" << std::endl; stream << "typedef int (*exec_content_t)(const uscxml_ctx* ctx, const uscxml_state* state, const void* event);" << std::endl; stream << "typedef int (*raise_done_event_t)(const uscxml_ctx* ctx, const uscxml_state* state, const uscxml_elem_donedata* donedata);" << std::endl; @@ -709,7 +711,7 @@ void ChartToC::writeTypes(std::ostream& stream) { stream << "typedef int (*exec_content_foreach_init_t)(const uscxml_ctx* ctx, const uscxml_elem_foreach* foreach);" << std::endl; stream << "typedef int (*exec_content_foreach_next_t)(const uscxml_ctx* ctx, const uscxml_elem_foreach* foreach);" << std::endl; stream << "typedef int (*exec_content_foreach_done_t)(const uscxml_ctx* ctx, const uscxml_elem_foreach* foreach);" << std::endl; - stream << "typedef int (*exec_content_assign_t)(const uscxml_ctx* ctx, const char* location, const char* expr);" << std::endl; + stream << "typedef int (*exec_content_assign_t)(const uscxml_ctx* ctx, const uscxml_elem_assign* assign);" << std::endl; stream << "typedef int (*exec_content_init_t)(const uscxml_ctx* ctx, const uscxml_elem_data* data);" << std::endl; stream << "typedef int (*exec_content_cancel_t)(const uscxml_ctx* ctx, const char* sendid, const char* sendidexpr);" << std::endl; stream << "typedef int (*exec_content_finalize_t)(const uscxml_ctx* ctx, const uscxml_elem_invoke* invoker, const void* event);" << std::endl; @@ -748,6 +750,16 @@ void ChartToC::writeTypes(std::ostream& stream) { stream << std::endl; stream << "/**" << std::endl; + stream << " * All information pertaining to an element." << std::endl; + stream << " */" << std::endl; + stream << "struct uscxml_elem_assign {" << std::endl; + stream << " const char* location;" << std::endl; + stream << " const char* expr;" << std::endl; + stream << " const char* content;" << std::endl; + stream << "};" << std::endl; + stream << std::endl; + + stream << "/**" << std::endl; stream << " * All information pertaining to any state element." << std::endl; stream << " */" << std::endl; stream << "struct uscxml_state {" << std::endl; @@ -872,6 +884,7 @@ void ChartToC::writeTypes(std::ostream& stream) { stream << " dequeue_internal_t dequeue_internal;" << std::endl; stream << " dequeue_external_t dequeue_external;" << std::endl; stream << " is_enabled_t is_enabled;" << std::endl; + stream << " is_matched_t is_matched;" << std::endl; stream << " is_true_t is_true;" << std::endl; stream << " raise_done_event_t raise_done_event;" << std::endl; stream << std::endl; @@ -1264,20 +1277,7 @@ void ChartToC::writeExecContent(std::ostream& stream, const Arabica::DOM::Nodeexec_content_assign != NULL) {" << std::endl; stream << padding; - stream << " if ((ctx->exec_content_assign(ctx, "; - stream << (HAS_ATTR(elem, "location") ? "\"" + escape(ATTR(elem, "location")) + "\"" : "NULL") << ", "; - if (HAS_ATTR(elem, "expr")) { - stream << "\"" + escape(ATTR(elem, "expr")) + "\""; - } else { - NodeSet assignTexts = filterChildType(Node_base::TEXT_NODE, elem); - if (assignTexts.size() > 0) { - stream << "\""; - writeExecContent(stream, assignTexts[0], 0); - stream << "\""; - } else { - stream << "NULL"; - } - } + stream << " if ((ctx->exec_content_assign(ctx, &" << _prefix << "_elem_assigns[" << ATTR(elem, "documentOrder") << "]"; stream << ")) != USCXML_ERR_OK) return err;" << std::endl; stream << padding << "} else {" << std::endl; stream << padding << " return USCXML_ERR_MISSING_CALLBACK;" << std::endl; @@ -1485,6 +1485,38 @@ void ChartToC::writeElementInfo(std::ostream& stream) { stream << std::endl; } + NodeSet assigns = DOMUtils::inDocumentOrder(_nsInfo.xmlNSPrefix + "assign", _scxml); + if (assigns.size() > 0) { + _hasElement.insert("assign"); + stream << "static const uscxml_elem_assign " << _prefix << "_elem_assigns[" << assigns.size() << "] = {" << std::endl; + stream << " /* location, expr, content */" << std::endl; + + for (size_t i = 0; i < assigns.size(); i++) { + Element assign(assigns[i]); + + stream << " { "; + stream << (HAS_ATTR(assign, "location") ? "\"" + escape(ATTR(assign, "location")) + "\"" : "NULL") << ", "; + stream << (HAS_ATTR(assign, "expr") ? "\"" + escape(ATTR(assign, "expr")) + "\"" : "NULL") << ", "; + + NodeSet assignTexts = filterChildType(Node_base::TEXT_NODE, assign); + if (assignTexts.size() > 0) { + if (boost::trim_copy(assignTexts[0].getNodeValue()).length() > 0) { + std::string escaped = escape(assignTexts[0].getNodeValue()); + stream << "\"" << escaped << "\""; + } + } else { + stream << "NULL"; + } + stream << " }," << std::endl; + + assign.setAttribute("documentOrder", toStr(i)); + } + + stream << "};" << std::endl; + stream << std::endl; + + } + NodeSet datas = DOMUtils::inDocumentOrder(_nsInfo.xmlNSPrefix + "data", _scxml); if (datas.size() > 0) { _hasElement.insert("data"); @@ -2169,7 +2201,8 @@ void ChartToC::writeFSM(std::ostream& stream) { stream << " if ((USCXML_GET_TRANS(i).event == NULL && ctx->event == NULL) || " << std::endl; stream << " (USCXML_GET_TRANS(i).event != NULL && ctx->event != NULL)) {" << std::endl; stream << " /* is it enabled? */" << std::endl; - stream << " if (ctx->is_enabled(ctx, &USCXML_GET_TRANS(i), ctx->event) > 0) {" << std::endl; + stream << " if ((ctx->event == NULL || ctx->is_matched(ctx, &USCXML_GET_TRANS(i), ctx->event) > 0) &&" << std::endl; + stream << " (USCXML_GET_TRANS(i).condition == NULL || ctx->is_enabled(ctx, &USCXML_GET_TRANS(i)) > 0)) {" << std::endl; stream << " /* remember that we found a transition */" << std::endl; stream << " ctx->flags |= USCXML_CTX_TRANSITION_FOUND;" << std::endl; stream << std::endl; diff --git a/test/src/test-c-machine.cpp b/test/src/test-c-machine.cpp index 2879ef9..b4864e2 100644 --- a/test/src/test-c-machine.cpp +++ b/test/src/test-c-machine.cpp @@ -66,6 +66,7 @@ public: // register callbacks with scxml context ctx.is_enabled = &isEnabled; + ctx.is_matched = &isMatched; ctx.is_true = &isTrue; ctx.raise_done_event = &raiseDoneEvent; ctx.invoke = &invoke; @@ -250,31 +251,15 @@ public: // callbacks for scxml context - static int isEnabled(const uscxml_ctx* ctx, const uscxml_transition* t, const void* e) { + static int isMatched(const uscxml_ctx* ctx, const uscxml_transition* t, const void* e) { Event* event = (Event*)e; - if (event == NULL) { - if (t->event == NULL) { - // spontaneous transition, null event - if (t->condition != NULL) - return isTrue(ctx, t->condition); - return true; - } else { - // spontaneous transition, but real event - return false; - } - } - - // real event but spontaneous transition - if (t->event == NULL) - return false; + return (nameMatch(t->event, event->name.c_str())); + } - // real transition, real event - if (nameMatch(t->event, event->name.c_str())) { - if (t->condition != NULL) - return isTrue(ctx, t->condition); - return true; - } - return false; + static int isEnabled(const uscxml_ctx* ctx, const uscxml_transition* t) { + if (t->condition != NULL) + return isTrue(ctx, t->condition); + return 1; } static int isTrue(const uscxml_ctx* ctx, const char* expr) { @@ -318,7 +303,7 @@ public: } else if (invocation->idlocation != NULL) { // test224 invokedMachine->invokeId = (invocation->sourcename != NULL ? std::string(invocation->sourcename) + "." : "") + UUID::getUUID(); - ctx->exec_content_assign(ctx, invocation->idlocation, std::string("\"" + invokedMachine->invokeId + "\"").c_str()); + USER_DATA(ctx)->dataModel.assign(invocation->idlocation, Data(invokedMachine->invokeId, Data::VERBATIM)); } else { delete invokedMachine; return USCXML_ERR_UNSUPPORTED; @@ -374,6 +359,19 @@ public: static int execContentSend(const uscxml_ctx* ctx, const uscxml_elem_send* send) { SendRequest* e = new SendRequest(); + std::string sendid; + if (send->id != NULL) { + sendid = send->id; + } else { + sendid = UUID::getUUID(); + if (send->idlocation != NULL) { + USER_DATA(ctx)->dataModel.assign(send->idlocation, Data(sendid, Data::VERBATIM)); + } else { + e->hideSendId = true; + } + } + e->sendid = sendid; + std::string target; if (send->target != NULL) { e->target = send->target; @@ -384,8 +382,8 @@ public: } if (e->target.size() > 0 && (e->target[0] != '#' || e->target[1] != '_')) { - delete e; - execContentRaise(ctx, "error.execution"); + e->name = "error.execution"; + execContentRaise(ctx, e); return USCXML_ERR_INVALID_TARGET; } @@ -401,15 +399,15 @@ public: e->type = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor"; } } catch (Event exc) { - execContentRaise(ctx, exc.name.c_str()); - delete e; + e->name = "error.execution"; + execContentRaise(ctx, e); return USCXML_ERR_EXEC_CONTENT; } // only one somewhat supported if (e->type != "http://www.w3.org/TR/scxml/#SCXMLEventProcessor") { - delete e; - execContentRaise(ctx, "error.execution"); + e->name = "error.execution"; + execContentRaise(ctx, e); return USCXML_ERR_INVALID_TARGET; } @@ -469,19 +467,6 @@ public: } } - std::string sendid; - if (send->id != NULL) { - sendid = send->id; - e->sendid = sendid; - } else { - sendid = UUID::getUUID(); - if (send->idlocation != NULL) { - USER_DATA(ctx)->dataModel.assign(send->idlocation, Data(sendid, Data::VERBATIM)); - } else { - e->hideSendId = true; - } - } - size_t delayMs = 0; std::string delay; if (send->delayexpr != NULL) { @@ -518,10 +503,7 @@ public: return USCXML_ERR_OK; } - static int execContentRaise(const uscxml_ctx* ctx, const char* event) { - Event* e = new Event(); - e->name = event; - + static int execContentRaise(const uscxml_ctx* ctx, Event* e) { if (boost::starts_with(e->name, "error.")) { e->eventType = Event::PLATFORM; } else { @@ -531,6 +513,12 @@ public: return USCXML_ERR_OK; } + static int execContentRaise(const uscxml_ctx* ctx, const char* event) { + Event* e = new Event(); + e->name = event; + return execContentRaise(ctx, e); + } + static int execContentCancel(const uscxml_ctx* ctx, const char* sendid, const char* sendidexpr) { std::string eventId; if (sendid != NULL) { @@ -567,16 +555,22 @@ public: return USCXML_ERR_OK; } - static int execContentAssign(const uscxml_ctx* ctx, const char* location, const char* expr) { - std::string key = location; + static int execContentAssign(const uscxml_ctx* ctx, const uscxml_elem_assign* assign) { + std::string key = assign->location; if (key == "_sessionid" || key == "_name" || key == "_ioprocessors" || key == "_invokers" || key == "_event") { execContentRaise(ctx, "error.execution"); return USCXML_ERR_EXEC_CONTENT; } try { - Data d = USER_DATA(ctx)->dataModel.getStringAsData(expr); - USER_DATA(ctx)->dataModel.assign(key, d); +// Data d = USER_DATA(ctx)->dataModel.getStringAsData(expr); + if (assign->expr != NULL) { + Data d = Data(assign->expr, Data::INTERPRETED); + USER_DATA(ctx)->dataModel.assign(key, d); + } else if (assign->content != NULL) { + Data d = Data(assign->content, Data::INTERPRETED); + USER_DATA(ctx)->dataModel.assign(key, d); + } } catch (Event e) { execContentRaise(ctx, e.name.c_str()); return USCXML_ERR_EXEC_CONTENT; @@ -639,27 +633,32 @@ public: try { if (data->expr != NULL) { -// d = USER_DATA(ctx)->dataModel.getStringAsData(data->expr); d = Data(data->expr, Data::INTERPRETED); - } else if (data->content != NULL) { - content << data->content; - d = USER_DATA(ctx)->dataModel.getStringAsData(content.str()); - // d = Data(content.str(), Data::INTERPRETED); - } else if (data->src != NULL) { - URL sourceURL(data->src); - if (USER_DATA(ctx)->baseURL.size() > 0) { - sourceURL.toAbsolute(USER_DATA(ctx)->baseURL); + + } else if (data->content != NULL || data->src != NULL) { + if (data->content) { + content << data->content; } else { - sourceURL.toAbsoluteCwd(); + URL sourceURL(data->src); + if (USER_DATA(ctx)->baseURL.size() > 0) { + sourceURL.toAbsolute(USER_DATA(ctx)->baseURL); + } else { + sourceURL.toAbsoluteCwd(); + } + content << sourceURL; } - content << sourceURL; - // d = Data(content.str(), Data::INTERPRETED); + /** + * first attempt to parse as structured data, we will try + * as space normalized string literals if this fails below + */ d = USER_DATA(ctx)->dataModel.getStringAsData(content.str()); + } else { d = Data("undefined", Data::INTERPRETED); } // this might fail with an unquoted string literal in content USER_DATA(ctx)->dataModel.init(data->id, d); + } catch (Event e) { if (content.str().size() > 0) { try { diff --git a/test/src/test-c-machine.scxml.c b/test/src/test-c-machine.scxml.c index cbbfbfe..6378096 100644 --- a/test/src/test-c-machine.scxml.c +++ b/test/src/test-c-machine.scxml.c @@ -165,12 +165,14 @@ typedef struct uscxml_elem_invoke uscxml_elem_invoke; typedef struct uscxml_elem_send uscxml_elem_send; typedef struct uscxml_elem_param uscxml_elem_param; typedef struct uscxml_elem_data uscxml_elem_data; +typedef struct uscxml_elem_assign uscxml_elem_assign; typedef struct uscxml_elem_donedata uscxml_elem_donedata; typedef struct uscxml_elem_foreach uscxml_elem_foreach; typedef void* (*dequeue_internal_t)(const uscxml_ctx* ctx); typedef void* (*dequeue_external_t)(const uscxml_ctx* ctx); -typedef int (*is_enabled_t)(const uscxml_ctx* ctx, const uscxml_transition* transition, const void* event); +typedef int (*is_enabled_t)(const uscxml_ctx* ctx, const uscxml_transition* transition); +typedef int (*is_matched_t)(const uscxml_ctx* ctx, const uscxml_transition* transition, const void* event); typedef int (*is_true_t)(const uscxml_ctx* ctx, const char* expr); typedef int (*exec_content_t)(const uscxml_ctx* ctx, const uscxml_state* state, const void* event); typedef int (*raise_done_event_t)(const uscxml_ctx* ctx, const uscxml_state* state, const uscxml_elem_donedata* donedata); @@ -182,7 +184,7 @@ typedef int (*exec_content_send_t)(const uscxml_ctx* ctx, const uscxml_elem_send typedef int (*exec_content_foreach_init_t)(const uscxml_ctx* ctx, const uscxml_elem_foreach* foreach); typedef int (*exec_content_foreach_next_t)(const uscxml_ctx* ctx, const uscxml_elem_foreach* foreach); typedef int (*exec_content_foreach_done_t)(const uscxml_ctx* ctx, const uscxml_elem_foreach* foreach); -typedef int (*exec_content_assign_t)(const uscxml_ctx* ctx, const char* location, const char* expr); +typedef int (*exec_content_assign_t)(const uscxml_ctx* ctx, const uscxml_elem_assign* assign); typedef int (*exec_content_init_t)(const uscxml_ctx* ctx, const uscxml_elem_data* data); typedef int (*exec_content_cancel_t)(const uscxml_ctx* ctx, const char* sendid, const char* sendidexpr); typedef int (*exec_content_finalize_t)(const uscxml_ctx* ctx, const uscxml_elem_invoke* invoker, const void* event); @@ -218,6 +220,15 @@ struct uscxml_elem_data { }; /** + * All information pertaining to an element. + */ +struct uscxml_elem_assign { + const char* location; + const char* expr; + const char* content; +}; + +/** * All information pertaining to any state element. */ struct uscxml_state { @@ -335,6 +346,7 @@ struct uscxml_ctx { dequeue_internal_t dequeue_internal; dequeue_external_t dequeue_external; is_enabled_t is_enabled; + is_matched_t is_matched; is_true_t is_true; raise_done_event_t raise_done_event; @@ -360,6 +372,12 @@ extern const uscxml_machine _uscxml_EC83C2A5_machine; #ifndef USCXML_NO_ELEM_INFO +static const uscxml_elem_assign _uscxml_EC83C2A5_elem_assigns[2] = { + /* location, expr, content */ + { "_ioprocessors", "'otherName'", NULL }, + { "Var2", "_ioprocessors", NULL }, +}; + static const uscxml_elem_data _uscxml_EC83C2A5_elem_datas[3] = { /* id, src, expr, content */ { "Var1", NULL, "_ioprocessors", NULL }, @@ -383,7 +401,7 @@ static const uscxml_elem_donedata _uscxml_EC83C2A5_elem_donedatas[1] = { static int _uscxml_EC83C2A5_s1_on_entry_0(const uscxml_ctx* ctx, const uscxml_state* state, const void* event) { int err = USCXML_ERR_OK; if likely(ctx->exec_content_assign != NULL) { - if ((ctx->exec_content_assign(ctx, "_ioprocessors", "'otherName'")) != USCXML_ERR_OK) return err; + if ((ctx->exec_content_assign(ctx, &_uscxml_EC83C2A5_elem_assigns[0])) != USCXML_ERR_OK) return err; } else { return USCXML_ERR_MISSING_CALLBACK; } @@ -403,7 +421,7 @@ static int _uscxml_EC83C2A5_s1_on_entry(const uscxml_ctx* ctx, const uscxml_stat static int _uscxml_EC83C2A5_s2_on_entry_0(const uscxml_ctx* ctx, const uscxml_state* state, const void* event) { int err = USCXML_ERR_OK; if likely(ctx->exec_content_assign != NULL) { - if ((ctx->exec_content_assign(ctx, "Var2", "_ioprocessors")) != USCXML_ERR_OK) return err; + if ((ctx->exec_content_assign(ctx, &_uscxml_EC83C2A5_elem_assigns[1])) != USCXML_ERR_OK) return err; } else { return USCXML_ERR_MISSING_CALLBACK; } @@ -836,7 +854,8 @@ SELECT_TRANSITIONS: if ((USCXML_GET_TRANS(i).event == NULL && ctx->event == NULL) || (USCXML_GET_TRANS(i).event != NULL && ctx->event != NULL)) { /* is it enabled? */ - if (ctx->is_enabled(ctx, &USCXML_GET_TRANS(i), ctx->event) > 0) { + if ((ctx->event == NULL || ctx->is_matched(ctx, &USCXML_GET_TRANS(i), ctx->event) > 0) && + (USCXML_GET_TRANS(i).condition == NULL || ctx->is_enabled(ctx, &USCXML_GET_TRANS(i)) > 0)) { /* remember that we found a transition */ ctx->flags |= USCXML_CTX_TRANSITION_FOUND; -- cgit v0.12