summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoralexzhornyak <alexander.zhornyak@gmail.com>2017-05-13 21:39:11 (GMT)
committeralexzhornyak <alexander.zhornyak@gmail.com>2017-05-13 21:39:11 (GMT)
commitb3c8edc7788fc6bcdd941ecdee5435957bc08366 (patch)
tree4e4238ad748c5f9e7baf28a24783bb2cccc07cdd
parent43370419fb7a9f14d85f96f39ffff86a337756d4 (diff)
parent82087b37adc295d1aab5afd51f855f8d9f0923f8 (diff)
downloaduscxml-b3c8edc7788fc6bcdd941ecdee5435957bc08366.zip
uscxml-b3c8edc7788fc6bcdd941ecdee5435957bc08366.tar.gz
uscxml-b3c8edc7788fc6bcdd941ecdee5435957bc08366.tar.bz2
Merge branch 'Improving-EvalAsData' into Explicit-Eval
-rw-r--r--src/uscxml/interpreter/BasicContentExecutor.cpp42
-rw-r--r--src/uscxml/interpreter/ContentExecutorImpl.h1
-rw-r--r--src/uscxml/interpreter/InterpreterImpl.h4
-rw-r--r--src/uscxml/messages/Event.h8
-rw-r--r--src/uscxml/plugins/DataModel.cpp4
-rw-r--r--src/uscxml/plugins/DataModel.h2
-rw-r--r--src/uscxml/plugins/DataModelImpl.h6
-rw-r--r--src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp127
-rw-r--r--src/uscxml/plugins/datamodel/lua/LuaDataModel.h1
-rw-r--r--src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h1
-rw-r--r--test/issues/test-issue116.scxml43
-rw-r--r--test/w3c/lua/test562.scxml176
12 files changed, 329 insertions, 86 deletions
diff --git a/src/uscxml/interpreter/BasicContentExecutor.cpp b/src/uscxml/interpreter/BasicContentExecutor.cpp
index 7ffda91..932d579 100644
--- a/src/uscxml/interpreter/BasicContentExecutor.cpp
+++ b/src/uscxml/interpreter/BasicContentExecutor.cpp
@@ -67,7 +67,7 @@ void BasicContentExecutor::processSend(XERCESC_NS::DOMElement* element) {
sendEvent.name = ATTR(element, kXMLCharEvent);
}
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element eventexpr", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element eventexpr", element);
}
try {
@@ -78,7 +78,7 @@ void BasicContentExecutor::processSend(XERCESC_NS::DOMElement* element) {
target = ATTR(element, kXMLCharTarget);
}
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element targetexpr", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element targetexpr", element);
}
try {
@@ -89,7 +89,7 @@ void BasicContentExecutor::processSend(XERCESC_NS::DOMElement* element) {
type = ATTR(element, kXMLCharType);
}
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element typeexpr", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element typeexpr", element);
}
try {
@@ -124,7 +124,7 @@ void BasicContentExecutor::processSend(XERCESC_NS::DOMElement* element) {
}
}
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element idlocation", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element idlocation", element);
}
try {
@@ -148,14 +148,14 @@ void BasicContentExecutor::processSend(XERCESC_NS::DOMElement* element) {
}
}
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element delayexpr", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element delayexpr", element);
}
try {
// namelist
processNameLists(sendEvent.namelist, element);
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element namelist", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element namelist", element);
}
@@ -163,7 +163,7 @@ void BasicContentExecutor::processSend(XERCESC_NS::DOMElement* element) {
// params
processParams(sendEvent.params, element);
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element param expr", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element param expr", element);
}
try {
@@ -173,7 +173,7 @@ void BasicContentExecutor::processSend(XERCESC_NS::DOMElement* element) {
sendEvent.data = elementAsData(contents.front());
}
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element content", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element content", element);
}
// if (sendReq->dom) {
@@ -431,14 +431,14 @@ void BasicContentExecutor::invoke(XERCESC_NS::DOMElement* element) {
element->setUserData(kXMLCharInvokeId, (void*)invokeId, NULL);
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in invoke element idlocation", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in invoke element idlocation", element);
}
try {
// namelist
processNameLists(invokeEvent.namelist, element);
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element namelist", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element namelist", element);
}
@@ -446,7 +446,7 @@ void BasicContentExecutor::invoke(XERCESC_NS::DOMElement* element) {
// params
processParams(invokeEvent.params, element);
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in send element param expr", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in send element param expr", element);
}
try {
@@ -467,7 +467,7 @@ void BasicContentExecutor::invoke(XERCESC_NS::DOMElement* element) {
#endif
}
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in invoke element content", element);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in invoke element content", element);
}
// autoforward
@@ -513,7 +513,7 @@ void BasicContentExecutor::raiseDoneEvent(XERCESC_NS::DOMElement* state, XERCESC
// namelist
processNameLists(doneEvent.namelist, doneData);
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in donedata element namelist", doneData);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in donedata element namelist", doneData);
}
@@ -521,7 +521,7 @@ void BasicContentExecutor::raiseDoneEvent(XERCESC_NS::DOMElement* state, XERCESC
// params
processParams(doneEvent.params, doneData);
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in donedata element param expr", doneData);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in donedata element param expr", doneData);
}
try {
@@ -531,7 +531,7 @@ void BasicContentExecutor::raiseDoneEvent(XERCESC_NS::DOMElement* state, XERCESC
doneEvent.data = elementAsData(contents.front());
}
} catch (Event e) {
- ERROR_EXECUTION_THROW2("Syntax error in donedata element content", doneData);
+ ERROR_EXECUTION_THROW3(e,"Syntax error in donedata element content", doneData);
}
} catch (ErrorEvent exc) {
@@ -691,9 +691,19 @@ Data BasicContentExecutor::elementAsData(XERCESC_NS::DOMElement* element, bool a
contentSS << X((*textIter)->getNodeValue());
}
+ // this must be handled in getAsData
// test294, test562
if (LOCALNAME(element) == "content") {
- return Data(spaceNormalize(contentSS.str()), Data::VERBATIM);
+ // need first try getAsData because how to pass 179 ?
+ try {
+ // test153, we need to throw for test150 in promela
+ Data d = _callbacks->getAsData(contentSS.str());
+ if (!d.empty())
+ return d;
+ }
+ catch (ErrorEvent &) {
+ return Data(spaceNormalize(contentSS.str()), Data::VERBATIM);
+ }
}
if (asExpression) // not actually used, but likely expected
diff --git a/src/uscxml/interpreter/ContentExecutorImpl.h b/src/uscxml/interpreter/ContentExecutorImpl.h
index 5f0acfe..fd6be89 100644
--- a/src/uscxml/interpreter/ContentExecutorImpl.h
+++ b/src/uscxml/interpreter/ContentExecutorImpl.h
@@ -59,6 +59,7 @@ public:
uint32_t iteration) = 0;
virtual Data evalAsData(const std::string& expr) = 0;
+ virtual void eval(const std::string& content) { evalAsData(expr); }
virtual Data getAsData(const std::string& expr) = 0;
virtual void assign(const std::string& location, const Data& data, const std::map<std::string, std::string>& attrs) = 0;
diff --git a/src/uscxml/interpreter/InterpreterImpl.h b/src/uscxml/interpreter/InterpreterImpl.h
index a68298b..fd6d393 100644
--- a/src/uscxml/interpreter/InterpreterImpl.h
+++ b/src/uscxml/interpreter/InterpreterImpl.h
@@ -187,6 +187,10 @@ public:
return _dataModel.evalAsData(expr);
}
+ virtual void eval(const std::string& content) {
+ _dataModel.eval(content);
+ }
+
virtual Data getAsData(const std::string& expr) {
return _dataModel.getAsData(expr);
}
diff --git a/src/uscxml/messages/Event.h b/src/uscxml/messages/Event.h
index b774f8a..fd9dd42 100644
--- a/src/uscxml/messages/Event.h
+++ b/src/uscxml/messages/Event.h
@@ -77,6 +77,14 @@
throw exc;\
}
+#define ERROR_EXECUTION_THROW3(evt,caption,node) \
+{\
+ auto it = evt.data.compound.find("cause"); \
+ ERROR_EXECUTION2(exc,it!=evt.data.compound.end() ? it->second.atom : "",node); \
+ exc.data.compound["caption"] = uscxml::Data(caption, uscxml::Data::VERBATIM); \
+ throw exc;\
+}
+
#define ERROR_COMMUNICATION_THROW(cause) \
{\
ERROR_COMMUNICATION(exc, cause); \
diff --git a/src/uscxml/plugins/DataModel.cpp b/src/uscxml/plugins/DataModel.cpp
index 07fd4b4..cf4dda2 100644
--- a/src/uscxml/plugins/DataModel.cpp
+++ b/src/uscxml/plugins/DataModel.cpp
@@ -42,6 +42,10 @@ Data DataModel::evalAsData(const std::string& content) {
return _impl->evalAsData(content);
}
+void DataModel::eval(const std::string& content) {
+ _impl->eval(content);
+}
+
bool DataModel::evalAsBool(const std::string& expr) {
return _impl->evalAsBool(expr);
}
diff --git a/src/uscxml/plugins/DataModel.h b/src/uscxml/plugins/DataModel.h
index 9185cc8..484f1cc 100644
--- a/src/uscxml/plugins/DataModel.h
+++ b/src/uscxml/plugins/DataModel.h
@@ -54,6 +54,8 @@ public:
virtual Data getAsData(const std::string& content);
/// @copydoc DataModelImpl::evalAsData()
virtual Data evalAsData(const std::string& content);
+ /// @copydoc DataModelImpl::eval()
+ virtual void eval(const std::string& content);
/// @copydoc DataModelImpl::evalAsBool()
virtual bool evalAsBool(const std::string& expr);
diff --git a/src/uscxml/plugins/DataModelImpl.h b/src/uscxml/plugins/DataModelImpl.h
index a804ea3..62453a8 100644
--- a/src/uscxml/plugins/DataModelImpl.h
+++ b/src/uscxml/plugins/DataModelImpl.h
@@ -173,6 +173,12 @@ public:
virtual Data evalAsData(const std::string& content) = 0;
/**
+ * evaluating script content without return
+ * @param mostly used in script content.
+ */
+ virtual void eval(const std::string& content) { evalAsData(content); }
+
+ /**
* Evaluate a given expression as a boolean.
* This function is a subset of evalAsData() but saves on creating and copying a Data object.
* @param expr An expression in the data-model's language.
diff --git a/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp b/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp
index 94d2467..ea90e8c 100644
--- a/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp
+++ b/src/uscxml/plugins/datamodel/lua/LuaDataModel.cpp
@@ -79,9 +79,12 @@ static int luaEval(lua_State* luaState, const std::string& expr) {
static Data getLuaAsData(lua_State* _luaState, const luabridge::LuaRef& lua) {
Data data;
if (lua.isFunction()) {
- // TODO: this might lead to a stack-overflow
- luabridge::LuaRef luaEvald = lua();
- return getLuaAsData(_luaState, luaEvald);
+ // we are creating __tmpFunc
+ // then it will be assigned to data variable
+ luabridge::setGlobal(_luaState, lua, "__tmpFunc");
+
+ data.atom = "__tmpFunc";
+ data.type = Data::INTERPRETED;
} else if(lua.isLightUserdata() || lua.isUserdata()) {
// not sure what to do
} else if(lua.isThread()) {
@@ -208,17 +211,24 @@ int LuaDataModel::luaInFunction(lua_State * l) {
std::shared_ptr<DataModelImpl> LuaDataModel::create(DataModelCallbacks* callbacks) {
std::shared_ptr<LuaDataModel> dm(new LuaDataModel());
- dm->_callbacks = callbacks;
- dm->setup();
+
+ dm->doCreate(callbacks);
+
return dm;
}
-void LuaDataModel::setup() {
- _luaState = luaL_newstate();
- luaL_openlibs(_luaState);
+LuaDataModel::~LuaDataModel() {
+ if (_luaState != NULL)
+ lua_close(_luaState);
+}
+
+void LuaDataModel::doCreate(DataModelCallbacks* callbacks) {
+ this->_callbacks = callbacks;
+ this->_luaState = luaL_newstate();
+ luaL_openlibs(this->_luaState);
try {
- const luabridge::LuaRef& requireFunc = luabridge::getGlobal(_luaState, "require");
+ const luabridge::LuaRef& requireFunc = luabridge::getGlobal(this->_luaState, "require");
if (requireFunc.isFunction()) {
const luabridge::LuaRef& resultLxp = requireFunc("lxp");
const luabridge::LuaRef& resultLxpLOM = requireFunc("lxp.lom");
@@ -226,47 +236,42 @@ void LuaDataModel::setup() {
// MSVC compiler bug with implicit cast operators, see comments in LuaRef class
if ((bool)resultLxp && (bool)resultLxpLOM) {
_luaHasXMLParser = true;
- luabridge::setGlobal(_luaState, resultLxp, "lxp");
- luabridge::setGlobal(_luaState, resultLxpLOM, "lxp.lom");
+ luabridge::setGlobal(this->_luaState, resultLxp, "lxp");
+ luabridge::setGlobal(this->_luaState, resultLxpLOM, "lxp.lom");
}
}
- } catch (luabridge::LuaException e) {
- LOG(_callbacks->getLogger(), USCXML_INFO) << e.what() << std::endl;
+ }
+ catch (luabridge::LuaException e) {
+ LOG(this->_callbacks->getLogger(), USCXML_INFO) << e.what() << std::endl;
}
- luabridge::getGlobalNamespace(_luaState).beginClass<LuaDataModel>("DataModel").endClass();
- luabridge::setGlobal(_luaState, this, "__datamodel");
+ luabridge::getGlobalNamespace(this->_luaState).beginClass<LuaDataModel>("DataModel").endClass();
+ luabridge::setGlobal(this->_luaState, this, "__datamodel");
- luabridge::getGlobalNamespace(_luaState).addCFunction("In", luaInFunction);
+ luabridge::getGlobalNamespace(this->_luaState).addCFunction("In", luaInFunction);
- luabridge::LuaRef ioProcTable = luabridge::newTable(_luaState);
- std::map<std::string, IOProcessor> ioProcs = _callbacks->getIOProcessors();
+ luabridge::LuaRef ioProcTable = luabridge::newTable(this->_luaState);
+ std::map<std::string, IOProcessor> ioProcs = this->_callbacks->getIOProcessors();
std::map<std::string, IOProcessor>::const_iterator ioProcIter = ioProcs.begin();
- while(ioProcIter != ioProcs.end()) {
+ while (ioProcIter != ioProcs.end()) {
Data ioProcData = ioProcIter->second.getDataModelVariables();
- ioProcTable[ioProcIter->first] = getDataAsLua(_luaState, ioProcData);
+ ioProcTable[ioProcIter->first] = getDataAsLua(this->_luaState, ioProcData);
ioProcIter++;
}
- luabridge::setGlobal(_luaState, ioProcTable, "_ioprocessors");
+ luabridge::setGlobal(this->_luaState, ioProcTable, "_ioprocessors");
- luabridge::LuaRef invTable = luabridge::newTable(_luaState);
- std::map<std::string, Invoker> invokers = _callbacks->getInvokers();
+ luabridge::LuaRef invTable = luabridge::newTable(this->_luaState);
+ std::map<std::string, Invoker> invokers = this->_callbacks->getInvokers();
std::map<std::string, Invoker>::const_iterator invIter = invokers.begin();
- while(invIter != invokers.end()) {
+ while (invIter != invokers.end()) {
Data invData = invIter->second.getDataModelVariables();
- invTable[invIter->first] = getDataAsLua(_luaState, invData);
+ invTable[invIter->first] = getDataAsLua(this->_luaState, invData);
invIter++;
}
- luabridge::setGlobal(_luaState, invTable, "_invokers");
-
- luabridge::setGlobal(_luaState, _callbacks->getName(), "_name");
- luabridge::setGlobal(_luaState, _callbacks->getSessionId(), "_sessionid");
-
-}
+ luabridge::setGlobal(this->_luaState, invTable, "_invokers");
-LuaDataModel::~LuaDataModel() {
- if (_luaState != NULL)
- lua_close(_luaState);
+ luabridge::setGlobal(this->_luaState, this->_callbacks->getName(), "_name");
+ luabridge::setGlobal(this->_luaState, this->_callbacks->getSessionId(), "_sessionid");
}
void LuaDataModel::addExtension(DataModelExtension* ext) {
@@ -353,49 +358,25 @@ void LuaDataModel::setEvent(const Event& event) {
Data LuaDataModel::evalAsData(const std::string& content) {
Data data;
- ErrorEvent originalError;
std::string trimmedExpr = boost::trim_copy(content);
- try {
- int retVals = luaEval(_luaState, "return(" + trimmedExpr + ")");
- if (retVals == 1) {
- data = getLuaAsData(_luaState, luabridge::LuaRef::fromStack(_luaState, -1));
- }
- lua_pop(_luaState, retVals);
- return data;
- } catch (ErrorEvent e) {
- originalError = e;
- }
-
- int retVals = 0;
- try {
- // evaluate again without the return()
- retVals = luaEval(_luaState, trimmedExpr);
- } catch (ErrorEvent e) {
- throw originalError; // we will assume syntax error and throw
- }
-
-#if 1
- // Note: Empty result is not an error test302, but how to do test488?
- if (retVals == 0 && !isValidSyntax(trimmedExpr)) {
- throw originalError; // we will assume syntax error and throw
+ int retVals = luaEval(_luaState, "return(" + trimmedExpr + ")");
+ if (retVals == 1) {
+ data = getLuaAsData(_luaState, luabridge::LuaRef::fromStack(_luaState, -1));
}
-#endif
+ lua_pop(_luaState, retVals);
+ return data;
+}
- try {
- if (retVals == 1) {
- data = getLuaAsData(_luaState, luabridge::LuaRef::fromStack(_luaState, -1));
- }
- lua_pop(_luaState, retVals);
- return data;
+void LuaDataModel::evalAsScript(const std::string& content) {
+ Data data;
- } catch (ErrorEvent e) {
- throw e; // we will assume syntax error and throw
- }
+ std::string trimmedExpr = boost::trim_copy(content);
+ int retVals = luaEval(_luaState, trimmedExpr);
- return data;
+ lua_pop(_luaState, retVals);
}
bool LuaDataModel::isValidSyntax(const std::string& expr) {
@@ -508,6 +489,7 @@ void LuaDataModel::assign(const std::string& location, const Data& data, const s
// JSObjectSetProperty(_ctx, JSContextGetGlobalObject(_ctx), JSStringCreateWithUTF8CString(location.c_str()), getNodeAsValue(data.node), 0, &exception);
} else {
+#if 0
// trigger error.execution for undefined locations, test286 test311
int retVals = luaEval(_luaState, location + " = " + location);
lua_pop(_luaState, retVals);
@@ -528,9 +510,14 @@ void LuaDataModel::assign(const std::string& location, const Data& data, const s
if (idPath.size() == 0)
return;
+#endif
luabridge::LuaRef lua = getDataAsLua(_luaState, data);
+ luabridge::setGlobal(_luaState, lua, "__tmpAssign");
+ evalAsScript(location + "= __tmpAssign");
+
+#if 0
if (idPath.size() == 1) {
// trivial case where we reference a simple toplevel identifier
luabridge::setGlobal(_luaState, lua, location.c_str());
@@ -583,7 +570,7 @@ void LuaDataModel::assign(const std::string& location, const Data& data, const s
}
}
-
+#endif
// std::cout << Data::toJSON(evalAsData(location)) << std::endl;
}
}
diff --git a/src/uscxml/plugins/datamodel/lua/LuaDataModel.h b/src/uscxml/plugins/datamodel/lua/LuaDataModel.h
index a43b959..93379fc 100644
--- a/src/uscxml/plugins/datamodel/lua/LuaDataModel.h
+++ b/src/uscxml/plugins/datamodel/lua/LuaDataModel.h
@@ -74,6 +74,7 @@ public:
virtual bool evalAsBool(const std::string& expr);
virtual Data evalAsData(const std::string& expr);
+ virtual void eval(const std::string& content);
virtual Data getAsData(const std::string& content);
virtual bool isDeclared(const std::string& expr);
diff --git a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h
index 61931c6..b030d8b 100644
--- a/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h
+++ b/src/uscxml/plugins/invoker/scxml/USCXMLInvoker.h
@@ -23,6 +23,7 @@
#include "uscxml/config.h"
#include "uscxml/interpreter/InterpreterImpl.h"
#include "uscxml/interpreter/BasicEventQueue.h"
+#include "uscxml/interpreter/LoggingImpl.h"
#include "uscxml/plugins/InvokerImpl.h"
diff --git a/test/issues/test-issue116.scxml b/test/issues/test-issue116.scxml
new file mode 100644
index 0000000..ca1cc56
--- /dev/null
+++ b/test/issues/test-issue116.scxml
@@ -0,0 +1,43 @@
+<scxml datamodel="lua" initial="StateShape1" name="ScxmlShape1" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
+ <datamodel>
+ <data id="varFunc1">function()
+ print(&quot;Hello from function&quot;)
+end
+ </data>
+ <data id="varFunc2">function()
+ return 2
+end
+ </data>
+ <data expr="nil" id="varFunc3"/>
+ </datamodel>
+ <final id="Pass">
+ <onentry>
+ <log expr="&quot;Pass&quot;" label="Outcome"/>
+ </onentry>
+ </final>
+ <final id="Fail">
+ <onentry>
+ <log expr="&quot;Fail&quot;" label="Outcome"/>
+ </onentry>
+ </final>
+ <state id="StateShape1">
+ <onentry>
+ <assign expr="varFunc2" location="varFunc3"/>
+ <assign expr="function()
+ return 4
+end" location="varFunc4"/>
+ <log expr="''" label="Test"/>
+ <script>print(&quot;this is from Script!&quot;)
+
+print(string.format(&quot;varFunc3()==%d&quot;,varFunc3()))
+print(string.format(&quot;varFunc2()==%d&quot;,varFunc2()))
+
+varFunc1()
+
+print(string.format(&quot;varFunc4()==%d&quot;,varFunc4()))
+ </script>
+ </onentry>
+ <transition cond="varFunc4()==4 and varFunc3()==2" target="Pass"/>
+ <transition target="Fail"/>
+ </state>
+</scxml> \ No newline at end of file
diff --git a/test/w3c/lua/test562.scxml b/test/w3c/lua/test562.scxml
new file mode 100644
index 0000000..3be5072
--- /dev/null
+++ b/test/w3c/lua/test562.scxml
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scxml datamodel="lua" initial="s0" name="ScxmlShape1" version="1.0" xmlns="http://www.w3.org/2005/07/scxml" xmlns:conf="http://www.w3.org/2005/scxml-conformance">
+ <state id="s0">
+ <onentry>
+ <send event="foo">
+ <content>
+this is a
+string
+ </content>
+ </send>
+ </onentry>
+ <transition cond="_event.data == 'this is a string'" event="foo" target="pass"/>
+ <transition event="*" target="fail"/>
+ </state>
+ <final id="pass">
+ <onentry>
+ <log expr="'pass'" label="Outcome"/>
+ </onentry>
+ </final>
+ <final id="fail">
+ <onentry>
+ <log expr="'fail'" label="Outcome"/>
+ </onentry>
+ </final>
+</scxml><!--VFBGMAdUVHJlZUV4AARMZWZ0AhEDVG9wAhEFV2lkdGgDUgQGSGVpZ2h0A8ABCURlc2lnbmluZw
+kMR3JpZC5WaXNpYmxlCQtQYWdlLkhlaWdodAMiBApQYWdlLldpZHRoAwMDD1BhZ2UuVXNlUHJpb
+nRlcggVU2VsZWN0ZWQuU2Nyb2xsVG9WaWV3CA5TaG93SGludFNoYXBlcwgRVmVydFNjcm9sbEJh
+ci5NYXgDCQIWVmVydFNjcm9sbEJhci5QYWdlU2l6ZQPAARVWZXJ0U2Nyb2xsQmFyLlZpc2libGU
+JDVpvb20uUGVuLk1vZGUHCHBtTm90WG9yBUFsaWduBwhhbENsaWVudAhUYWJPcmRlcgIDAAtUU2
+N4bWxTaGFwZQtTY3htbFNoYXBlMQhBdXRvU2l6ZQgJUm91bmRTaXplAgoOU2hhZG93LlZpc2lib
+GUIAlgwAh4CWDEDEgICWTACFAJZMQMIAghFeHBhbmRlZAkKSW1hZ2VJbmRleAcGdGlOb25lDlRl
+eHQuVmVydEFsaWduBwZ2dGFUb3AMVGV4dC5TdHJpbmdzAQYLU2N4bWxTaGFwZTEAC0V4dHJhUGF
+yYW1zBjV4bWxuczpjb25mPWh0dHA6Ly93d3cudzMub3JnLzIwMDUvc2N4bWwtY29uZm9ybWFuY2
+UNCglTY3htbE5hbWUGC1NjeG1sU2hhcGUxB0luaXRpYWwGAnMwCURhdGFtb2RlbAYDbHVhAAALV
+FN0YXRlU2hhcGULU3RhdGVTaGFwZTEIQXV0b1NpemUIDEJvcmRlci5Db2xvcgT/gAAADEJvcmRl
+ci5XaWR0aAICC0JydXNoLkNvbG9yBIDW/wAFQ29sb3IEgNb/AAlSb3VuZFNpemUCCg5TaGFkb3c
+uVmlzaWJsZQgFU3R5bGUHEXRzc1JvdW5kUmVjdGFuZ2xlAlgwAjwCWDEDoAACWTACMgJZMQJkCE
+V4cGFuZGVkCQpJbWFnZUluZGV4BwZ0aU5vbmUGUGFyZW50BxdUcmVlRWRpdG9yMS5TY3htbFNoY
+XBlMQxUZXh0LlN0cmluZ3MBBgJzMAACSWQGAnMwCUlzSW5pdGlhbAkAABJUT25FbnRyeVN0YXRl
+U2hhcGUST25FbnRyeVN0YXRlU2hhcGUxEUltYWdlLlRyYW5zcGFyZW50CQpJbWFnZS5EYXRhCvw
+BAAAJVFBuZ0ltYWdliVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAA
+sSAAALEgHS3X78AAAABGdBTUEAALGOfPtRkwAAAZRJREFUeNqdkk1LQkEUht9JS1PCWyC1kFZBE
+AbXRWSBoLSKNkFpRAuVwF/Quh/SRl20adWuVSi0SFqkiARR2qZSy/zIvH5d7zT3cs0wbeEL7xzm
+MM+ZYc4hlFKMIiKDhJBexg4fW73MvJpJMIcRQ6h7RGF+QDs4lossL1t5q2MJ0/MmCG0B6fsM0rE
+nvD7m5AIuVqDcD8bdezv8zAqHTDGDWkOA0BSU2O600bhrIneTTzDQ1gPXiI/dFFz3rv6BlMjcFk
+XQmAQhW/fTaxpSQK2DRDwBt7PAFQZCQqOOltjC+IcW0q0YFa+oSwH1G6TkPz7kkm+poZAsjaSB9
+qpTblzSaQU0bpLS7tE+l8ymhkJdGa5Rrl2ooGmbRGxbq85XUvgX0lQB4wOilXP1qTMe4jNbZoOt
+RR0rVxkIyTK+ABMl+Itn6ufI7TAfID61YObzkzXlxn7pP4GpHBLvp7/bwcA5LzgyhojGpOfLBtY
+3HYUICbomMPnFXEWCSnDlwv0DoMoSgI9tvSDqyFEGUISfTwaM3Cj6BmO5JfTInnBwAAAAAElFTk
+SuQmCCDlNoYWRvdy5WaXNpYmxlCAhFeHBhbmRlZAkKSW1hZ2VJbmRleAcGdGlOb25lBlBhcmVud
+AcXVHJlZUVkaXRvcjEuU3RhdGVTaGFwZTEMVGV4dC5TdHJpbmdzAQYHb25lbnRyeQAAAApUU2Vu
+ZFNoYXBlClNlbmRTaGFwZTERSW1hZ2UuVHJhbnNwYXJlbnQJCkltYWdlLkRhdGEKVwMAAAlUUG5
+nSW1hZ2WJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACToAAAk6Af
+BkkkoAAAL/SURBVHjapZNLTBNRGIXP7Uw740yhUWNViIAaogtFMFFjE1M1UagvEkGNqHHhpksX7
+twoJtYViXHRxCe28RE1LnSDCBQNCsGyIFqgtbRALbGlYk2hlnl6Oz4SF66c2dw7uef7/3P/M0TX
+dfzPQ440Hz7ncGzfvaZq9YKqKtDpiz9Muvu1ZhgTpAWJ6ezq/jQ1lThrZlnJAFy72uZzOneeqqi
+soocINFWDpmlUqKGoLq5NJhNkRUY8FkMwGIxQwEb2N+C+v917qLHRrelUrKmwWq1QVRX6LwjDMP
+g6+wVj4Qiq165BaCQUDPS+dvAcJxsA350b3gMHDrpLSm3IZDJQaAfL7csgSwWj9cnJSYyOjmJzX
+R3KylaAioOv+t78DXC59hkAQgiSySSi0Qg219YiFo8jlUpj69YtEAQBnMWCvjcDwUDglYNfxMvF
+uyK+dgpocLlFaykURYJUKCAciWBiMmF0UltXC1EQQT3jRaATH8Pjg5n0zDaGZWhBA3DL29BQ7xY
+WCZifyxn+S202zOfzEPkSzM3l6Slg6RI7rj++iqe9D2aG+0IOntejtCaI/+5tb/3ePe6iH0VRII
+oiGFpNkVXced6GAslCUwAza0H2exriMmqjp3+4vyNymLqOkYf3/Dedzh1nOI4zfBbFLGum3eThe
+dKCxRUEJp0zRsoyZpgZC0ysjtddgyP9HdFj5FLrhfObajY2C6JokmWJVRWVVRSdzeXmmZDatrK8
+Jm/RZBY/01UMlg5iIuBEFc9uTPSQixcvME1NRyzpdJpPpT+L8VjcmkxOi5mZr7by/U/aN7kWVkn
+ff6aRxoSmEbDwQHgAE49acZRcueJBS8sJ5HI5SJKEOB3d7GwWobFBMi7f9JavU9bThKs0U6rNjq
+qaXah+H8B4jx+NiRGEiMdzGcePtxiAAh1hEfAtO4/OPh8GPwToCEEY6mA6Cr1yA1p3nsTprttoo
+Nc0Sr/hH4A5dL99gKHwS9gWA9kUEB0C7JU4WlaNOLXyjhOAxBgF/O/v/AN+1H00GUkzLwAAAABJ
+RU5ErkJggg5TaGFkb3cuVmlzaWJsZQgIRXhwYW5kZWQJCkltYWdlSW5kZXgHBnRpTm9uZQZQYXJ
+lbnQHHlRyZWVFZGl0b3IxLk9uRW50cnlTdGF0ZVNoYXBlMQxUZXh0LlN0cmluZ3MBBgpzZW5kIH
+tmb299AAVFdmVudAYDZm9vIFByb3RvY29sQ29udHJvbEJpbmRpbmcuU2N4bWxOYW1lBgNmb28gU
+HJvdG9jb2xDb250cm9sQmluZGluZy5CaW5kVHlwZXMLC3BidFN1cHBsaWVyAAAADVRDb250ZW50
+U2hhcGUNQ29udGVudFNoYXBlMRFJbWFnZS5UcmFuc3BhcmVudAkKSW1hZ2UuRGF0YQr0AgAACVR
+QbmdJbWFnZYlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAABl0RVh0U29mdHdhcm
+UAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKMSURBVHjajdJ/SFNRFAfw7918+6Ebc1MkFHFWbOKbF
+TNGk+oPBymoFCwxCRIWaZILFgzDIvqjqBAU1KgpCQYjpIRQiUosCPGfYDTSLKk0zNwc/nxPnWm+
+7iZ7+2944MLh3nM/7533DilvbEQkJBIJZAxjpWkB9hiCIAySGEAIsTrt9iZbYWE5PUh4cX1zc+F
+mV1fLdCDgJSUuV2y/9kVzsye8sZHwskwuR8/gYM+rkZE7SQzznZysrxeBZxTgeT4hoFKpUO1210
+ml0k66QI7W1IhAX3u7h+O4hIBarYbd6ayjaWe09fzKShF40929J6DE4YgD+rIyERjp7Y22ML0Cv
+PxB8IuTRA9y1Ds4c0CAXrPbwvGqqjiQUVwsAv6BAc/YzDKe+NZhMeggJQQMI0GSVIJ3/iAumpNh
+yk7F4YqKOKC2WkXg5/Cw57r3Ewymg+C4MPJz0+D/GoClUI+p30uY8E/i/vkj2G+zxQGZ2SwCgdF
+RT8WNPqSxxzAXCiFFpYBaqQS/xMHIGvF5qB8Dd+3YV1QUB4jJFAPqVny+x6Wup0CeDcHlEAQpnb
+bNbeTo8xCa+gbNwjhet16Axmy+TOs9UQAGQwy4sjY+3tHQ9hZ+PhW8TA1GJoOwtYUUZTLI6jzY5
+EV0XD2FFJZtoPUPd4GsLEChiOSueZ+v5cvMEm49n8Q/TQ60Wm1k4LEQDEK5PY8H50woyE2Hwmi8
+RutbEQ5TQKcDrWRPl5bedjocZxk6XX84gv6xMKYXtyOPgCFDgdoTmSjI1kBO30puNDbRy/cwO0u
+/gU7H5lssl957vTXpWm1qwimiv/WR1/uhwe1uE+bmhujOX0KUykOSzMxq7DGEtbWJnUDgI01X6e
+L/A7IW5iT2WNLlAAAAAElFTkSuQmCCDlNoYWRvdy5WaXNpYmxlCApJbWFnZUluZGV4BwZ0aU5vb
+mUGUGFyZW50BxZUcmVlRWRpdG9yMS5TZW5kU2hhcGUxDFRleHQuU3RyaW5ncwEGIGNvbnRlbnQg
+ew0KdGhpcyBpcyAgYSAgDQpzdHJpbmd9AAxYTUxUZXh0LlRleHQGFg0KdGhpcyBpcyAgYSAgDQp
+zdHJpbmcAAAtURmluYWxTaGFwZQtGaW5hbFNoYXBlMQhBdXRvU2l6ZQgJUm91bmRTaXplAgoOU2
+hhZG93LlZpc2libGUIBVN0eWxlBwl0c3NDaXJjbGUCWDADlgACWDEDrwACWTADBAECWTEDHQEIR
+XhwYW5kZWQJCkltYWdlSW5kZXgHBnRpTm9uZQZQYXJlbnQHF1RyZWVFZGl0b3IxLlNjeG1sU2hh
+cGUxD1RleHQuVmVydE9mZnNldALODFRleHQuU3RyaW5ncwEGBHBhc3MAAklkBgRwYXNzAAASVE9
+uRW50cnlTdGF0ZVNoYXBlEk9uRW50cnlTdGF0ZVNoYXBlMhFJbWFnZS5UcmFuc3BhcmVudAkKSW
+1hZ2UuRGF0YQr8AQAACVRQbmdJbWFnZYlQTkcNChoKAAAADUlIRFIAAAAOAAAADggGAAAAH0gt0
+QAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjnz7UZMAAAGUSURBVHjanZJNS0JBFIbf
+SUtTwlsgtZBWQRAG10VkgaC0ijZBaUQLlcBf0Lof0kZdtGnVrlUotEhapIgEUdqmUsv8yLx+Xe8
+093LNMG3hC+8c5jDPmWHOIZRSjCIig4SQXsYOH1u9zLyaSTCHEUOoe0RhfkA7OJaLLC9beatjCd
+PzJghtAen7DNKxJ7w+5uQCLlag3A/G3Xs7/MwKh0wxg1pDgNAUlNjutNG4ayJ3k08w0NYD14iP3
+RRc967+gZTI3BZF0JgEIVv302saUkCtg0Q8AbezwBUGQkKjjpbYwviHFtKtGBWvqEsB9Ruk5D8+
+5JJvqaGQLI2kgfaqU25c0mkFNG6S0u7RPpfMpoZCXRmuUa5dqKBpm0RsW6vOV1L4F9JUAeMDopV
+z9akzHuIzW2aDrUUdK1cZCMkyvgATJfiLZ+rnyO0wHyA+tWDm85M15cZ+6T+BqRwS76e/28HAOS
+84MoaIxqTnywbWNx2FCAm6JjD5xVxFgkpw5cL9A6DKEoCPbb0g6shRBlCEn08GjNwo+gZjuSX0y
+J5wcAAAAABJRU5ErkJggg5TaGFkb3cuVmlzaWJsZQgIRXhwYW5kZWQJCkltYWdlSW5kZXgHBnRp
+Tm9uZQZQYXJlbnQHF1RyZWVFZGl0b3IxLkZpbmFsU2hhcGUxDFRleHQuU3RyaW5ncwEGB29uZW5
+0cnkAAAAJVExvZ1NoYXBlCUxvZ1NoYXBlMRFJbWFnZS5UcmFuc3BhcmVudAkKSW1hZ2UuRGF0YQ
+pBAgAACVRQbmdJbWFnZYlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAAARnQU1BA
+ACvyDcFiukAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAAByUlEQVR42mP8
+//8/AyWAkWoGVFRUoJj07ds3hk+fPjG8f/+e4d27dwxv374Fs3/+Y2H4y8TO8PH5HUaSXRA9/4k
+okDoIxOJAbLs0UeYahgvw2Wydv5jBxUybgfHfX4YDNz68+fnjuz1RLoDZbCLNoqknwcbw8fMfhl
+N33jEcufpkE9yAvLy8Umw2/+cS47bImpdpoykhZijDyXDm5icGMT42hgMXHzKcu/5oGU4X/NrLK
+PHsh6L7pc8Wvc+40oVVFXThmvecvsNw+uq9vb9//PTFagBQs/d/Fokt/3ktGZj4LRm+XGlk2PVr
+AsN3Pi+GHcdvMJy6eHvvn9+/fR9vyfmO1YDvOxkDGUUD17GpFQI51xn+/OJneHsolmHivTaGpcc
+59gKVgDXjjMav2xijmCSilrLJWjMwfDnNwMjrxPDp3imGvtX3bs+/7qkP04zTgI+bGNNYZNNm/h
+e2YmBn+snw+ycbw5e7Kxme3jrib5jzeRPBpPxuHeO0N2wOmUIMzxl+vLt54e9vhst//zC8/vGNo
+UOr8P9rrAbY2NisBFISwPTJmOf93NxG7t5ioKZumfj/N4nKCzDwaiWjKFAjg2Q0qk1EG0AqAAD0
+sxPNyYZgMQAAAABJRU5ErkJggg5TaGFkb3cuVmlzaWJsZQgKSW1hZ2VJbmRleAcGdGlOb25lBlB
+hcmVudAceVHJlZUVkaXRvcjEuT25FbnRyeVN0YXRlU2hhcGUyDFRleHQuU3RyaW5ncwEGFGxvZy
+B7T3V0Y29tZToncGFzcyd9AAVMYWJlbAYHT3V0Y29tZQRFeHByBgYncGFzcycAAAtURmluYWxTa
+GFwZQtGaW5hbFNoYXBlMghBdXRvU2l6ZQgJUm91bmRTaXplAgoOU2hhZG93LlZpc2libGUIBVN0
+eWxlBwl0c3NDaXJjbGUCWDADIgECWDEDOwECWTAD6wACWTEDBAEIRXhwYW5kZWQJCkltYWdlSW5
+kZXgHBnRpTm9uZQZQYXJlbnQHF1RyZWVFZGl0b3IxLlNjeG1sU2hhcGUxD1RleHQuVmVydE9mZn
+NldALODFRleHQuU3RyaW5ncwEGBGZhaWwAAklkBgRmYWlsAAASVE9uRW50cnlTdGF0ZVNoYXBlE
+k9uRW50cnlTdGF0ZVNoYXBlMxFJbWFnZS5UcmFuc3BhcmVudAkKSW1hZ2UuRGF0YQr8AQAACVRQ
+bmdJbWFnZYlQTkcNChoKAAAADUlIRFIAAAAOAAAADggGAAAAH0gt0QAAAAlwSFlzAAALEgAACxI
+B0t1+/AAAAARnQU1BAACxjnz7UZMAAAGUSURBVHjanZJNS0JBFIbfSUtTwlsgtZBWQRAG10Vkga
+C0ijZBaUQLlcBf0Lof0kZdtGnVrlUotEhapIgEUdqmUsv8yLx+Xe8093LNMG3hC+8c5jDPmWHOI
+ZRSjCIig4SQXsYOH1u9zLyaSTCHEUOoe0RhfkA7OJaLLC9beatjCdPzJghtAen7DNKxJ7w+5uQC
+Llag3A/G3Xs7/MwKh0wxg1pDgNAUlNjutNG4ayJ3k08w0NYD14iP3RRc967+gZTI3BZF0JgEIVv
+302saUkCtg0Q8AbezwBUGQkKjjpbYwviHFtKtGBWvqEsB9Ruk5D8+5JJvqaGQLI2kgfaqU25c0m
+kFNG6S0u7RPpfMpoZCXRmuUa5dqKBpm0RsW6vOV1L4F9JUAeMDopVz9akzHuIzW2aDrUUdK1cZC
+MkyvgATJfiLZ+rnyO0wHyA+tWDm85M15cZ+6T+BqRwS76e/28HAOS84MoaIxqTnywbWNx2FCAm6
+JjD5xVxFgkpw5cL9A6DKEoCPbb0g6shRBlCEn08GjNwo+gZjuSX0yJ5wcAAAAABJRU5ErkJggg5
+TaGFkb3cuVmlzaWJsZQgIRXhwYW5kZWQJCkltYWdlSW5kZXgHBnRpTm9uZQZQYXJlbnQHF1RyZW
+VFZGl0b3IxLkZpbmFsU2hhcGUyDFRleHQuU3RyaW5ncwEGB29uZW50cnkAAAAJVExvZ1NoYXBlC
+UxvZ1NoYXBlMhFJbWFnZS5UcmFuc3BhcmVudAkKSW1hZ2UuRGF0YQpBAgAACVRQbmdJbWFnZYlQ
+TkcNChoKAAAADUlIRFIAAAAQAAAAEAgGAAAAH/P/YQAAAARnQU1BAACvyDcFiukAAAAZdEVYdFN
+vZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAAByUlEQVR42mP8//8/AyWAkWoGVFRUoJj07d
+s3hk+fPjG8f/+e4d27dwxv374Fs3/+Y2H4y8TO8PH5HUaSXRA9/4kokDoIxOJAbLs0UeYahgvw2
+Wydv5jBxUybgfHfX4YDNz68+fnjuz1RLoDZbCLNoqknwcbw8fMfhlN33jEcufpkE9yAvLy8Umw2
+/+cS47bImpdpoykhZijDyXDm5icGMT42hgMXHzKcu/5oGU4X/NrLKPHsh6L7pc8Wvc+40oVVFXT
+hmvecvsNw+uq9vb9//PTFagBQs/d/Fokt/3ktGZj4LRm+XGlk2PVrAsN3Pi+GHcdvMJy6eHvvn9
++/fR9vyfmO1YDvOxkDGUUD17GpFQI51xn+/OJneHsolmHivTaGpcc59gKVgDXjjMav2xijmCSil
+rLJWjMwfDnNwMjrxPDp3imGvtX3bs+/7qkP04zTgI+bGNNYZNNm/he2YmBn+snw+ycbw5e7Kxme
+3jrib5jzeRPBpPxuHeO0N2wOmUIMzxl+vLt54e9vhst//zC8/vGNoUOr8P9rrAbY2NisBFISwPT
+JmOf93NxG7t5ioKZumfj/N4nKCzDwaiWjKFAjg2Q0qk1EG0AqAAD0sxPNyYZgMQAAAABJRU5Erk
+Jggg5TaGFkb3cuVmlzaWJsZQgKSW1hZ2VJbmRleAcGdGlOb25lBlBhcmVudAceVHJlZUVkaXRvc
+jEuT25FbnRyeVN0YXRlU2hhcGUzDFRleHQuU3RyaW5ncwEGFGxvZyB7T3V0Y29tZTonZmFpbCd9
+AAVMYWJlbAYHT3V0Y29tZQRFeHByBgYnZmFpbCcAAA9UVHJlZUNvbm5lY3Rpb24eU3RhdGVTaGF
+wZTFfT25FbnRyeVN0YXRlU2hhcGUxCUZyb21TaGFwZQcXVHJlZUVkaXRvcjEuU3RhdGVTaGFwZT
+EHVG9TaGFwZQceVHJlZUVkaXRvcjEuT25FbnRyeVN0YXRlU2hhcGUxAAAPVFRyZWVDb25uZWN0a
+W9uHU9uRW50cnlTdGF0ZVNoYXBlMV9TZW5kU2hhcGUxCUZyb21TaGFwZQceVHJlZUVkaXRvcjEu
+T25FbnRyeVN0YXRlU2hhcGUxB1RvU2hhcGUHFlRyZWVFZGl0b3IxLlNlbmRTaGFwZTEAAA9UVHJ
+lZUNvbm5lY3Rpb24YU2VuZFNoYXBlMV9Db250ZW50U2hhcGUxCUZyb21TaGFwZQcWVHJlZUVkaX
+RvcjEuU2VuZFNoYXBlMQdUb1NoYXBlBxlUcmVlRWRpdG9yMS5Db250ZW50U2hhcGUxAAAPVFRyZ
+WVDb25uZWN0aW9uHkZpbmFsU2hhcGUxX09uRW50cnlTdGF0ZVNoYXBlMglGcm9tU2hhcGUHF1Ry
+ZWVFZGl0b3IxLkZpbmFsU2hhcGUxB1RvU2hhcGUHHlRyZWVFZGl0b3IxLk9uRW50cnlTdGF0ZVN
+oYXBlMgAAD1RUcmVlQ29ubmVjdGlvbhxPbkVudHJ5U3RhdGVTaGFwZTJfTG9nU2hhcGUxCUZyb2
+1TaGFwZQceVHJlZUVkaXRvcjEuT25FbnRyeVN0YXRlU2hhcGUyB1RvU2hhcGUHFVRyZWVFZGl0b
+3IxLkxvZ1NoYXBlMQAAD1RUcmVlQ29ubmVjdGlvbh5GaW5hbFNoYXBlMl9PbkVudHJ5U3RhdGVT
+aGFwZTMJRnJvbVNoYXBlBxdUcmVlRWRpdG9yMS5GaW5hbFNoYXBlMgdUb1NoYXBlBx5UcmVlRWR
+pdG9yMS5PbkVudHJ5U3RhdGVTaGFwZTMAAA9UVHJlZUNvbm5lY3Rpb24cT25FbnRyeVN0YXRlU2
+hhcGUzX0xvZ1NoYXBlMglGcm9tU2hhcGUHHlRyZWVFZGl0b3IxLk9uRW50cnlTdGF0ZVNoYXBlM
+wdUb1NoYXBlBxVUcmVlRWRpdG9yMS5Mb2dTaGFwZTIAABdUU3RhdGVNYWNoaW5lQ29ubmVjdGlv
+bg9UcmVlQ29ubmVjdGlvbjETQXJyb3dGcm9tLkJhY2tDb2xvcgcHY2xXaGl0ZRlBcnJvd0Zyb20
+uQnJ1c2guQmFja0NvbG9yBwdjbFdoaXRlDkFycm93RnJvbS5TaXplAgoPQXJyb3dGcm9tLlN0eW
+xlBwljYXNDaXJjbGUMQXJyb3dUby5TaXplAgoFU3R5bGUHBmNzTGluZQlGcm9tU2hhcGUHF1RyZ
+WVFZGl0b3IxLlN0YXRlU2hhcGUxB1RvU2hhcGUHF1RyZWVFZGl0b3IxLkZpbmFsU2hhcGUxClRl
+eHQuQW5nbGUCWgxUZXh0LlN0cmluZ3MBBgNmb28GIV9ldmVudC5kYXRhID09ICd0aGlzIGlzIGE
+gc3RyaW5nJwAFRXZlbnQGA2ZvbwlDb25kaXRpb24GIV9ldmVudC5kYXRhID09ICd0aGlzIGlzIG
+Egc3RyaW5nJwZQYXJhbXMOACBQcm90b2NvbENvbnRyb2xCaW5kaW5nLlNjeG1sTmFtZQYDZm9vA
+AAXVFN0YXRlTWFjaGluZUNvbm5lY3Rpb24PVHJlZUNvbm5lY3Rpb24yE0Fycm93RnJvbS5CYWNr
+Q29sb3IHB2NsV2hpdGUZQXJyb3dGcm9tLkJydXNoLkJhY2tDb2xvcgcHY2xXaGl0ZQ5BcnJvd0Z
+yb20uU2l6ZQIKD0Fycm93RnJvbS5TdHlsZQcJY2FzQ2lyY2xlDEFycm93VG8uU2l6ZQIKBVN0eW
+xlBwZjc0xpbmUJRnJvbVNoYXBlBxdUcmVlRWRpdG9yMS5TdGF0ZVNoYXBlMQdUb1NoYXBlBxdUc
+mVlRWRpdG9yMS5GaW5hbFNoYXBlMgpUZXh0LkFuZ2xlAloMVGV4dC5TdHJpbmdzAQYBKgAFRXZl
+bnQGASoGUGFyYW1zDgAgUHJvdG9jb2xDb250cm9sQmluZGluZy5TY3htbE5hbWUGASoAAAA=--> \ No newline at end of file