diff options
Diffstat (limited to 'test/src')
-rw-r--r-- | test/src/test-c-machine.cpp | 429 | ||||
-rw-r--r-- | test/src/test-c-machine.machine.c | 532 | ||||
-rw-r--r-- | test/src/test-misc.cpp | 3 | ||||
-rw-r--r-- | test/src/test-w3c.cpp | 68 |
4 files changed, 1025 insertions, 7 deletions
diff --git a/test/src/test-c-machine.cpp b/test/src/test-c-machine.cpp new file mode 100644 index 0000000..a9e5ecb --- /dev/null +++ b/test/src/test-c-machine.cpp @@ -0,0 +1,429 @@ +#include <string.h> +#include <stdlib.h> // malloc +#include <assert.h> // assert +#include <stdio.h> // printf +#include <sstream> // stringstream +#include <deque> // deque +#include <boost/algorithm/string.hpp> // trim + +#define SCXML_VERBOSE 1 + +#ifndef AUTOINCLUDE_TEST +#include "test-c-machine.machine.c" +#endif + +#include "uscxml/Convenience.h" +//#include "uscxml/DOMUtils.h" +#include "uscxml/Factory.h" +#include "uscxml/InterpreterInfo.h" +#include "uscxml/UUID.h" + +#include "uscxml/concurrency/DelayedEventQueue.h" +#include "uscxml/concurrency/tinythread.h" + +#define USER_DATA(ctx) ((GenCInterpreterInfo*)(((scxml_ctx*)ctx)->user_data)) + +using namespace uscxml; + +typedef struct scxml_foreach_info scxml_foreach_info; +struct scxml_foreach_info { + size_t iterations; + size_t currIteration; +}; + +class GenCInterpreterInfo : public InterpreterInfo { +public: + NameSpaceInfo getNameSpaceInfo() const { + return nsInfo; + } + const std::string& getName() { + return name; + } + const std::string& getSessionId() { + return sessionId; + } + const std::map<std::string, IOProcessor>& getIOProcessors() { + return ioProcs; + } + bool isInState(const std::string& stateId) { + for (int i = 0 ; i < SCXML_NUMBER_STATES; i++) { + if (scxml_states[i].name != NULL && IS_SET(i, ctx->config) && stateId == scxml_states[i].name) + return true; + } + return false; + } + Arabica::DOM::Document<std::string> getDocument() const { + return document; + } + const std::map<std::string, Invoker>& getInvokers() { + return invokers; + } + + NameSpaceInfo nsInfo; + std::string name; + std::string sessionId; + std::map<std::string, IOProcessor> ioProcs; + std::map<std::string, Invoker> invokers; + Arabica::DOM::Document<std::string> document; + scxml_ctx* ctx; + boost::shared_ptr<DataModelImpl> datamodel; + + std::map<const scxml_elem_foreach*, scxml_foreach_info*> foreachInfo; + std::deque<Event*> iq; + std::deque<Event*> eq; + + DelayedEventQueue delayQueue; + std::map<std::string, SendRequest*> sendIds; + + tthread::condition_variable monitor; + tthread::mutex mutex; +}; + +int matches(const char* desc, const char* event) { + const char* dPtr = desc; + const char* ePtr = event; + while(true) { + + // next event descriptor + if (*dPtr == ' ') { + dPtr++; + ePtr = event; + continue; + } + + // descriptor is done, return match + if (*dPtr == 0 || *dPtr == '*') + return true; + + // descriptor differs from event name + if (*dPtr != *ePtr) + return false; + + // move both pointers one character + dPtr++; + ePtr++; + } +} + +int exec_content_raise(const scxml_ctx* ctx, const char* event) { + Event* e = new Event(); + e->name = strdup(event); + printf("Raising Internal Event: %s\n", e->name.c_str()); + USER_DATA(ctx)->iq.push_back(e); + return SCXML_ERR_OK; +} + +int is_true(const scxml_ctx* ctx, const char* expr) { + try { + return USER_DATA(ctx)->datamodel->evalAsBool(expr); + } catch (Event e) { + exec_content_raise(ctx, e.name.c_str()); + } + return false; +} + +int is_enabled(const scxml_ctx* ctx, const scxml_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 is_true(ctx, t->condition); + return true; + } else { + // spontaneous transition, but real event + return false; + } + } + + // real transition, real event + if (matches(t->event, event->name.c_str())) { + if (t->condition != NULL) + return is_true(ctx, t->condition); + return true; + } + return false; +} + +int raise_done_event(const scxml_ctx* ctx, const scxml_state* state) { + std::string doneName = "done.state."; + exec_content_raise(ctx, (doneName + state->name).c_str()); + return SCXML_ERR_OK; +} + +void delayedSend(void* ctx, std::string eventName) { + tthread::lock_guard<tthread::mutex> lock(USER_DATA(ctx)->mutex); + + SendRequest* e = USER_DATA(ctx)->sendIds[eventName]; + if (e->target == "#_internal") { + printf("Pushing Internal Event: %s\n", e->name.c_str()); + USER_DATA(ctx)->iq.push_back(e); + } else { + printf("Pushing External Event: %s\n", e->name.c_str()); + USER_DATA(ctx)->eq.push_back(e); + } + USER_DATA(ctx)->monitor.notify_all(); +} + +int exec_content_cancel(const scxml_ctx* ctx, const char* sendid, const char* sendidexpr) { + std::string eventId; + if (sendid != NULL) { + eventId = sendid; + } else if (sendidexpr != NULL) { + eventId = USER_DATA(ctx)->datamodel->evalAsString(sendidexpr); + } + + if (eventId.length() > 0) { + USER_DATA(ctx)->delayQueue.cancelEvent(eventId); + } else { + exec_content_raise(ctx, "error.execution"); + return SCXML_ERR_EXEC_CONTENT; + } + return SCXML_ERR_OK; +} + +int exec_content_send(const scxml_ctx* ctx, const scxml_elem_send* send) { + SendRequest* e = new SendRequest(); + + std::string target; + if (send->target != NULL) { + e->target = send->target; + } else if (send->targetexpr != NULL) { + e->target = USER_DATA(ctx)->datamodel->evalAsString(send->targetexpr); + } + + if (e->target.size() > 0 && (e->target[0] != '#' || e->target[1] != '_')) { + delete e; + exec_content_raise(ctx, "error.execution"); + return SCXML_ERR_INVALID_TARGET; + } + + if (send->type != NULL) { + e->type = send->type; + } else if (send->typeexpr != NULL) { + e->type = USER_DATA(ctx)->datamodel->evalAsString(send->typeexpr); + } else { + e->type = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor"; + } + + // only one somewhat supported + if (e->type != "http://www.w3.org/TR/scxml/#SCXMLEventProcessor") { + delete e; + exec_content_raise(ctx, "error.execution"); + return SCXML_ERR_INVALID_TARGET; + } + + e->origintype = e->type; + + if (send->eventexpr != NULL) { + e->name = USER_DATA(ctx)->datamodel->evalAsString(send->eventexpr); + } else { + e->name = strdup(send->event); + } + + const scxml_elem_param* param = send->params; + while (param && ELEM_PARAM_IS_SET(param)) { + Data paramValue; + if (param->expr != NULL) { + paramValue = USER_DATA(ctx)->datamodel->getStringAsData(param->expr); + } else if(param->location) { + paramValue = USER_DATA(ctx)->datamodel->getStringAsData(param->location); + } + e->params.insert(std::make_pair(param->name, paramValue)); + param++; + } + + if (send->content != NULL) { + e->data = Data(send->content, Data::VERBATIM); + } + + const char* sendid = NULL; + if (send->id != NULL) { + sendid = send->id; + } else { + sendid = strdup(UUID::getUUID().c_str()); + if (send->idlocation != NULL) + USER_DATA(ctx)->datamodel->assign(send->idlocation, Data(sendid, Data::VERBATIM)); + } + + size_t delayMs = 0; + std::string delay; + if (send->delayexpr != NULL) { + delay = USER_DATA(ctx)->datamodel->evalAsString(send->delayexpr); + } else if (send->delay != NULL) { + delay = send->delay; + } + if (delay.size() > 0) { + boost::trim(delay); + + NumAttr delayAttr(delay); + if (iequals(delayAttr.unit, "ms")) { + delayMs = strTo<uint32_t>(delayAttr.value); + } else if (iequals(delayAttr.unit, "s")) { + delayMs = strTo<double>(delayAttr.value) * 1000; + } else if (delayAttr.unit.length() == 0) { // unit less delay is interpreted as milliseconds + delayMs = strTo<uint32_t>(delayAttr.value); + } else { + std::cerr << "Cannot make sense of delay value " << delay << ": does not end in 's' or 'ms'"; + } + } + + USER_DATA(ctx)->sendIds[sendid] = e; + if (delayMs > 0) { + USER_DATA(ctx)->delayQueue.addEvent(sendid, delayedSend, delayMs, (void*)ctx); + } else { + delayedSend((void*)ctx, sendid); + } + + return SCXML_ERR_OK; +} + +int exec_content_init(const scxml_ctx* ctx, const scxml_elem_data* data) { + while(ELEM_DATA_IS_SET(data)) { + Data d; + if (data->expr != NULL) { + d = Data(data->expr, Data::INTERPRETED); + } else if (data->content != NULL) { + d = Data(data->content, Data::INTERPRETED); + } else { + d = Data("undefined", Data::INTERPRETED); + } + try { + USER_DATA(ctx)->datamodel->init(data->id, d); + } catch (Event e) { + exec_content_raise(ctx, e.name.c_str()); + } + data++; + } + return SCXML_ERR_OK; +} + +int exec_content_assign(const scxml_ctx* ctx, const char* location, const char* expr) { + try { + Data d(expr, Data::INTERPRETED); + USER_DATA(ctx)->datamodel->assign(location, d); + } catch (Event e) { + exec_content_raise(ctx, e.name.c_str()); + return SCXML_ERR_EXEC_CONTENT; + } + return SCXML_ERR_OK; +} + +int exec_content_foreach_init(const scxml_ctx* ctx, const scxml_elem_foreach* foreach) { + try { + scxml_foreach_info* feInfo = (scxml_foreach_info*)malloc(sizeof(scxml_foreach_info)); + USER_DATA(ctx)->foreachInfo[foreach] = feInfo; + + feInfo->iterations = USER_DATA(ctx)->datamodel->getLength(foreach->array); + feInfo->currIteration = 0; + } catch (Event e) { + exec_content_raise(ctx, e.name.c_str()); + return SCXML_ERR_EXEC_CONTENT; + } + return SCXML_ERR_OK; +} + +int exec_content_foreach_next(const scxml_ctx* ctx, const scxml_elem_foreach* foreach) { + try { + scxml_foreach_info* feInfo = USER_DATA(ctx)->foreachInfo[foreach]; + if (feInfo->currIteration < feInfo->iterations) { + USER_DATA(ctx)->datamodel->setForeach((foreach->item != NULL ? foreach->item : ""), + (foreach->array != NULL ? foreach->array : ""), + (foreach->index != NULL ? foreach->index : ""), + feInfo->currIteration); + feInfo->currIteration++; + return SCXML_ERR_OK; + } + } catch (Event e) { + exec_content_raise(ctx, e.name.c_str()); + free(USER_DATA(ctx)->foreachInfo[foreach]); + USER_DATA(ctx)->foreachInfo.erase(foreach); + return SCXML_ERR_EXEC_CONTENT; + } + return SCXML_ERR_FOREACH_DONE; +} + +int exec_content_foreach_done(const scxml_ctx* ctx, const scxml_elem_foreach* foreach) { + free(USER_DATA(ctx)->foreachInfo[foreach]); + USER_DATA(ctx)->foreachInfo.erase(foreach); + return SCXML_ERR_OK; +} + +int exec_content_log(const scxml_ctx* ctx, const char* label, const char* expr) { + if (label != 0) { + printf("%s: %s\n", label, expr); + } else { + printf("%s\n", USER_DATA(ctx)->datamodel->evalAsString(expr).c_str()); + } + return SCXML_ERR_OK; +} + +void* dequeue_external(const scxml_ctx* ctx) { + tthread::lock_guard<tthread::mutex> lock(USER_DATA(ctx)->mutex); + while (USER_DATA(ctx)->eq.size() == 0) { + USER_DATA(ctx)->monitor.wait(USER_DATA(ctx)->mutex); + } + Event* e = USER_DATA(ctx)->eq.front(); + USER_DATA(ctx)->eq.pop_front(); + USER_DATA(ctx)->datamodel->setEvent(*e); + printf("Popping External Event: %s\n", e->name.c_str()); + return e; +} + +void* dequeue_internal(const scxml_ctx* ctx) { + if (USER_DATA(ctx)->iq.size() == 0) + return NULL; + Event* e = USER_DATA(ctx)->iq.front(); + USER_DATA(ctx)->iq.pop_front(); + USER_DATA(ctx)->datamodel->setEvent(*e); + printf("Popping Internal Event: %s\n", e->name.c_str()); + return e; +} + +int main(int argc, char** argv) { + int err; + + // setup info object required for datamodel + GenCInterpreterInfo interpreterInfo; + interpreterInfo.name = "adsf"; + interpreterInfo.sessionId = "rfwef"; + interpreterInfo.datamodel = Factory::getInstance()->createDataModel("ecmascript", &interpreterInfo); + interpreterInfo.delayQueue.start(); + + scxml_ctx ctx; + memset(&ctx, 0, sizeof(scxml_ctx)); + interpreterInfo.ctx = &ctx; + + // set info object as user data + ctx.user_data = (void*)&interpreterInfo; + + // register callbacks with scxml context + ctx.is_enabled = &is_enabled; + ctx.is_true = &is_true; + ctx.raise_done_event = &raise_done_event; + + ctx.exec_content_send = &exec_content_send; + ctx.exec_content_raise = &exec_content_raise; + ctx.exec_content_cancel = &exec_content_cancel; + ctx.exec_content_log = &exec_content_log; + ctx.exec_content_assign = &exec_content_assign; + ctx.exec_content_foreach_init = &exec_content_foreach_init; + ctx.exec_content_foreach_next = &exec_content_foreach_next; + ctx.exec_content_foreach_done = &exec_content_foreach_done; + ctx.dequeue_external = &dequeue_external; + ctx.dequeue_internal = &dequeue_internal; + ctx.exec_content_init = &exec_content_init; + + while((err = scxml_step(&ctx)) == SCXML_ERR_OK); + assert(ctx.flags & SCXML_CTX_TOP_LEVEL_FINAL); + + size_t passIdx = 0; + for (int i = 0; i < SCXML_NUMBER_STATES; i++) { + if (scxml_states[i].name && strcmp(scxml_states[i].name, "pass") == 0) { + passIdx = i; + break; + } + } + assert(IS_SET(passIdx, ctx.config)); + return EXIT_SUCCESS; +}
\ No newline at end of file diff --git a/test/src/test-c-machine.machine.c b/test/src/test-c-machine.machine.c new file mode 100644 index 0000000..d4c924d --- /dev/null +++ b/test/src/test-c-machine.machine.c @@ -0,0 +1,532 @@ +#include <stdint.h> // explicit types +#include <stddef.h> // NULL + +#define IS_SET(idx, bitset) ((bitset[idx >> 3] & (1 << (idx & 7))) != 0) +#define SET_BIT(idx, bitset) bitset[idx >> 3] |= (1 << (idx & 7)); +#define CLEARBIT(idx, bitset) bitset[idx >> 3] &= (1 << (idx & 7)) ^ 0xFF; + +// error return codes +#define SCXML_ERR_OK 0 +#define SCXML_ERR_IDLE 1 +#define SCXML_ERR_DONE 2 +#define SCXML_ERR_MISSING_CALLBACK 3 +#define SCXML_ERR_FOREACH_DONE 4 +#define SCXML_ERR_EXEC_CONTENT 5 +#define SCXML_ERR_INVALID_TARGET 6 +#define SCXML_ERR_INVALID_TYPE 7 + +#define SCXML_NUMBER_STATES 5 +#define SCXML_NUMBER_TRANSITIONS 4 + +#define SCXML_TRANS_SPONTANEOUS 0x01 +#define SCXML_TRANS_TARGETLESS 0x02 +#define SCXML_TRANS_INTERNAL 0x04 + +#define SCXML_STATE_ATOMIC 0x01 +#define SCXML_STATE_PARALLEL 0x02 +#define SCXML_STATE_COMPOUND 0x03 +#define SCXML_STATE_FINAL 0x04 +#define SCXML_STATE_HISTORY_DEEP 0x05 +#define SCXML_STATE_HISTORY_SHALLOW 0x06 +#define SCXML_STATE_INITIAL 0x07 + +#define SCXML_CTX_PRISTINE 0x00 +#define SCXML_CTX_SPONTANEOUS 0x01 +#define SCXML_CTX_INITIALIZED 0x02 +#define SCXML_CTX_TOP_LEVEL_FINAL 0x04 +#define SCXML_CTX_TRANSITION_FOUND 0x08 + +#define ELEM_DATA_IS_SET(data) (data->id != NULL) +#define ELEM_PARAM_IS_SET(param) (param->name != NULL) + + +typedef struct scxml_transition scxml_transition; +typedef struct scxml_state scxml_state; +typedef struct scxml_ctx scxml_ctx; +typedef struct scxml_invoke scxml_invoke; + +typedef struct scxml_elem_send scxml_elem_send; +typedef struct scxml_elem_param scxml_elem_param; +typedef struct scxml_elem_data scxml_elem_data; +typedef struct scxml_elem_foreach scxml_elem_foreach; + +typedef void* (*dequeue_internal_cb_t)(const scxml_ctx* ctx); +typedef void* (*dequeue_external_cb_t)(const scxml_ctx* ctx); +typedef int (*is_enabled_cb_t)(const scxml_ctx* ctx, const scxml_transition* transition, const void* event); +typedef int (*is_true_cb_t)(const scxml_ctx* ctx, const char* expr); +typedef int (*exec_content_t)(const scxml_ctx* ctx, const scxml_state* state, const void* event); +typedef int (*raise_done_event_t)(const scxml_ctx* ctx, const scxml_state* state); +typedef int (*invoke_t)(const scxml_ctx* ctx, const scxml_state* s, const scxml_invoke* x); + +typedef int (*exec_content_log_t)(const scxml_ctx* ctx, const char* label, const char* expr); +typedef int (*exec_content_raise_t)(const scxml_ctx* ctx, const char* event); +typedef int (*exec_content_send_t)(const scxml_ctx* ctx, const scxml_elem_send* send); +typedef int (*exec_content_foreach_init_t)(const scxml_ctx* ctx, const scxml_elem_foreach* foreach); +typedef int (*exec_content_foreach_next_t)(const scxml_ctx* ctx, const scxml_elem_foreach* foreach); +typedef int (*exec_content_foreach_done_t)(const scxml_ctx* ctx, const scxml_elem_foreach* foreach); +typedef int (*exec_content_assign_t)(const scxml_ctx* ctx, const char* location, const char* expr); +typedef int (*exec_content_init_t)(const scxml_ctx* ctx, const scxml_elem_data* data); +typedef int (*exec_content_cancel_t)(const scxml_ctx* ctx, const char* sendid, const char* sendidexpr); +typedef int (*exec_content_finalize_t)(const scxml_ctx* ctx, const scxml_invoke* invoker, const void* event); +typedef int (*exec_content_script_t)(const scxml_ctx* ctx, const char* src, const char* content); + +struct scxml_elem_data { + const char* id; + const char* src; + const char* expr; + const char* content; +}; + +struct scxml_state { + const char* name; // eventual name + exec_content_t on_entry; // on entry handlers + exec_content_t on_exit; // on exit handlers + invoke_t invoke; // invocations + char children[1]; // all children + char completion[1]; // default completion + char ancestors[1]; // all ancestors + const scxml_elem_data* data; + uint8_t type; // atomic, parallel, compound, final, history +}; + +struct scxml_transition { + uint16_t source; + char target[1]; + const char* event; + const char* condition; + exec_content_t on_transition; + uint8_t type; + char conflicts[1]; + char exit_set[1]; +}; + +struct scxml_elem_foreach { + const char* array; + const char* item; + const char* index; +}; + +struct scxml_elem_param { + const char* name; + const char* expr; + const char* location; +}; + +struct scxml_elem_invoke { + const char* type; + const char* typeexpr; + const char* src; + const char* srcexpr; + const char* id; + const char* idlocation; + const char* namelist; + uint8_t autoforward; + const scxml_elem_param* params; + const exec_content_finalize_t* finalize; + const char* content; + void* user_data; +}; + +struct scxml_elem_send { + const char* event; + const char* eventexpr; + const char* target; + const char* targetexpr; + const char* type; + const char* typeexpr; + const char* id; + const char* idlocation; + const char* delay; + const char* delayexpr; + const char* namelist; + const char* content; + const scxml_elem_param* params; + void* user_data; +}; + +struct scxml_ctx { + uint8_t flags; + + char config[1]; + char history[1]; + char pending_invokes[1]; + char initialized_data[1]; + + void* user_data; + + dequeue_internal_cb_t dequeue_internal; + dequeue_external_cb_t dequeue_external; + is_enabled_cb_t is_enabled; + is_true_cb_t is_true; + raise_done_event_t raise_done_event; + + exec_content_log_t exec_content_log; + exec_content_raise_t exec_content_raise; + exec_content_send_t exec_content_send; + exec_content_foreach_init_t exec_content_foreach_init; + exec_content_foreach_next_t exec_content_foreach_next; + exec_content_foreach_done_t exec_content_foreach_done; + exec_content_assign_t exec_content_assign; + exec_content_init_t exec_content_init; + exec_content_cancel_t exec_content_cancel; + exec_content_script_t exec_content_script; + invoke_t invoke; +}; + +scxml_elem_data scxml_elem_datas[4] = { + { "Var1", NULL, NULL, NULL }, + { NULL, NULL, NULL, NULL }, + { "Var2", NULL, "1", NULL }, + { NULL, NULL, NULL, NULL } +}; + +int s1_on_entry_0(const scxml_ctx* ctx, const scxml_state* state, const void* event) { + int err = SCXML_ERR_OK; + if (ctx->exec_content_assign != NULL) { + if ((ctx->exec_content_assign(ctx, "Var1", "Var2")) != SCXML_ERR_OK) return err; + } else { + return SCXML_ERR_MISSING_CALLBACK; + } + return SCXML_ERR_OK; +} + +int s1_on_entry(const scxml_ctx* ctx, const scxml_state* state, const void* event) { + s1_on_entry_0(ctx, state, event); + return SCXML_ERR_OK; +} + +int pass_on_entry_0(const scxml_ctx* ctx, const scxml_state* state, const void* event) { + int err = SCXML_ERR_OK; + if (ctx->exec_content_log != NULL) { + if ((ctx->exec_content_log(ctx, "Outcome", "'pass'")) != SCXML_ERR_OK) return err; + } else { + return SCXML_ERR_MISSING_CALLBACK; + } + return SCXML_ERR_OK; +} + +int pass_on_entry(const scxml_ctx* ctx, const scxml_state* state, const void* event) { + pass_on_entry_0(ctx, state, event); + return SCXML_ERR_OK; +} + +int fail_on_entry_0(const scxml_ctx* ctx, const scxml_state* state, const void* event) { + int err = SCXML_ERR_OK; + if (ctx->exec_content_log != NULL) { + if ((ctx->exec_content_log(ctx, "Outcome", "'fail'")) != SCXML_ERR_OK) return err; + } else { + return SCXML_ERR_MISSING_CALLBACK; + } + return SCXML_ERR_OK; +} + +int fail_on_entry(const scxml_ctx* ctx, const scxml_state* state, const void* event) { + fail_on_entry_0(ctx, state, event); + return SCXML_ERR_OK; +} + +scxml_state scxml_states[5] = { + { NULL, NULL, NULL, NULL, { 0x1e /* 01111, 1 2 3 4 */ }, { 0x02 /* 01000, 1 */ }, { 0x00 /* 00000, */ }, (const scxml_elem_data*)&scxml_elem_datas[0], SCXML_STATE_COMPOUND }, + { "s0", NULL, NULL, NULL, { 0x00 /* 00000, */ }, { 0x00 /* 00000, */ }, { 0x01 /* 10000, 0 */ }, NULL, SCXML_STATE_ATOMIC }, + { "s1", s1_on_entry, NULL, NULL, { 0x00 /* 00000, */ }, { 0x00 /* 00000, */ }, { 0x01 /* 10000, 0 */ }, (const scxml_elem_data*)&scxml_elem_datas[1], SCXML_STATE_ATOMIC }, + { "pass", pass_on_entry, NULL, NULL, { 0x00 /* 00000, */ }, { 0x00 /* 00000, */ }, { 0x01 /* 10000, 0 */ }, NULL, SCXML_STATE_FINAL }, + { "fail", fail_on_entry, NULL, NULL, { 0x00 /* 00000, */ }, { 0x00 /* 00000, */ }, { 0x01 /* 10000, 0 */ }, NULL, SCXML_STATE_FINAL } +}; + +scxml_transition scxml_transitions[4] = { + { 1, { 0x04 /* 00100 */ }, NULL, "typeof Var2 === 'undefined' ", NULL, SCXML_TRANS_SPONTANEOUS, { 0x0f /* 1111 */ }, { 0x1e /* 01111 */ } }, + { 1, { 0x10 /* 00001 */ }, NULL, NULL, NULL, SCXML_TRANS_SPONTANEOUS, { 0x0f /* 1111 */ }, { 0x1e /* 01111 */ } }, + { 2, { 0x08 /* 00010 */ }, NULL, "Var1===Var2", NULL, SCXML_TRANS_SPONTANEOUS, { 0x0f /* 1111 */ }, { 0x1e /* 01111 */ } }, + { 2, { 0x10 /* 00001 */ }, NULL, NULL, NULL, SCXML_TRANS_SPONTANEOUS, { 0x0f /* 1111 */ }, { 0x1e /* 01111 */ } } +}; + +#ifdef SCXML_VERBOSE +void printStateNames(const char* a) { + const char* seperator = ""; + for (int i = 0; i < SCXML_NUMBER_STATES; i++) { + if (IS_SET(i, a)) { + printf("%s%s", seperator, (scxml_states[i].name != NULL ? scxml_states[i].name : "UNK")); + seperator = ", "; + } + } + printf("\n"); +} + +void printBitsetIndices(const char* a, size_t length) { + const char* seperator = ""; + for (int i = 0; i < length; i++) { + if (IS_SET(i, a)) { + printf("%s%d", seperator, i); + seperator = ", "; + } + } + printf("\n"); +} +#endif + +void bit_or(char* dest, const char* mask, size_t length) { + for (int i = 0; i < length; ++i) { + dest[i] |= mask[i]; + } +} + +void bit_copy(char* dest, const char* source, size_t length) { + for (int i = 0; i < length; ++i) { + dest[i] = source[i]; + } +} + +int bit_has_and(const char* a, const char* b, size_t length) { + for (int i = 0; i < length; ++i) { + if (a[i] & b[i]) + return true; + } + return false; +} + +void bit_and_not(char* dest, const char* mask, size_t length) { + for (int i = 0; i < length; ++i) { + dest[i] &= ~mask[i]; + } +} + +int bit_any_set(const char* a, size_t length) { + for (int i = 0; i < length; ++i) { + if (a[i] > 0) + return true; + } + return false; +} + +int scxml_step(scxml_ctx* ctx) { + +#ifdef SCXML_VERBOSE + printStateNames(ctx->config); +#endif + +MACRO_STEP: + ctx->flags &= ~SCXML_CTX_TRANSITION_FOUND; + + if (ctx->flags & SCXML_CTX_TOP_LEVEL_FINAL) + return SCXML_ERR_DONE; + + int err = SCXML_ERR_OK; + char conflicts[1] = {0}; + char target_set[1] = {0}; + char exit_set[1] = {0}; + char trans_set[1] = {0}; + char entry_set[1] = {0}; + + void* event; + if (ctx->flags == SCXML_CTX_PRISTINE) { + bit_or(target_set, scxml_states[0].completion, 1); + ctx->flags |= SCXML_CTX_SPONTANEOUS | SCXML_CTX_INITIALIZED; + goto COMPLETE_CONFIG; + } + + if (ctx->flags & SCXML_CTX_SPONTANEOUS) { + event = NULL; + goto SELECT_TRANSITIONS; + } + if ((event = ctx->dequeue_internal(ctx)) != NULL) { + goto SELECT_TRANSITIONS; + } + if ((event = ctx->dequeue_external(ctx)) != NULL) { + goto SELECT_TRANSITIONS; + } + +SELECT_TRANSITIONS: + for (int i = 0; i < SCXML_NUMBER_TRANSITIONS; i++) { + // is the transition active? + if (IS_SET(scxml_transitions[i].source, ctx->config)) { + // is it non-conflicting? + if (!IS_SET(i, conflicts)) { + // is it enabled? + if (ctx->is_enabled(ctx, &scxml_transitions[i], event) > 0) { + // remember that we found a transition + ctx->flags |= SCXML_CTX_TRANSITION_FOUND; + + // transitions that are pre-empted + bit_or(conflicts, scxml_transitions[i].conflicts, 1); + + // states that are directly targeted (resolve as entry-set later) + bit_or(target_set, scxml_transitions[i].target, 1); + + // states that will be left + bit_or(exit_set, scxml_transitions[i].exit_set, 1); + + SET_BIT(i, trans_set); + } + } + } + } + + if (ctx->flags & SCXML_CTX_TRANSITION_FOUND) { + ctx->flags |= SCXML_CTX_SPONTANEOUS; + } else { + ctx->flags &= ~SCXML_CTX_SPONTANEOUS; + goto MACRO_STEP; + } + +REMEMBER_HISTORY: + // are my ancestors in the exit set? + for (int i = 0; i < SCXML_NUMBER_STATES; i++) { + if (IS_SET(i, ctx->config) && bit_has_and(exit_set, scxml_states[i].ancestors, 1)) { + SET_BIT(i, ctx->history); + } + } + +#ifdef SCXML_VERBOSE + printf("Exiting: "); + printStateNames(exit_set); +#endif + +EXIT_STATES: + for (int i = SCXML_NUMBER_STATES - 1; i >= 0; i--) { + if (IS_SET(i, exit_set) && IS_SET(i, ctx->config)) { + // call all on exit handlers + if (scxml_states[i].on_exit != NULL) { + if((err = scxml_states[i].on_exit(ctx, &scxml_states[i], event)) != SCXML_ERR_OK) + return err; + } + CLEARBIT(i, ctx->config); + } + } + +COMPLETE_CONFIG: + // calculate new entry set + bit_copy(entry_set, target_set, 1); + + // iterate for ancestors + for (int i = 0; i < SCXML_NUMBER_STATES; i++) { + if (IS_SET(i, entry_set)) { + bit_or(entry_set, scxml_states[i].ancestors, 1); + } + } + +ADD_DESCENDANTS: + // iterate for descendants + for (int i = 0; i < SCXML_NUMBER_STATES; i++) { + if (IS_SET(i, entry_set)) { + switch (scxml_states[i].type) { + case SCXML_STATE_PARALLEL: { + bit_or(entry_set, scxml_states[i].completion, 1); + break; + } + case SCXML_STATE_INITIAL: { + for (int j = 0; j < SCXML_NUMBER_TRANSITIONS; j++) { + if (scxml_transitions[j].source == i) { + SET_BIT(j, trans_set); + CLEARBIT(i, entry_set); + bit_or(entry_set, scxml_transitions[j].target, 1); + // one target may have been above, reestablish completion + goto ADD_DESCENDANTS; + } + } + break; + } + case SCXML_STATE_COMPOUND: { // we need to check whether one child is already in entry_set + if (!bit_has_and(entry_set, scxml_states[i].children, 1)) { + bit_or(entry_set, scxml_states[i].completion, 1); + } + break; + } + } + } + } + +#ifdef SCXML_VERBOSE + printf("Transitions: "); + printBitsetIndices(trans_set, sizeof(char) * 8 * 1); +#endif + +TAKE_TRANSITIONS: + for (int i = 0; i < SCXML_NUMBER_TRANSITIONS; i++) { + if (IS_SET(i, trans_set)) { + // call executable content in transition + if (scxml_transitions[i].on_transition != NULL) { + if((err = scxml_transitions[i].on_transition(ctx, + &scxml_states[scxml_transitions[i].source], + event)) != SCXML_ERR_OK) + return err; + } + } + } + +#ifdef SCXML_VERBOSE + printf("Entering: "); + printStateNames(entry_set); +#endif + +ENTER_STATES: + for (int i = 0; i < SCXML_NUMBER_STATES; i++) { + if (IS_SET(i, entry_set) && !IS_SET(i, ctx->config)) { + SET_BIT(i, ctx->config); + if (scxml_states[i].on_entry != NULL) { + if((err = scxml_states[i].on_entry(ctx, &scxml_states[i], event)) != SCXML_ERR_OK) + return err; + } + + // initialize data + if(!IS_SET(i, ctx->initialized_data)) { + if (scxml_states[i].data != NULL && ctx->exec_content_init != NULL) { + ctx->exec_content_init(ctx, scxml_states[i].data); + } + SET_BIT(i, ctx->initialized_data); + } + + // handle final states + if (scxml_states[i].type == SCXML_STATE_FINAL) { + if (scxml_states[i].ancestors[0] == 0x01) { + ctx->flags |= SCXML_CTX_TOP_LEVEL_FINAL; + } else { + // raise done event + size_t parent = 0; + for (int j = 0; j < SCXML_NUMBER_STATES; j++) { + // we could trade runtime for memory here by saving the parent index + if (!IS_SET(j, scxml_states[i].ancestors)) { + if (parent != 0) { + break; + } + continue; + } else { + parent = j; + } + } + ctx->raise_done_event(ctx, &scxml_states[parent]); + } + + /** + * are we the last final state to leave a parallel state?: + * 1. Gather all parallel states in our ancestor chain + * 2. Find all states for which these parallels are ancestors + * 3. Iterate all active final states and remove their ancestors + * 4. If a state remains, not all children of a parallel are final + */ + for (int j = 0; j < SCXML_NUMBER_STATES; j++) { + if (scxml_states[j].type == SCXML_STATE_PARALLEL) { + char parallel_children[2] = {0, 0}; + size_t parallel = j; + for (int k = 0; k < SCXML_NUMBER_STATES; k++) { + if (IS_SET(parallel, scxml_states[k].ancestors) && IS_SET(k, ctx->config)) { + if (scxml_states[k].type == SCXML_STATE_FINAL) { + bit_and_not(parallel_children, scxml_states[k].ancestors, 2); + } else { + SET_BIT(k, parallel_children); + } + } + } + if (!bit_any_set(parallel_children, 2)) { + ctx->raise_done_event(ctx, &scxml_states[parallel]); + } + } + } + + } + + } + } + + return SCXML_ERR_OK; +} + diff --git a/test/src/test-misc.cpp b/test/src/test-misc.cpp new file mode 100644 index 0000000..09cda31 --- /dev/null +++ b/test/src/test-misc.cpp @@ -0,0 +1,3 @@ +int main(int argc, char** argv) { + return 0; +}
\ No newline at end of file diff --git a/test/src/test-w3c.cpp b/test/src/test-w3c.cpp index 1480ecb..0069d0d 100644 --- a/test/src/test-w3c.cpp +++ b/test/src/test-w3c.cpp @@ -20,11 +20,36 @@ static bool withFlattening = false; static double delayFactor = 1; +static size_t benchmarkRuns = 0; static std::string documentURI; int retCode = EXIT_FAILURE; uscxml::Interpreter interpreter; +void printUsageAndExit(const char* progName) { + // remove path from program name + std::string progStr(progName); + if (progStr.find_last_of(PATH_SEPERATOR) != std::string::npos) { + progStr = progStr.substr(progStr.find_last_of(PATH_SEPERATOR) + 1, progStr.length() - (progStr.find_last_of(PATH_SEPERATOR) + 1)); + } + + printf("%s version " USCXML_VERSION " (" CMAKE_BUILD_TYPE " build - " CMAKE_COMPILER_STRING ")\n", progStr.c_str()); + printf("Usage\n"); + printf("\t%s", progStr.c_str()); + printf(" [-f] [-dN] [-bN]"); +#ifdef BUILD_AS_PLUGINS + printf(" [-p pluginPath]"); +#endif + printf(" URL"); + printf("\n"); + printf("Options\n"); + printf("\t-f : flatten to SCXML state-machine\n"); + printf("\t-d FACTOR : delay factor\n"); + printf("\t-b ITERATIONS : benchmark with number of runs\n"); + printf("\n"); + exit(1); +} + class W3CStatusMonitor : public uscxml::StateTransitionMonitor { void beforeCompletion(uscxml::Interpreter tmp) { @@ -61,7 +86,7 @@ int main(int argc, char** argv) { } int option; - while ((option = getopt(argc, argv, "fd:")) != -1) { + while ((option = getopt(argc, argv, "fd:b:")) != -1) { switch(option) { case 'f': withFlattening = true; @@ -69,6 +94,9 @@ int main(int argc, char** argv) { case 'd': delayFactor = strTo<double>(optarg); break; + case 'b': + benchmarkRuns = strTo<size_t>(optarg); + break; default: break; } @@ -76,7 +104,7 @@ int main(int argc, char** argv) { documentURI = argv[optind]; - LOG(INFO) << "Processing " << documentURI << (withFlattening ? " FSM converted" : "") << (delayFactor ? "" : " with delays *= " + toStr(delayFactor)); + LOG(INFO) << "Processing " << documentURI << (withFlattening ? " FSM converted" : "") << (delayFactor ? "" : " with delays *= " + toStr(delayFactor)) << (benchmarkRuns > 0 ? " for " + toStr(benchmarkRuns) + " benchmarks" : ""); if (withFlattening) { interpreter = Interpreter::fromURL(documentURI); Transformer flattener = ChartToFlatSCXML::transform(interpreter); @@ -117,11 +145,37 @@ int main(int argc, char** argv) { } if (interpreter) { - W3CStatusMonitor* vm = new W3CStatusMonitor(); - interpreter.addMonitor(vm); - - interpreter.start(); - while(interpreter.runOnMainThread(25)); + if (benchmarkRuns > 0) { + LOG(INFO) << "Benchmarking " << documentURI << (withFlattening ? " FSM converted" : "") << (delayFactor ? "" : " with delays *= " + toStr(delayFactor)); + + InterpreterState state = interpreter.getState(); + + double avg = 0; + uint64_t now = 0; + size_t remainingRuns = benchmarkRuns; + uint64_t start = tthread::chrono::system_clock::now(); + + while(remainingRuns-- > 0) { + now = tthread::chrono::system_clock::now(); + for(;;) { + state = interpreter.step(true); + if (state < 0) + break; + } + avg += (double)(tthread::chrono::system_clock::now() - now) / (double)benchmarkRuns; + interpreter.reset(); + } + uint64_t totalDuration = tthread::chrono::system_clock::now() - start; + std::cout << benchmarkRuns << " iterations in " << totalDuration << " ms" << std::endl; + std::cout << avg << " ms on average" << std::endl; + + } else { + W3CStatusMonitor* vm = new W3CStatusMonitor(); + interpreter.addMonitor(vm); + + interpreter.start(); + while(interpreter.runOnMainThread(25)); + } } } catch(Event e) { std::cout << e << std::endl; |