From ff86d690dc02d7dd495000331d378e7d8eb688ac Mon Sep 17 00:00:00 2001 From: Stefan Radomski Date: Mon, 19 Jan 2015 17:41:18 +0100 Subject: Plenty of smaller fixes and adaptations --- CMakeLists.txt | 14 + apps/uscxml-analyze.cpp | 193 +++ apps/uscxml-browser.cpp | 36 +- apps/uscxml-transform.cpp | 119 +- contrib/local/annotate-xml-lineno.pl | 43 + contrib/local/create-random-scxml.pl | 214 +++ src/bindings/swig/csharp/uscxml.i | 15 +- src/uscxml/Common.h | 24 + src/uscxml/Convenience.cpp | 128 ++ src/uscxml/Convenience.h | 90 +- src/uscxml/DOMUtils.cpp | 6 +- src/uscxml/DOMUtils.h | 3 +- src/uscxml/Interpreter.cpp | 97 +- src/uscxml/Interpreter.h | 2 +- src/uscxml/debug/InterpreterIssue.cpp | 59 +- src/uscxml/debug/SCXMLDotWriter.cpp | 2 +- src/uscxml/interpreter/InterpreterDraft6.cpp | 4 +- src/uscxml/interpreter/InterpreterRC.cpp | 6 +- .../plugins/datamodel/promela/PromelaDataModel.cpp | 12 +- .../plugins/datamodel/promela/PromelaDataModel.h | 1 + .../invoker/xhtml/template/xhtml-invoker.inc.h | 1376 +++++++++--------- src/uscxml/transform/ChartToCPP.cpp | 546 -------- src/uscxml/transform/ChartToCPP.cpp.todo | 546 ++++++++ src/uscxml/transform/ChartToCPP.h | 72 - src/uscxml/transform/ChartToCPP.h.todo | 72 + src/uscxml/transform/ChartToFSM.cpp | 798 ++++++++++- src/uscxml/transform/ChartToFSM.cpp.new | 1482 ++++++++++++++++++++ src/uscxml/transform/ChartToFSM.h | 115 +- src/uscxml/transform/ChartToFSM.h.new | 303 ++++ src/uscxml/transform/ChartToFlatSCXML.cpp | 139 +- src/uscxml/transform/ChartToMinimalSCXML.cpp | 35 +- src/uscxml/transform/ChartToMinimalSCXML.h | 3 + src/uscxml/transform/ChartToPromela.cpp | 457 +++--- src/uscxml/transform/ChartToPromela.h | 31 +- src/uscxml/transform/ChartToTex.cpp | 284 ++++ src/uscxml/transform/ChartToTex.h | 61 + src/uscxml/transform/FlatStateIdentifier.h | 20 +- test/uscxml/promela/test-complete.scxml | 154 ++ test/uscxml/promela/test-event-source-auto.scxml | 22 + test/w3c/analyze_tests.pl | 21 +- test/w3c/run_promela_test.cmake | 5 +- 41 files changed, 5789 insertions(+), 1821 deletions(-) create mode 100644 apps/uscxml-analyze.cpp create mode 100755 contrib/local/annotate-xml-lineno.pl create mode 100755 contrib/local/create-random-scxml.pl create mode 100644 src/uscxml/Convenience.cpp delete mode 100644 src/uscxml/transform/ChartToCPP.cpp create mode 100644 src/uscxml/transform/ChartToCPP.cpp.todo delete mode 100644 src/uscxml/transform/ChartToCPP.h create mode 100644 src/uscxml/transform/ChartToCPP.h.todo create mode 100644 src/uscxml/transform/ChartToFSM.cpp.new create mode 100644 src/uscxml/transform/ChartToFSM.h.new create mode 100644 src/uscxml/transform/ChartToTex.cpp create mode 100644 src/uscxml/transform/ChartToTex.h create mode 100644 test/uscxml/promela/test-complete.scxml create mode 100644 test/uscxml/promela/test-event-source-auto.scxml diff --git a/CMakeLists.txt b/CMakeLists.txt index 789fb2b..edb8716 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1235,6 +1235,20 @@ if (NOT CMAKE_CROSSCOMPILING) install_executable(TARGETS uscxml-transform COMPONENT tools) if (WIN32) + add_executable(uscxml-analyze apps/uscxml-analyze.cpp ${PROJECT_SOURCE_DIR}/contrib/src/getopt/getopt.c) + else() + add_executable(uscxml-analyze apps/uscxml-analyze.cpp) + endif() + target_link_libraries(uscxml-analyze uscxml uscxml_transform) + if (NOT CMAKE_CROSSCOMPILING) + if (ENABLE_COTIRE) + set_target_properties(uscxml-analyze PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE) + endif() + endif() + set_target_properties(uscxml-analyze PROPERTIES FOLDER "Apps") + install_executable(TARGETS uscxml-analyze COMPONENT tools) + + if (WIN32) add_executable(uscxml-dot apps/uscxml-dot.cpp ${PROJECT_SOURCE_DIR}/contrib/src/getopt/getopt.c) else() add_executable(uscxml-dot apps/uscxml-dot.cpp) diff --git a/apps/uscxml-analyze.cpp b/apps/uscxml-analyze.cpp new file mode 100644 index 0000000..75ec48c --- /dev/null +++ b/apps/uscxml-analyze.cpp @@ -0,0 +1,193 @@ +#include "uscxml/config.h" +#include "uscxml/Interpreter.h" +#include "uscxml/transform/ChartToFSM.h" +#include "uscxml/DOMUtils.h" +#include +#include +#include + +#include "uscxml/Factory.h" +#include "uscxml/server/HTTPServer.h" +#include "getopt.h" + +#ifdef HAS_SIGNAL_H +#include +#endif + +#ifdef HAS_EXECINFO_H +#include +#endif + +#ifdef HAS_DLFCN_H +#include +#endif + +#define ANNOTATE(envKey, annotationParam) \ +envVarIsTrue(envKey) || std::find(annotations.begin(), annotations.end(), annotationParam) != annotations.end() + +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()); +#ifdef BUILD_AS_PLUGINS + printf(" [-p pluginPath]"); +#endif + printf(" [URL]"); + printf("\n"); + exit(1); +} + +int main(int argc, char** argv) { + using namespace uscxml; + + std::string outType; + std::string pluginPath; + std::string inputFile; + std::string outputFile; + std::list annotations; + +#if defined(HAS_SIGNAL_H) && !defined(WIN32) + signal(SIGPIPE, SIG_IGN); +#endif + + // setup logging + google::LogToStderr(); + google::InitGoogleLogging(argv[0]); + + optind = 0; + opterr = 0; + + struct option longOptions[] = { + {"help", required_argument, 0, 'p'}, + {"plugin-path", required_argument, 0, 'p'}, + {"loglevel", required_argument, 0, 'l'}, + {0, 0, 0, 0} + }; + + // parse global options + int optionInd = 0; + int option; + for (;;) { + option = getopt_long_only(argc, argv, "p:l:h", longOptions, &optionInd); + if (option == -1) { + break; + } + switch(option) { + // cases without short option + case 0: { + break; + } + // cases with short-hand options + case 'p': + pluginPath = optarg; + break; + case 'l': + break; + case 'h': + case '?': { + printUsageAndExit(argv[0]); + break; + } + default: + break; + } + } + + if (optind < argc) { + inputFile = argv[optind]; + } + + // register plugins + if (pluginPath.length() > 0) { + Factory::setDefaultPluginPath(pluginPath); + } + + // start HTTP server + HTTPServer::getInstance(31444, 31445, NULL); + + Interpreter interpreter; + try { + if (inputFile.size() == 0 || inputFile == "-") { + LOG(INFO) << "Reading SCXML from STDIN"; + std::stringstream ss; + std::string line; + while (std::getline(std::cin, line)) { + ss << line; + } + URL tmp("anonymous.scxml"); + tmp.toAbsoluteCwd(); + interpreter = Interpreter::fromXML(ss.str(), tmp); + } else { + interpreter = Interpreter::fromURL(inputFile); + } + if (!interpreter) { + LOG(ERROR) << "Cannot create interpreter from " << inputFile; + exit(EXIT_FAILURE); + } + + // analyze here + Arabica::XPath::NodeSet states = interpreter.getNodeSetForXPath("//" + interpreter.getNameSpaceInfo().xpathPrefix + "state"); + Arabica::XPath::NodeSet final = interpreter.getNodeSetForXPath("//" + interpreter.getNameSpaceInfo().xpathPrefix + "final"); + Arabica::XPath::NodeSet parallels = interpreter.getNodeSetForXPath("//" + interpreter.getNameSpaceInfo().xpathPrefix + "parallel"); + Arabica::XPath::NodeSet shallowHistories = interpreter.getNodeSetForXPath("//" + interpreter.getNameSpaceInfo().xpathPrefix + "history[@type='shallow']"); + shallowHistories.push_back(interpreter.getNodeSetForXPath("//" + interpreter.getNameSpaceInfo().xpathPrefix + "history[not(@type)]")); + Arabica::XPath::NodeSet deepHistories = interpreter.getNodeSetForXPath("//" + interpreter.getNameSpaceInfo().xpathPrefix + "history[@type='deep']"); + Arabica::XPath::NodeSet transitions = interpreter.getNodeSetForXPath("//" + interpreter.getNameSpaceInfo().xpathPrefix + "transition"); + + std::cout << "# Number of elements" << std::endl; + std::cout << "nr_states: " << (states.size() + final.size() + parallels.size()) << std::endl; + std::cout << "nr_parallel: " << parallels.size() << std::endl; + std::cout << "nr_hist_flat: " << shallowHistories.size() << std::endl; + std::cout << "nr_hist_deep: " << deepHistories.size() << std::endl; + std::cout << "nr_trans: " << transitions.size() << std::endl; + + + std::cout << "# Transition Histogram: number of transitions, number of active configurations" << std::endl; + + size_t numberOfLegalConfs = 0; + size_t lastBin = 0; + std::map histogram = Complexity::getTransitionHistogramm(interpreter.getDocument().getDocumentElement()); + for (std::map::iterator binIter = histogram.begin(); binIter != histogram.end(); binIter++) { + while (binIter->first > lastBin) { + std::cout << "th: " << toStr(lastBin++) << ", 0" << std::endl; + } + std::cout << "th: " << toStr(binIter->first) << ", " << binIter->second << std::endl; + numberOfLegalConfs += binIter->second; + lastBin = binIter->first + 1; + } + + std::stringstream transPowerSetSS; + std::string transPowerSetSeperator = ""; + for (std::map::reverse_iterator binIter = histogram.rbegin(); binIter != histogram.rend(); binIter++) { + transPowerSetSS << transPowerSetSeperator << binIter->second << " * " << "2**" << binIter->first; + transPowerSetSeperator = " + "; + } + std::cout << "# Sum of Powersets:" << std::endl; + std::cout << "ps_sum: " << transPowerSetSS.str() << std::endl; + std::cout << "# Upper bounds:" << std::endl; + std::cout << "# \tActive configurations: " << std::endl; + std::cout << "up_ac: " << numberOfLegalConfs << std::endl; + std::cout << "# \tGlobal configurations: " << std::endl; + std::cout << "up_gc: " << Complexity::stateMachineComplexity(interpreter.getDocument().getDocumentElement()) << std::endl; + + std::cout << "# \tGlobal configurations (no history): " << std::endl; + std::cout << "up_gcnh: " << Complexity::stateMachineComplexity(interpreter.getDocument().getDocumentElement(), uscxml::Complexity::IGNORE_HISTORY) << std::endl; + + std::cout << "# \tGlobal configurations (no nested data): " << std::endl; + std::cout << "up_gcnd: " << Complexity::stateMachineComplexity(interpreter.getDocument().getDocumentElement(), uscxml::Complexity::IGNORE_NESTED_DATA) << std::endl; + + std::cout << "# \tGlobal configurations (no nested data, no history): " << std::endl; + std::cout << "up_gcnhd: " << Complexity::stateMachineComplexity(interpreter.getDocument().getDocumentElement(), uscxml::Complexity::IGNORE_HISTORY_AND_NESTED_DATA) << std::endl; + + } catch (Event e) { + std::cout << e << std::endl; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/apps/uscxml-browser.cpp b/apps/uscxml-browser.cpp index 87b1b1d..0562ba7 100644 --- a/apps/uscxml-browser.cpp +++ b/apps/uscxml-browser.cpp @@ -22,40 +22,6 @@ #include #endif -class VerboseMonitor : public uscxml::InterpreterMonitor { - void onStableConfiguration(uscxml::Interpreter interpreter) { - printConfig(interpreter.getConfiguration()); - } - - void beforeProcessingEvent(uscxml::Interpreter interpreter, const uscxml::Event& event) { - switch (event.eventType) { - case uscxml::Event::INTERNAL: - std::cout << "Internal Event: " << event.name << std::endl; - break; - case uscxml::Event::EXTERNAL: - std::cout << "External Event: " << event.name << std::endl; - break; - case uscxml::Event::PLATFORM: - std::cout << "Platform Event: " << event.name << std::endl; - break; - } - } - - void beforeCompletion(uscxml::Interpreter interpreter) { - printConfig(interpreter.getConfiguration()); - } - - void printConfig(const Arabica::XPath::NodeSet& config) { - std::string seperator; - std::cout << "Config: {"; - for (int i = 0; i < config.size(); i++) { - std::cout << seperator << ATTR_CAST(config[i], "id"); - seperator = ", "; - } - std::cout << "}" << std::endl; - } -}; - #ifdef CMAKE_BUILD_TYPE_DEBUG #ifdef HAS_EXECINFO_H @@ -206,7 +172,7 @@ int main(int argc, char** argv) { interpreter.setCapabilities(options.getCapabilities()); if (options.verbose) { - VerboseMonitor* vm = new VerboseMonitor(); + StateTransitionMonitor* vm = new StateTransitionMonitor(); interpreter.addMonitor(vm); } diff --git a/apps/uscxml-transform.cpp b/apps/uscxml-transform.cpp index d8a9013..055c1ae 100644 --- a/apps/uscxml-transform.cpp +++ b/apps/uscxml-transform.cpp @@ -1,6 +1,7 @@ #include "uscxml/config.h" #include "uscxml/Interpreter.h" #include "uscxml/transform/ChartToFlatSCXML.h" +#include "uscxml/transform/ChartToTex.h" #include "uscxml/transform/ChartToMinimalSCXML.h" #include "uscxml/transform/ChartToPromela.h" #include "uscxml/DOMUtils.h" @@ -24,6 +25,9 @@ #include #endif +#define ANNOTATE(envKey, annotationParam) \ +envVarIsTrue(envKey) || std::find(annotations.begin(), annotations.end(), annotationParam) != annotations.end() + class VerboseMonitor : public uscxml::InterpreterMonitor { void onStableConfiguration(uscxml::Interpreter interpreter) { printConfig(interpreter.getConfiguration()); @@ -58,20 +62,29 @@ void printUsageAndExit(const char* progName) { 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(" [-t pml|flat] [-v] [-lN]"); + printf(" [-t pml|flat|min|tex] [-a {OPTIONS}] [-v] [-lN]"); #ifdef BUILD_AS_PLUGINS printf(" [-p pluginPath]"); #endif printf(" [-i URL] [-o FILE]"); printf("\n"); printf("Options\n"); - printf("\t-t flat : flatten to SCXML state-machine\n"); - printf("\t-t pml : convert to spin/promela program\n"); - printf("\t-t min : minimize SCXML state-chart\n"); - printf("\t-v : be verbose\n"); - printf("\t-lN : Set loglevel to N\n"); - printf("\t-i URL : Input file (defaults to STDIN)\n"); - printf("\t-o FILE : Output file (defaults to STDOUT)\n"); + printf("\t-t flat : flatten to SCXML state-machine\n"); + printf("\t-t pml : convert to spin/promela program\n"); + printf("\t-t min : minimize SCXML state-chart\n"); + printf("\t-t tex : write global state transition table as tex file\n"); + printf("\t-a {OPTIONS} : annotate SCXML elements with comma seperated options\n"); + printf("\t 'priority' - transitions with their priority for transition selection\n"); + printf("\t 'step' - global states with their step identifier (-tflat only)\n"); + printf("\t 'members' - global transitions with their member transitions per index (-tflat only)\n"); + printf("\t 'sends' - transititve number of sends to external queue for global transitions (-tflat only)\n"); + printf("\t 'raises' - transititve number of raises to internal queue for global transitions (-tflat only)\n"); + printf("\t 'verbose' - comments detailling state changes and transitions for content selection (-tflat only)\n"); + printf("\t 'progress' - insert comments documenting progress in dociment (-tmin only)\n"); + printf("\t-v : be verbose\n"); + printf("\t-lN : Set loglevel to N\n"); + printf("\t-i URL : Input file (defaults to STDIN)\n"); + printf("\t-o FILE : Output file (defaults to STDOUT)\n"); printf("\n"); exit(1); } @@ -84,7 +97,8 @@ int main(int argc, char** argv) { std::string pluginPath; std::string inputFile; std::string outputFile; - + std::list annotations; + #if defined(HAS_SIGNAL_H) && !defined(WIN32) signal(SIGPIPE, SIG_IGN); #endif @@ -99,9 +113,10 @@ int main(int argc, char** argv) { struct option longOptions[] = { {"verbose", no_argument, 0, 'v'}, {"type", required_argument, 0, 't'}, + {"annotate", required_argument, 0, 'a'}, {"plugin-path", required_argument, 0, 'p'}, {"input-file", required_argument, 0, 'i'}, - {"output-file", required_argument, 0, 'o'}, + {"output-file", required_argument, 0, 'o'}, {"loglevel", required_argument, 0, 'l'}, {0, 0, 0, 0} }; @@ -110,7 +125,7 @@ int main(int argc, char** argv) { int optionInd = 0; int option; for (;;) { - option = getopt_long_only(argc, argv, "+vp:t:i:o:l:", longOptions, &optionInd); + option = getopt_long_only(argc, argv, "+vp:t:i:o:l:a:", longOptions, &optionInd); if (option == -1) { break; } @@ -132,6 +147,9 @@ int main(int argc, char** argv) { case 'i': inputFile = optarg; break; + case 'a': + annotations = InterpreterImpl::tokenize(optarg, ','); + break; case 'o': outputFile = optarg; break; @@ -145,18 +163,45 @@ int main(int argc, char** argv) { } } - if (outType.length() == 0 && outputFile.length() > 0) { - // try to get type from outfile extension - size_t dotPos = outputFile.find_last_of("."); - if (dotPos != std::string::npos) { - outType= outputFile.substr(dotPos + 1); - } - } + // make sure given annotation options are available in the environment + if(ANNOTATE("USCXML_ANNOTATE_GLOBAL_STATE_STEP", "step")) + setenv("USCXML_ANNOTATE_GLOBAL_STATE_STEP", "YES", 1); + + if (ANNOTATE("USCXML_ANNOTATE_GLOBAL_TRANS_PRIO", "priority")) + setenv("USCXML_ANNOTATE_GLOBAL_TRANS_PRIO", "YES", 1); + + if (ANNOTATE("USCXML_ANNOTATE_VERBOSE_COMMENTS", "verbose")) + setenv("USCXML_ANNOTATE_VERBOSE_COMMENTS", "YES", 1); + + if(ANNOTATE("USCXML_ANNOTATE_GLOBAL_TRANS_MEMBERS", "members")) + setenv("USCXML_ANNOTATE_GLOBAL_TRANS_MEMBERS", "YES", 1); + + if(ANNOTATE("USCXML_ANNOTATE_GLOBAL_TRANS_SENDS", "sends")) + setenv("USCXML_ANNOTATE_GLOBAL_TRANS_SENDS", "YES", 1); + + if(ANNOTATE("USCXML_ANNOTATE_GLOBAL_TRANS_RAISES", "raises")) + setenv("USCXML_ANNOTATE_GLOBAL_TRANS_RAISES", "YES", 1); + + if(ANNOTATE("USCXML_ANNOTATE_PROGRESS", "progress")) + setenv("USCXML_ANNOTATE_PROGRESS", "YES", 1); + +// if (outType.length() == 0 && outputFile.length() > 0) { +// // try to get type from outfile extension +// size_t dotPos = outputFile.find_last_of("."); +// if (dotPos != std::string::npos) { +// outType= outputFile.substr(dotPos + 1); +// } +// } - if (outType.length() == 0) - printUsageAndExit(argv[0]); +// if (outType.length() == 0) +// printUsageAndExit(argv[0]); - if (outType != "flat" && outType != "scxml" && outType != "pml" && outType != "min") + if (outType != "flat" && + outType != "scxml" && + outType != "pml" && + outType != "min" && + outType != "tex" && + std::find(annotations.begin(), annotations.end(), "priority") == annotations.end()) printUsageAndExit(argv[0]); // register plugins @@ -200,7 +245,19 @@ int main(int argc, char** argv) { exit(EXIT_SUCCESS); } - if (outType == "scxml" || outType == "flat") { + if (outType == "tex") { + if (outputFile.size() == 0 || outputFile == "-") { + ChartToTex::transform(interpreter).writeTo(std::cout); + } else { + std::ofstream outStream; + outStream.open(outputFile.c_str()); + ChartToTex::transform(interpreter).writeTo(outStream); + outStream.close(); + } + exit(EXIT_SUCCESS); + } + + if (outType == "flat") { if (outputFile.size() == 0 || outputFile == "-") { ChartToFlatSCXML::transform(interpreter).writeTo(std::cout); } else { @@ -224,6 +281,24 @@ int main(int argc, char** argv) { exit(EXIT_SUCCESS); } +#if 1 + if (annotations.size() > 0) { + ChartToFSM annotater(interpreter); + if (std::find(annotations.begin(), annotations.end(), "priority") != annotations.end()) + annotater.indexTransitions(); + + if (outputFile.size() == 0 || outputFile == "-") { + std::cout << annotater.getDocument(); + } else { + std::ofstream outStream; + outStream.open(outputFile.c_str()); + outStream << annotater.getDocument(); + outStream.close(); + } + exit(EXIT_SUCCESS); + } +#endif + } catch (Event e) { std::cout << e << std::endl; } diff --git a/contrib/local/annotate-xml-lineno.pl b/contrib/local/annotate-xml-lineno.pl new file mode 100755 index 0000000..f7e1a79 --- /dev/null +++ b/contrib/local/annotate-xml-lineno.pl @@ -0,0 +1,43 @@ +#!/usr/bin/perl -w + +use strict; +use File::Spec; +use File::Basename; +use XML::LibXML; +use Data::Dumper; + +my $xmlIn = shift or die("Expected *.xml file as input"); + +# absolutize and split into components +$xmlIn = File::Spec->rel2abs($xmlIn) or die($!); +my($filename, $dirs, $suffix) = fileparse($xmlIn) or die($!); + +my $parser = XML::LibXML->new({'line_numbers' => 1 }); +# my $xml = $parser->parse_file($xmlIn); +my $doc = $parser->load_xml('location' => $xmlIn, 'line_numbers' => 1) ; + +my $lineOffset = 0; + +sub lineNoNodes { + my $node = shift; + + if ($node->nodeType == XML_ELEMENT_NODE) { + $node->setAttribute("line_start", $node->line_number() + $lineOffset); + } + + my $prevElem; + for my $child ($node->childNodes()) { + lineNoNodes($child); + if ($prevElem) { + $prevElem->setAttribute("line_end", $child->line_number() - 1 + $lineOffset); + undef($prevElem); + } + if ($child->nodeType == XML_ELEMENT_NODE) { + $prevElem = $child; + } + } +} + +&lineNoNodes($doc->getDocumentElement()); + +print $doc->toString(); \ No newline at end of file diff --git a/contrib/local/create-random-scxml.pl b/contrib/local/create-random-scxml.pl new file mode 100755 index 0000000..7c6a3b8 --- /dev/null +++ b/contrib/local/create-random-scxml.pl @@ -0,0 +1,214 @@ +#!/usr/bin/perl -w + +use strict; +use List::Util qw[min max sum]; + +use Getopt::Long qw(GetOptions); +use Data::Dumper; + +my %options = (); + +GetOptions( + \%options, + "depth-max=i", + "child-max=i", + "events-max=i", + "states-max=i", + "trans-max=i", + "random-seed=i" +); + +my $seed = $options{'random-seed'} || int(rand(2**31)); +my $maxDepth = $options{'depth-max'} || 6; +my $maxChilds = $options{'child-max'} || 6; +my $maxStates = $options{'states-max'} || 60; +my $maxTrans = $options{'trans-max'} || 6; +my $maxEvents = $options{'trans-max'} || int($maxStates / 3) + 1; + +srand($seed); + +my $machine; +my $stateId = 0; + +my $probs = { + 'state' => { + 'type' => { + 'history' => 1, + 'parallel' => 2, + 'state' => 6, + 'final' => 1 + } + }, + 'transition' => { + 'target' => 0.8, + 'event' => 0.7, + 'cond' => 0.9, + 'execContent' => 0.7, + }, + 'history' => { + 'deep' => 0.2 + } +}; + +my $sumChildProbs = sum( values(%{$probs->{'state'}->{'type'}})); + +sub putMachine { + my $where = shift; + + $$where->{'name'} = 'test'; + $$where->{'type'} = 'scxml'; + $$where->{'datamodel'} = 'ecmascript'; + + putState(\$$where->{'children'}, 0); + putTransition(\$$where); +} + +sub putTransition { + my $where = shift; + + return if $$where->{'type'} eq 'final'; + + my $nrTrans = int(rand($maxTrans + 1)); + $nrTrans = min($nrTrans, 1) if $$where->{'type'} eq 'history'; + + for (my $i = 0; $i < $nrTrans; $i++) { + + my $trans; + if (rand(1) < $probs->{'transition'}->{'target'}) { + # has a target - pick one at random + $trans->{'target'} = 'id' . int(rand($stateId)); + } + + if (rand(1) < $probs->{'transition'}->{'event'}) { + # has an event + $trans->{'event'} = 'e' . int(rand($maxEvents + 1)); + } + + if (rand(1) < $probs->{'transition'}->{'cond'}) { + # has a condition + if (int(rand(2)) > 0) { + $trans->{'cond'} = 'true'; + } else { + $trans->{'cond'} = 'false'; + } + } + + if (rand(1) < $probs->{'transition'}->{'execContent'}) { + # has a executable content + push @{$trans->{'execContent'}}, ''; + } + + push @{$$where->{'transitions'}}, $trans; + } + + # continue with childs + foreach (@{$$where->{'children'}}) { + putTransition(\$_); + } +} + +sub putState { + my $where = shift; + my $depth = shift; + my $minStates = shift || 0; + my $r; + + return if ($stateId > $maxStates); + return if ($depth > $maxDepth); + my $nrChilds = int(rand($maxChilds + 1)); + $nrChilds = max($minStates, $nrChilds); + + for (my $i = 0; $i < $nrChilds; $i++) { + my $r = rand($sumChildProbs); + + my $state; + foreach my $type (keys %{$probs->{'state'}->{'type'}}) { + my $prob = $probs->{'state'}->{'type'}->{$type}; + if ($r < $prob) { + $state->{'type'} = $type; + last; + } + $r -= $prob; + } + + $state->{'id'} = "id".$stateId++; + + if ($state->{'type'} eq 'parallel') { + putState(\$state->{'children'}, $depth + 1, 2); + } elsif ($state->{'type'} eq 'state') { + putState(\$state->{'children'}, $depth + 1); + } elsif ($state->{'type'} eq 'history') { + if (rand(1) < $probs->{'history'}->{'deep'}) { + $state->{'deep'} = 1; + } + } + + push @{$$where}, $state; + } +}; + +sub writeState { + my $state = shift; + + print STDOUT '<'.$state->{'type'}; + print STDOUT ' id="'.$state->{'id'} . '"'; + print STDOUT ' type="deep"' if exists $state->{'deep'}; + print STDOUT '>'; + + foreach (@{$state->{'children'}}) { + writeState($_); + } + + foreach (@{$state->{'transitions'}}) { + writeTransition($_); + } + + print STDOUT '{'type'} . '>'; + +}; + +sub writeTransition { + my $trans = shift; + + print STDOUT '{'target'}; + print STDOUT ' event="' . $trans->{'event'} . '"' if $trans->{'event'}; + print STDOUT ' cond="' . $trans->{'cond'} . '"' if $trans->{'cond'}; + + if ($trans->{'execContent'}) { + print STDOUT '>'; + foreach (@{$trans->{'execContent'}}) { + print STDOUT $_; + } + print STDOUT ''; + } else { + print STDOUT '/>'; + } + +}; + +sub writeMachine { + my $machine = shift; + print STDOUT '{'datamodel'}; + print STDOUT ' seed="' . $seed . '"'; + print STDOUT ' name="' . $machine->{'name'} . '"' if $machine->{'name'}; + print STDOUT '>'; + + foreach (@{$machine->{'children'}}) { + writeState($_); + } + + print STDOUT ''; +} + +putMachine(\$machine); +# print Dumper($machine); + +writeMachine($machine); + + +#print Dumper($machine); + + +# writeState($machine); diff --git a/src/bindings/swig/csharp/uscxml.i b/src/bindings/swig/csharp/uscxml.i index e218bf4..18b8cb8 100644 --- a/src/bindings/swig/csharp/uscxml.i +++ b/src/bindings/swig/csharp/uscxml.i @@ -36,12 +36,18 @@ typedef uscxml::InterpreterIssue InterpreterIssue; %feature("director") uscxml::WrappedExecutableContent; %feature("director") uscxml::WrappedInterpreterMonitor; -// disable warning related to unknown base class +// disable warnings + +// unknown base class #pragma SWIG nowarn=401 -// do not warn when we override symbols via extend +// override symbols via extend #pragma SWIG nowarn=302 -// do not warn when ignoring overrided method +// ignoring overrided method #pragma SWIG nowarn=516 +// pointer from director +#pragma SWIG nowarn=473 +// renamed params -> _params +#pragma SWIG nowarn=314 %csconst(1); @@ -160,6 +166,9 @@ WRAP_TO_STRING(uscxml::InterpreterIssue); %include "../uscxml_ignores.i" +// InterpreterMonitor -> StateTransitionMonitor +%ignore uscxml::StateTransitionMonitor; + //*********************************************** // Beautify important classes //*********************************************** diff --git a/src/uscxml/Common.h b/src/uscxml/Common.h index c261301..85729f2 100644 --- a/src/uscxml/Common.h +++ b/src/uscxml/Common.h @@ -20,6 +20,17 @@ #ifndef COMMON_H_YZ3CIYP #define COMMON_H_YZ3CIYP +#if __cplusplus >= 201402L +#define DEPRECATED [[deprecated]] +#elif defined(__GNUC__) +#define DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define DEPRECATED __declspec(deprecated) +#else +#pragma message("WARNING: You need to implement DEPRECATED for this compiler") +#define DEPRECATED(alternative) +#endif + #if defined(_WIN32) && !defined(USCXML_STATIC) # ifdef USCXML_EXPORT # define USCXML_API __declspec(dllexport) @@ -41,6 +52,19 @@ #include #endif +#if defined(_WIN32) +inline int setenv(const char *name, const char *value, int overwrite) +{ + int errcode = 0; + if(!overwrite) { + size_t envsize = 0; + errcode = getenv_s(&envsize, NULL, 0, name); + if(errcode || envsize) return errcode; + } + return _putenv_s(name, value); +} +#endif + #define _USE_MATH_DEFINES #include diff --git a/src/uscxml/Convenience.cpp b/src/uscxml/Convenience.cpp new file mode 100644 index 0000000..6013575 --- /dev/null +++ b/src/uscxml/Convenience.cpp @@ -0,0 +1,128 @@ +/** + * @file + * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include +#include +#include +#include + +namespace uscxml { +bool isnan(double x) { + return x != x; +} + +bool isNumeric(const char* pszInput, int nNumberBase) { + std::string base = ".-0123456789ABCDEF"; + std::string input = pszInput; + return (input.find_first_not_of(base.substr(0, nNumberBase + 2)) == std::string::npos); +} + +bool isInteger(const char* pszInput, int nNumberBase) { + std::string base = "-0123456789ABCDEF"; + std::string input = pszInput; + return (input.find_first_not_of(base.substr(0, nNumberBase + 1)) == std::string::npos); +} + +bool iequals(const std::string& a, const std::string& b) { + // this impementation beats boost::iequals 2700ms vs 2100ms for test-performance.scxml - we don't care for non-ascii yet + unsigned int size = a.size(); + if (b.size() != size) + return false; + for (unsigned int i = 0; i < size; ++i) + if (tolower(a[i]) != tolower(b[i])) + return false; + return true; +} + +bool equals(const std::string& a, const std::string& b) { + unsigned int size = a.size(); + if (b.size() != size) + return false; + for (unsigned int i = 0; i < size; ++i) + if (a[i] != b[i]) + return false; + return true; +} + +bool stringIsTrue(const std::string& value) { + return (iequals(value, "on") || + iequals(value, "true") || + iequals(value, "1") || + iequals(value, "yes")); +} + +bool envVarIsTrue(const char* name) { + const char* value = getenv(name); + if (value == NULL) + return false; + return stringIsTrue(value); +} + +bool envVarIEquals(const char* name, const char* value) { + const char* envVarValue = getenv(name); + if (envVarValue == NULL) + return false; + return iequals(envVarValue, value); +} + +std::string unescape(const std::string& a) { + std::stringstream b; + // see http://en.cppreference.com/w/cpp/language/escape + + std::string::const_iterator it = a.begin(); + while (it != a.end()) { + char c = *it++; + if (c == '\\' && it != a.end()) { + switch (*it++) { + case '\\': + c = '\\'; + break; + case '0': + c = '\0'; + break; + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + } + } + b << c; + } + + return b.str(); +} + +} diff --git a/src/uscxml/Convenience.h b/src/uscxml/Convenience.h index 86a0b52..cbc38d9 100644 --- a/src/uscxml/Convenience.h +++ b/src/uscxml/Convenience.h @@ -21,14 +21,13 @@ #define CONVENIENCE_H_LU7GZ6CB #include +#include #include #include namespace uscxml { -inline bool isnan(double x) { - return x != x; -} - +inline bool isnan(double x); + // see http://stackoverflow.com/questions/228005/alternative-to-itoa-for-converting-integer-to-string-c template std::string toStr(T tmp) { std::ostringstream out; @@ -44,82 +43,15 @@ template T strTo(std::string tmp) { return output; } -inline bool isNumeric( const char* pszInput, int nNumberBase) { - std::string base = ".-0123456789ABCDEF"; - std::string input = pszInput; - return (input.find_first_not_of(base.substr(0, nNumberBase + 2)) == std::string::npos); -} - -inline bool isInteger( const char* pszInput, int nNumberBase) { - std::string base = "-0123456789ABCDEF"; - std::string input = pszInput; - return (input.find_first_not_of(base.substr(0, nNumberBase + 1)) == std::string::npos); -} - -inline bool iequals(const std::string& a, const std::string& b) { - // this impementation beats boost::iequals 2700ms vs 2100ms for test-performance.scxml - we don't care for non-ascii yet - unsigned int size = a.size(); - if (b.size() != size) - return false; - for (unsigned int i = 0; i < size; ++i) - if (tolower(a[i]) != tolower(b[i])) - return false; - return true; -} +bool isNumeric(const char* pszInput, int nNumberBase); +bool isInteger( const char* pszInput, int nNumberBase); +bool iequals(const std::string& a, const std::string& b); +bool equals(const std::string& a, const std::string& b); +bool stringIsTrue(const std::string& value); +bool envVarIsTrue(const char* name); +bool envVarIEquals(const char* name, const char* value); -inline bool equals(const std::string& a, const std::string& b) { - unsigned int size = a.size(); - if (b.size() != size) - return false; - for (unsigned int i = 0; i < size; ++i) - if (a[i] != b[i]) - return false; - return true; -} - -inline std::string unescape(const std::string& a) { - std::stringstream b; - // see http://en.cppreference.com/w/cpp/language/escape - - std::string::const_iterator it = a.begin(); - while (it != a.end()) { - char c = *it++; - if (c == '\\' && it != a.end()) { - switch (*it++) { - case '\\': - c = '\\'; - break; - case '0': - c = '\0'; - break; - case 'a': - c = '\a'; - break; - case 'b': - c = '\b'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case 'v': - c = '\v'; - break; - } - } - b << c; - } - - return b.str(); -} +std::string unescape(const std::string& a); // see http://www.cplusplus.com/forum/general/27544/ diff --git a/src/uscxml/DOMUtils.cpp b/src/uscxml/DOMUtils.cpp index fa58759..2d3c3ea 100644 --- a/src/uscxml/DOMUtils.cpp +++ b/src/uscxml/DOMUtils.cpp @@ -26,12 +26,8 @@ namespace uscxml { - bool DOMUtils::attributeIsTrue(const::std::string& value) { - return (iequals(value, "on") || - iequals(value, "true") || - iequals(value, "1") || - iequals(value, "yes")); + return stringIsTrue(value.c_str()); } std::string DOMUtils::xPathForNode(const Arabica::DOM::Node& node, const std::string& ns) { diff --git a/src/uscxml/DOMUtils.h b/src/uscxml/DOMUtils.h index 7fd291c..2264e64 100644 --- a/src/uscxml/DOMUtils.h +++ b/src/uscxml/DOMUtils.h @@ -44,7 +44,8 @@ class USCXML_API DOMUtils { public: static std::string xPathForNode(const Arabica::DOM::Node& node, const std::string& ns = ""); static std::list > getElementsByType(const Arabica::DOM::Node& root, Arabica::DOM::Node_base::Type type); - static bool attributeIsTrue(const::std::string& value); + // deprecated, use stringIsTrue from Convenience.h instead + DEPRECATED static bool attributeIsTrue(const::std::string& value); }; class USCXML_API NumAttr { diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 6888d0e..060a397 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -330,29 +330,39 @@ void NameSpaceInfo::init(const std::map& namespaceInfo } void StateTransitionMonitor::beforeTakingTransition(uscxml::Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) { - std::cout << "Transition: " << uscxml::DOMUtils::xPathForNode(transition) << std::endl; + std::cerr << "Transition: " << uscxml::DOMUtils::xPathForNode(transition) << std::endl; } void StateTransitionMonitor::onStableConfiguration(uscxml::Interpreter interpreter) { - std::cout << "Config: {"; + std::cerr << "Config: {"; printNodeSet(interpreter.getConfiguration()); - std::cout << "}" << std::endl; + std::cerr << "}" << std::endl; } void StateTransitionMonitor::beforeProcessingEvent(uscxml::Interpreter interpreter, const uscxml::Event& event) { - std::cout << "Event: " << event.name << std::endl; + switch (event.eventType) { + case uscxml::Event::INTERNAL: + std::cerr << "Internal Event: " << event.name << std::endl; + break; + case uscxml::Event::EXTERNAL: + std::cerr << "External Event: " << event.name << std::endl; + break; + case uscxml::Event::PLATFORM: + std::cerr << "Platform Event: " << event.name << std::endl; + break; + } } void StateTransitionMonitor::beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Element& element) { - std::cout << "Executable Content: " << DOMUtils::xPathForNode(element) << std::endl; + std::cerr << "Executable Content: " << DOMUtils::xPathForNode(element) << std::endl; } void StateTransitionMonitor::beforeExitingState(uscxml::Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { exitingStates.push_back(state); if (!moreComing) { - std::cout << "Exiting: {"; + std::cerr << "Exiting: {"; printNodeSet(exitingStates); - std::cout << "}" << std::endl; + std::cerr << "}" << std::endl; exitingStates = Arabica::XPath::NodeSet(); } } @@ -360,9 +370,9 @@ void StateTransitionMonitor::beforeExitingState(uscxml::Interpreter interpreter, void StateTransitionMonitor::beforeEnteringState(uscxml::Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { enteringStates.push_back(state); if (!moreComing) { - std::cout << "Entering: {"; + std::cerr << "Entering: {"; printNodeSet(enteringStates); - std::cout << "}" << std::endl; + std::cerr << "}" << std::endl; enteringStates = Arabica::XPath::NodeSet(); } @@ -371,7 +381,7 @@ void StateTransitionMonitor::beforeEnteringState(uscxml::Interpreter interpreter void StateTransitionMonitor::printNodeSet(const Arabica::XPath::NodeSet& config) { std::string seperator; for (int i = 0; i < config.size(); i++) { - std::cout << seperator << ATTR_CAST(config[i], "id"); + std::cerr << seperator << ATTR_CAST(config[i], "id"); seperator = ", "; } } @@ -577,7 +587,7 @@ InterpreterImpl::~InterpreterImpl() { tthread::lock_guard lock(_mutex); stop(); // unset started bit } -// std::cout << "stopped " << this << std::endl; +// std::cerr << "stopped " << this << std::endl; // tthread::lock_guard lock(_mutex); if (_thread) { if (_thread->get_id() != tthread::this_thread::get_id()) { @@ -774,11 +784,11 @@ InterpreterState InterpreterImpl::step(int waitForMS) { NodeSet initialTransitions = getDocumentInitialTransitions(); assert(initialTransitions.size() > 0); #if VERBOSE - std::cout << _name << ": initialTransitions: " << std::endl; + std::cerr << _name << ": initialTransitions: " << std::endl; for (int i = 0; i < initialTransitions.size(); i++) { - std::cout << initialTransitions[i] << std::endl; + std::cerr << initialTransitions[i] << std::endl; } - std::cout << std::endl; + std::cerr << std::endl; #endif enterStates(initialTransitions); @@ -856,7 +866,7 @@ InterpreterState InterpreterImpl::step(int waitForMS) { NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); for (unsigned int j = 0; j < invokes.size(); j++) { Element invokeElem = Element(invokes[j]); - if (!HAS_ATTR(invokeElem, "persist") || !DOMUtils::attributeIsTrue(ATTR(invokeElem, "persist"))) { + if (!HAS_ATTR(invokeElem, "persist") || !stringIsTrue(ATTR(invokeElem, "persist"))) { invoke(invokeElem); } } @@ -1007,7 +1017,7 @@ void InterpreterImpl::stabilize() { _currEvent = _internalQueue.front(); _internalQueue.pop_front(); #if VERBOSE - std::cout << "Received internal event " << _currEvent.name << std::endl; + std::cerr << "Received internal event " << _currEvent.name << std::endl; #endif USCXML_MONITOR_CALLBACK2(beforeProcessingEvent, _currEvent) @@ -1032,7 +1042,7 @@ void InterpreterImpl::stabilize() { NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); for (unsigned int j = 0; j < invokes.size(); j++) { Element invokeElem = Element(invokes[j]); - if (!HAS_ATTR(invokeElem, "persist") || !DOMUtils::attributeIsTrue(ATTR(invokeElem, "persist"))) { + if (!HAS_ATTR(invokeElem, "persist") || !stringIsTrue(ATTR(invokeElem, "persist"))) { invoke(invokeElem); } } @@ -1107,11 +1117,11 @@ LOOP: } #if VERBOSE - std::cout << "Enabled eventless transitions: " << std::endl; + std::cerr << "Enabled eventless transitions: " << std::endl; for (int i = 0; i < enabledTransitions.size(); i++) { - std::cout << enabledTransitions[i] << std::endl << "----" << std::endl; + std::cerr << enabledTransitions[i] << std::endl << "----" << std::endl; } - std::cout << std::endl; + std::cerr << std::endl; #endif enabledTransitions = removeConflictingTransitions(enabledTransitions); @@ -1522,7 +1532,7 @@ void InterpreterImpl::init() { // _running = true; #if VERBOSE - std::cout << "running " << this << std::endl; + std::cerr << "running " << this << std::endl; #endif if (_binding == EARLY) { @@ -1592,7 +1602,7 @@ void InterpreterImpl::initializeData(const Element& data) { void InterpreterImpl::receiveInternal(const Event& event) { #if VERBOSE - std::cout << _name << " receiveInternal: " << event.name << std::endl; + std::cerr << _name << " receiveInternal: " << event.name << std::endl; #endif _internalQueue.push_back(event); // _condVar.notify_all(); @@ -1600,7 +1610,7 @@ void InterpreterImpl::receiveInternal(const Event& event) { void InterpreterImpl::receive(const Event& event, bool toFront) { #if VERBOSE - std::cout << _name << " receive: " << event.name << std::endl; + std::cerr << _name << " receive: " << event.name << std::endl; #endif if (toFront) { _externalQueue.push_front(event); @@ -1715,7 +1725,7 @@ void InterpreterImpl::processDOMorText(const Arabica::DOM::Element& Node child = element.getFirstChild(); while (child) { -// std::cout << child.getNodeType() << std::endl; +// std::cerr << child.getNodeType() << std::endl; if (child.getNodeType() == Node_base::TEXT_NODE || child.getNodeType() == Node_base::CDATA_SECTION_NODE) { std::string trimmed = child.getNodeValue(); @@ -2035,7 +2045,7 @@ void InterpreterImpl::invoke(const Arabica::DOM::Element& element) if (HAS_ATTR(element, "id")) { invokeReq.invokeid = ATTR(element, "id"); } else { - if (HAS_ATTR(_scxml, "flat") && DOMUtils::attributeIsTrue(ATTR(_scxml, "flat")) && HAS_ATTR(element, "parent")) { + if (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat")) && HAS_ATTR(element, "parent")) { invokeReq.invokeid = ATTR(element, "parent") + "." + UUID::getUUID(); } else { invokeReq.invokeid = ATTR(getParentState(element), "id") + "." + UUID::getUUID(); @@ -2381,7 +2391,7 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Element& c Arabica::DOM::Element ifElem = (Arabica::DOM::Element)content; #if VERBOSE if (HAS_ATTR(ifElem, "cond")) - std::cout << ATTR(ifElem, "cond") << std::endl; + std::cerr << ATTR(ifElem, "cond") << std::endl; #endif /** * A block is everything up to or between elseifs and else. Those element @@ -2446,15 +2456,15 @@ void InterpreterImpl::executeContent(const Arabica::DOM::Element& c // --- LOG -------------------------- Arabica::DOM::Element logElem = (Arabica::DOM::Element)content; if (logElem.hasAttribute("label")) - std::cout << logElem.getAttribute("label") << ": "; + std::cerr << logElem.getAttribute("label") << ": "; if (logElem.hasAttribute("expr")) { try { - std::cout << _dataModel.evalAsString(logElem.getAttribute("expr")) << std::endl; + std::cerr << _dataModel.evalAsString(logElem.getAttribute("expr")) << std::endl; } CATCH_AND_DISTRIBUTE2("Syntax error in expr attribute of log element", content) } else { if (logElem.hasAttribute("label")) - std::cout << std::endl; + std::cerr << std::endl; } } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "assign")) { // --- ASSIGN -------------------------- @@ -2586,7 +2596,7 @@ void InterpreterImpl::finalizeAndAutoForwardCurrentEvent() { executeContent(finalizeElem); } } - if (HAS_ATTR(invokeIter->second.getElement(), "autoforward") && DOMUtils::attributeIsTrue(ATTR(invokeIter->second.getElement(), "autoforward"))) { + if (HAS_ATTR(invokeIter->second.getElement(), "autoforward") && stringIsTrue(ATTR(invokeIter->second.getElement(), "autoforward"))) { try { // do not autoforward to invokers that send to #_parent from the SCXML IO Processor! // Yes do so, see test229! @@ -2602,7 +2612,7 @@ void InterpreterImpl::finalizeAndAutoForwardCurrentEvent() { } void InterpreterImpl::returnDoneEvent(const Arabica::DOM::Node& state) { -// std::cout << state << std::endl; +// std::cerr << state << std::endl; if (_parentQueue != NULL) { Event done; done.name = "done.invoke." + _sessionId; @@ -2695,11 +2705,11 @@ Arabica::DOM::Node InterpreterImpl::getAncestorElement(const Arabic Arabica::DOM::Node InterpreterImpl::findLCCA(const Arabica::XPath::NodeSet& states) { #if VERBOSE_FIND_LCCA - std::cout << "findLCCA: "; + std::cerr << "findLCCA: "; for (int i = 0; i < states.size(); i++) { - std::cout << ATTR_CAST(states[i], "id") << ", "; + std::cerr << ATTR_CAST(states[i], "id") << ", "; } - std::cout << std::endl << std::flush; + std::cerr << std::endl << std::flush; #endif Arabica::XPath::NodeSet ancestors = getProperAncestors(states[0], Arabica::DOM::Node()); @@ -2711,7 +2721,7 @@ Arabica::DOM::Node InterpreterImpl::findLCCA(const Arabica::XPath:: for (int j = 0; j < states.size(); j++) { #if VERBOSE_FIND_LCCA - std::cout << "Checking " << ATTR_CAST(states[j], "id") << " and " << ATTR_CAST(ancestors[i], "id") << std::endl; + std::cerr << "Checking " << ATTR_CAST(states[j], "id") << " and " << ATTR_CAST(ancestors[i], "id") << std::endl; #endif if (!isDescendant(states[j], ancestors[i])) @@ -2728,7 +2738,7 @@ NEXT_ANCESTOR: ancestor = _scxml; assert(ancestor); #if VERBOSE_FIND_LCCA - std::cout << " -> " << ATTR_CAST(ancestor, "id") << " " << ancestor.getLocalName() << std::endl; + std::cerr << " -> " << ATTR_CAST(ancestor, "id") << " " << ancestor.getLocalName() << std::endl; #endif return ancestor; } @@ -2814,7 +2824,7 @@ Arabica::XPath::NodeSet InterpreterImpl::getInitialStates(Arabica:: } #if VERBOSE - std::cout << "Getting initial state of " << TAGNAME(state) << " " << ATTR(state, "id") << std::endl; + std::cerr << "Getting initial state of " << TAGNAME(state) << " " << ATTR(state, "id") << std::endl; #endif if (isAtomic(state)) { @@ -2940,7 +2950,7 @@ std::string InterpreterImpl::spaceNormalize(const std::string& text) { content << seperator << text.substr(start, i + 1 - start); } } -// std::cout << ">>" << content.str() << "<<" << std::endl; +// std::cerr << ">>" << content.str() << "<<" << std::endl; #else @@ -2979,7 +2989,7 @@ NodeSet InterpreterImpl::filterChildElements(const std::string& tag for (unsigned int i = 0; i < childs.getLength(); i++) { if (childs.item(i).getNodeType() != Node_base::ELEMENT_NODE) continue; -// std::cout << TAGNAME(childs.item(i)) << std::endl; +// std::cerr << TAGNAME(childs.item(i)) << std::endl; if(iequals(TAGNAME_CAST(childs.item(i)), tagName)) { filteredChildElems.push_back(childs.item(i)); } @@ -3098,9 +3108,6 @@ bool InterpreterImpl::isInEmbeddedDocument(const Node& node) { // a node is in an embedded document if there is a content element in its parents Node parent = node; while(parent) { - if(parent == _scxml) { - return false; - } if(iequals(parent.getLocalName(), "content")) { return true; } @@ -3357,10 +3364,10 @@ bool InterpreterImpl::isLegalConfiguration(const NodeSet& config) { Element configElem(config[i]); if (!isAtomic(configElem) && !isParallel(configElem)) { Node foundChildState; - //std::cout << config[i] << std::endl; + //std::cerr << config[i] << std::endl; NodeSet childs = getChildStates(config[i]); for (int j = 0; j < childs.size(); j++) { - //std::cout << childs[j] << std::endl; + //std::cerr << childs[j] << std::endl; if (isMember(childs[j], config)) { if (foundChildState) { LOG(ERROR) << "Invalid configuration: Multiple childs of compound '" << ATTR_CAST(config[i], "id") @@ -3395,7 +3402,7 @@ bool InterpreterImpl::isLegalConfiguration(const NodeSet& config) { } bool InterpreterImpl::isInState(const std::string& stateId) { - if (HAS_ATTR(_scxml, "flat") && DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) { + if (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat"))) { // extension for flattened SCXML documents if (_configuration.size() > 0 && HAS_ATTR_CAST(_configuration[0], "id")) { // all states are encoded in the current statename diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h index c509684..7019eb9 100644 --- a/src/uscxml/Interpreter.h +++ b/src/uscxml/Interpreter.h @@ -423,7 +423,7 @@ public: static bool isParallel(const Arabica::DOM::Element& state); static bool isCompound(const Arabica::DOM::Element& state); static bool isDescendant(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2); - bool isInEmbeddedDocument(const Arabica::DOM::Node& node); + static bool isInEmbeddedDocument(const Arabica::DOM::Node& node); bool isInitial(const Arabica::DOM::Element& state); static std::string stateToString(InterpreterState state); diff --git a/src/uscxml/debug/InterpreterIssue.cpp b/src/uscxml/debug/InterpreterIssue.cpp index 1bd416c..0bca74e 100644 --- a/src/uscxml/debug/InterpreterIssue.cpp +++ b/src/uscxml/debug/InterpreterIssue.cpp @@ -318,11 +318,43 @@ std::list InterpreterIssue::forInterpreter(InterpreterImpl* in std::string stateId = ATTR(state, "id"); - if (!InterpreterImpl::isMember(state, reachable)) { - issues.push_back(InterpreterIssue("State with id '" + stateId + "' is unreachable", state, InterpreterIssue::USCXML_ISSUE_FATAL)); + // check for valid transition with history states + if (LOCALNAME(state) == "history") { + NodeSet transitions = InterpreterImpl::filterChildElements(_nsInfo.xmlNSPrefix + "transition", state, false); + if (transitions.size() > 1) { + issues.push_back(InterpreterIssue("History pseudo-state with id '" + stateId + "' has multiple transitions", state, InterpreterIssue::USCXML_ISSUE_FATAL)); + } else if (transitions.size() == 1) { + Element transition = Element(transitions[0]); + if (HAS_ATTR(transition, "cond")) { + issues.push_back(InterpreterIssue("Transition in history pseudo-state '" + stateId + "' must not have a condition", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); + } + if (HAS_ATTR(transition, "event")) { + issues.push_back(InterpreterIssue("Transition in history pseudo-state '" + stateId + "' must not have an event attribute", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); + } + if (!HAS_ATTR(transition, "target")) { + issues.push_back(InterpreterIssue("Transition in history pseudo-state '" + stateId + "' has no target", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); + } else { + NodeSet targetStates = interpreter->getTargetStates(transition); + for (int j = 0; j < targetStates.size(); j++) { + Element target = Element(targetStates[j]); + if (HAS_ATTR(state, "type") && ATTR(state, "type") == "deep") { + if (!InterpreterImpl::isDescendant(target, state.getParentNode())) { + issues.push_back(InterpreterIssue("Transition in deep history pseudo-state '" + stateId + "' has invalid target state '" + ATTR(target, "id") + "'", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); + } + } else { + if (target.getParentNode() != state.getParentNode()) { + issues.push_back(InterpreterIssue("Transition in shallow history pseudo-state '" + stateId + "' has invalid target state '" + ATTR(target, "id") + "'", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); + } + } + } + } + } } - + // check whether state is reachable + if (!InterpreterImpl::isMember(state, reachable) && !InterpreterImpl::isInEmbeddedDocument(state)) { + issues.push_back(InterpreterIssue("State with id '" + stateId + "' is unreachable", state, InterpreterIssue::USCXML_ISSUE_FATAL)); + } // check for uniqueness of id attribute if (seenStates.find(stateId) != seenStates.end()) { @@ -336,15 +368,17 @@ std::list InterpreterIssue::forInterpreter(InterpreterImpl* in Element transition = Element(transitions[i]); // check for valid target - std::list targetIds = InterpreterImpl::tokenizeIdRefs(ATTR(transition, "target")); - if (targetIds.size() == 0) { - issues.push_back(InterpreterIssue("Transition has empty target state list", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); - } - - for (std::list::iterator targetIter = targetIds.begin(); targetIter != targetIds.end(); targetIter++) { - if (seenStates.find(*targetIter) == seenStates.end()) { - issues.push_back(InterpreterIssue("Transition has non-existant target state with id '" + *targetIter + "'", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); - continue; + if (HAS_ATTR(transition, "target")) { + std::list targetIds = InterpreterImpl::tokenizeIdRefs(ATTR(transition, "target")); + if (targetIds.size() == 0) { + issues.push_back(InterpreterIssue("Transition has empty target state list", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); + } + + for (std::list::iterator targetIter = targetIds.begin(); targetIter != targetIds.end(); targetIter++) { + if (seenStates.find(*targetIter) == seenStates.end()) { + issues.push_back(InterpreterIssue("Transition has non-existant target state with id '" + *targetIter + "'", transition, InterpreterIssue::USCXML_ISSUE_FATAL)); + continue; + } } } } @@ -413,6 +447,7 @@ NEXT_TRANSITION: } } + // check that all invokers exists { for (int i = 0; i < invokes.size(); i++) { diff --git a/src/uscxml/debug/SCXMLDotWriter.cpp b/src/uscxml/debug/SCXMLDotWriter.cpp index 4f0143f..82faaa4 100644 --- a/src/uscxml/debug/SCXMLDotWriter.cpp +++ b/src/uscxml/debug/SCXMLDotWriter.cpp @@ -45,7 +45,7 @@ SCXMLDotWriter::SCXMLDotWriter(Interpreter interpreter, NodeList scxmlElems = interpreter.getDocument().getElementsByTagName("scxml"); _scxml = (Element)scxmlElems.item(0); - _isFlat = HAS_ATTR(_scxml, "flat") && DOMUtils::attributeIsTrue(ATTR(_scxml, "flat")); + _isFlat = HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat")); if (_anchors.size() == 0) { StateAnchor anchor; diff --git a/src/uscxml/interpreter/InterpreterDraft6.cpp b/src/uscxml/interpreter/InterpreterDraft6.cpp index 2bab937..6bbb7d8 100644 --- a/src/uscxml/interpreter/InterpreterDraft6.cpp +++ b/src/uscxml/interpreter/InterpreterDraft6.cpp @@ -248,7 +248,7 @@ void InterpreterDraft6::exitStates(const Arabica::XPath::NodeSet& e NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", statesToExit[i]); for (int j = 0; j < invokes.size(); j++) { Element invokeElem = (Element)invokes[j]; - if (HAS_ATTR(invokeElem, "persist") && DOMUtils::attributeIsTrue(ATTR(invokeElem, "persist"))) { + if (HAS_ATTR(invokeElem, "persist") && stringIsTrue(ATTR(invokeElem, "persist"))) { // extension for flattened SCXML documents, we will need an explicit uninvoke element } else { cancelInvoke(invokeElem); @@ -387,7 +387,7 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet& NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", statesToEnter[k]); for (unsigned int j = 0; j < invokes.size(); j++) { Element invokeElem = Element(invokes[j]); - if (HAS_ATTR(invokeElem, "persist") && DOMUtils::attributeIsTrue(ATTR(invokeElem, "persist"))) { + if (HAS_ATTR(invokeElem, "persist") && stringIsTrue(ATTR(invokeElem, "persist"))) { invoke(invokeElem); } } diff --git a/src/uscxml/interpreter/InterpreterRC.cpp b/src/uscxml/interpreter/InterpreterRC.cpp index b58a236..0237618 100644 --- a/src/uscxml/interpreter/InterpreterRC.cpp +++ b/src/uscxml/interpreter/InterpreterRC.cpp @@ -151,7 +151,7 @@ void InterpreterRC::exitStates(const Arabica::XPath::NodeSet& enabl NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", statesToExit[i]); for (int j = 0; j < invokes.size(); j++) { Element invokeElem = (Element)invokes[j]; - if (HAS_ATTR(invokeElem, "persist") && DOMUtils::attributeIsTrue(ATTR(invokeElem, "persist"))) { + if (HAS_ATTR(invokeElem, "persist") && stringIsTrue(ATTR(invokeElem, "persist"))) { } else { cancelInvoke(invokeElem); } @@ -271,12 +271,12 @@ void InterpreterRC::enterStates(const Arabica::XPath::NodeSet& enab USCXML_MONITOR_CALLBACK3(afterEnteringState, s, i + 1 < statesToEnter.size()) - if (HAS_ATTR(_scxml, "flat") && DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) { + if (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat"))) { // extension for flattened interpreters NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", s); for (unsigned int j = 0; j < invokes.size(); j++) { Element invokeElem = Element(invokes[j]); - if (HAS_ATTR(invokeElem, "persist") && DOMUtils::attributeIsTrue(ATTR(invokeElem, "persist"))) { + if (HAS_ATTR(invokeElem, "persist") && stringIsTrue(ATTR(invokeElem, "persist"))) { invoke(invokeElem); } } diff --git a/src/uscxml/plugins/datamodel/promela/PromelaDataModel.cpp b/src/uscxml/plugins/datamodel/promela/PromelaDataModel.cpp index 679177a..2621c66 100644 --- a/src/uscxml/plugins/datamodel/promela/PromelaDataModel.cpp +++ b/src/uscxml/plugins/datamodel/promela/PromelaDataModel.cpp @@ -211,6 +211,16 @@ bool PromelaDataModel::isLocation(const std::string& expr) { return true; } +bool PromelaDataModel::isValidSyntax(const std::string& expr) { + try { + PromelaParser parser(expr); + } catch (Event e) { + std::cerr << e << std::endl; + return false; + } + return true; +} + uint32_t PromelaDataModel::getLength(const std::string& expr) { if (!isDeclared(expr)) { ERROR_EXECUTION_THROW("Variable '" + expr + "' was not declared"); @@ -593,7 +603,7 @@ void PromelaDataModel::setVariable(void* ast, const Data& value) { if (value.compound.size() > 0 || value.atom.size() > 0) ERROR_EXECUTION_THROW("Variable " + node->value + " is an array"); - if (_variables[node->value].compound["size"] < value.array.size()) + if (strTo(_variables[node->value].compound["size"].atom) < value.array.size()) ERROR_EXECUTION_THROW("Array assigned to " + node->value + " is too large"); } diff --git a/src/uscxml/plugins/datamodel/promela/PromelaDataModel.h b/src/uscxml/plugins/datamodel/promela/PromelaDataModel.h index 581c761..25fe536 100644 --- a/src/uscxml/plugins/datamodel/promela/PromelaDataModel.h +++ b/src/uscxml/plugins/datamodel/promela/PromelaDataModel.h @@ -47,6 +47,7 @@ public: virtual bool validate(const std::string& location, const std::string& schema); virtual bool isLocation(const std::string& expr); + virtual bool isValidSyntax(const std::string& expr); virtual uint32_t getLength(const std::string& expr); virtual void setForeach(const std::string& item, diff --git a/src/uscxml/plugins/invoker/xhtml/template/xhtml-invoker.inc.h b/src/uscxml/plugins/invoker/xhtml/template/xhtml-invoker.inc.h index 971260b..c5a5798 100644 --- a/src/uscxml/plugins/invoker/xhtml/template/xhtml-invoker.inc.h +++ b/src/uscxml/plugins/invoker/xhtml/template/xhtml-invoker.inc.h @@ -1,691 +1,691 @@ unsigned char template_xhtml_invoker_html[] = { - 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3d, - 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, - 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, - 0x78, 0x68, 0x74, 0x6d, 0x6c, 0x22, 0x3e, 0x0a, 0x09, 0x3c, 0x68, 0x65, - 0x61, 0x64, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6d, 0x65, 0x74, 0x61, 0x20, - 0x68, 0x74, 0x74, 0x70, 0x2d, 0x65, 0x71, 0x75, 0x69, 0x76, 0x3d, 0x22, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, - 0x22, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x3d, 0x22, 0x74, - 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x63, 0x68, 0x61, - 0x72, 0x73, 0x65, 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x22, 0x20, - 0x2f, 0x3e, 0x0a, 0x0a, 0x09, 0x09, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x47, - 0x65, 0x74, 0x20, 0x64, 0x6f, 0x6d, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x64, - 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x63, 0x72, 0x6f, 0x73, - 0x73, 0x20, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x73, 0x20, 0x2d, - 0x2d, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, - 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x3e, - 0x0a, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x65, 0x20, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x6f, - 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x31, 0x32, - 0x30, 0x36, 0x39, 0x33, 0x37, 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x2d, 0x64, 0x6f, 0x6d, 0x72, 0x65, 0x61, 0x64, - 0x79, 0x0a, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x64, 0x6f, 0x6d, - 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, - 0x63, 0x6b, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2a, - 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x45, 0x78, - 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x20, 0x2a, 0x2f, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x2f, 0x2a, 0x40, 0x63, 0x63, 0x5f, 0x6f, 0x6e, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x40, 0x69, 0x66, 0x20, 0x28, 0x40, 0x5f, 0x77, 0x69, - 0x6e, 0x33, 0x32, 0x20, 0x7c, 0x7c, 0x20, 0x40, 0x5f, 0x77, 0x69, 0x6e, - 0x36, 0x34, 0x29, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x28, 0x27, 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x69, 0x65, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x22, 0x20, 0x64, 0x65, 0x66, 0x65, - 0x72, 0x20, 0x73, 0x72, 0x63, 0x3d, 0x22, 0x2f, 0x2f, 0x3a, 0x22, 0x3e, - 0x3c, 0x5c, 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x27, 0x29, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x27, 0x69, 0x65, 0x53, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x27, 0x29, 0x2e, 0x6f, - 0x6e, 0x72, 0x65, 0x61, 0x64, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, - 0x72, 0x65, 0x61, 0x64, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, - 0x3d, 0x20, 0x27, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x27, - 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, - 0x61, 0x72, 0x20, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x20, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x54, 0x61, 0x67, 0x4e, 0x61, - 0x6d, 0x65, 0x28, 0x27, 0x68, 0x65, 0x61, 0x64, 0x27, 0x29, 0x5b, 0x30, - 0x5d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, - 0x72, 0x20, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3d, 0x20, 0x64, 0x6f, - 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x27, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x27, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2e, 0x74, 0x79, - 0x70, 0x65, 0x3d, 0x20, 0x27, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x6a, 0x61, - 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x27, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x2e, 0x73, 0x72, 0x63, 0x3d, 0x20, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x2d, 0x67, 0x6f, 0x6f, - 0x64, 0x2d, 0x78, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x77, 0x67, 0x78, 0x70, 0x61, 0x74, 0x68, - 0x2e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x2e, 0x6a, 0x73, 0x27, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x68, 0x65, 0x61, - 0x64, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, - 0x64, 0x28, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x29, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x77, 0x67, 0x78, 0x70, 0x61, 0x74, - 0x68, 0x2e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x28, 0x29, 0x3b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x65, 0x20, 0x68, - 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x6f, - 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x31, 0x38, - 0x31, 0x31, 0x31, 0x31, 0x36, 0x2f, 0x69, 0x65, 0x2d, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x64, 0x6f, 0x6d, - 0x2d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x6e, 0x6f, 0x64, 0x65, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x6f, - 0x64, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x61, 0x6c, 0x6c, 0x43, - 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x29, 0x20, 0x7b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x73, 0x77, 0x69, 0x74, - 0x63, 0x68, 0x20, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x6f, 0x64, - 0x65, 0x54, 0x79, 0x70, 0x65, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x63, 0x61, 0x73, 0x65, - 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x4c, - 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x3a, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x76, 0x61, 0x72, 0x20, 0x6e, 0x65, 0x77, 0x4e, 0x6f, 0x64, 0x65, - 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, - 0x74, 0x4e, 0x53, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x55, 0x52, 0x49, 0x2c, 0x20, 0x6e, - 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x20, 0x26, - 0x26, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, - 0x20, 0x3e, 0x20, 0x30, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, - 0x28, 0x76, 0x61, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, - 0x69, 0x6c, 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6c, 0x65, 0x6e, - 0x67, 0x74, 0x68, 0x3b, 0x20, 0x69, 0x20, 0x3c, 0x20, 0x69, 0x6c, 0x3b, - 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, - 0x65, 0x77, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x73, 0x65, 0x74, 0x41, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x6e, 0x6f, 0x64, 0x65, - 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5b, - 0x69, 0x5d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x2c, - 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x67, 0x65, 0x74, 0x41, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5b, 0x69, - 0x5d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x29, 0x29, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x69, 0x66, 0x28, 0x61, 0x6c, 0x6c, 0x43, 0x68, 0x69, - 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x20, 0x26, 0x26, 0x20, 0x6e, 0x6f, 0x64, - 0x65, 0x2e, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, - 0x20, 0x26, 0x26, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x68, 0x69, - 0x6c, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x2e, 0x6c, 0x65, 0x6e, 0x67, - 0x74, 0x68, 0x20, 0x3e, 0x20, 0x30, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, - 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x30, - 0x2c, 0x20, 0x69, 0x6c, 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, - 0x63, 0x68, 0x69, 0x6c, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x2e, 0x6c, - 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3b, 0x20, 0x69, 0x20, 0x3c, 0x20, 0x69, - 0x6c, 0x3b, 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6e, 0x65, 0x77, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x61, 0x70, 0x70, - 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, - 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x68, - 0x69, 0x6c, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x5b, 0x69, 0x5d, 0x2c, - 0x20, 0x61, 0x6c, 0x6c, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, - 0x29, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, - 0x6e, 0x65, 0x77, 0x4e, 0x6f, 0x64, 0x65, 0x3b, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x72, - 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x20, 0x20, 0x20, 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x4e, - 0x4f, 0x44, 0x45, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x20, 0x20, 0x20, 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x44, 0x41, 0x54, 0x41, 0x5f, - 0x53, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x44, 0x45, - 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, - 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x43, 0x4f, 0x4d, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, - 0x4f, 0x44, 0x45, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, - 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x78, 0x74, 0x4e, 0x6f, 0x64, 0x65, - 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x7d, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x28, 0x29, 0x3b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x40, 0x65, 0x6e, - 0x64, 0x20, 0x40, 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2a, - 0x20, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2c, 0x20, 0x43, 0x68, - 0x72, 0x6f, 0x6d, 0x65, 0x2c, 0x20, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x20, - 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x64, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x61, 0x64, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, - 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x64, 0x6f, 0x63, - 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x61, 0x64, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, 0x27, - 0x44, 0x4f, 0x4d, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4c, 0x6f, - 0x61, 0x64, 0x65, 0x64, 0x27, 0x2c, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x62, - 0x61, 0x63, 0x6b, 0x2c, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x2f, 0x2a, 0x20, 0x53, 0x61, 0x66, 0x61, 0x72, 0x69, 0x2c, 0x20, 0x69, - 0x43, 0x61, 0x62, 0x2c, 0x20, 0x4b, 0x6f, 0x6e, 0x71, 0x75, 0x65, 0x72, - 0x6f, 0x72, 0x20, 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, - 0x20, 0x28, 0x2f, 0x4b, 0x48, 0x54, 0x4d, 0x4c, 0x7c, 0x57, 0x65, 0x62, - 0x4b, 0x69, 0x74, 0x7c, 0x69, 0x43, 0x61, 0x62, 0x2f, 0x69, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x28, 0x6e, 0x61, 0x76, 0x69, 0x67, 0x61, 0x74, 0x6f, - 0x72, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x29, - 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, - 0x20, 0x44, 0x4f, 0x4d, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, - 0x72, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, - 0x76, 0x61, 0x6c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x69, 0x66, 0x20, 0x28, 0x2f, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x7c, - 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2f, 0x69, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x28, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x72, 0x65, 0x61, 0x64, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x29, - 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, - 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x28, 0x29, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x49, - 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x28, 0x44, 0x4f, 0x4d, 0x4c, - 0x6f, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x29, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x7d, 0x2c, 0x20, 0x31, 0x30, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2a, 0x20, 0x4f, 0x74, - 0x68, 0x65, 0x72, 0x20, 0x77, 0x65, 0x62, 0x20, 0x62, 0x72, 0x6f, 0x77, - 0x73, 0x65, 0x72, 0x73, 0x20, 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6f, 0x6e, 0x6c, 0x6f, 0x61, - 0x64, 0x20, 0x3d, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x3c, - 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x0a, 0x0a, 0x09, 0x09, - 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, - 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x0a, - 0x09, 0x09, 0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x28, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x29, 0x20, 0x7b, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2a, 0x2a, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x20, 0x2a, 0x20, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x66, - 0x6f, 0x72, 0x20, 0x74, 0x77, 0x6f, 0x2d, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x20, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, - 0x6f, 0x75, 0x73, 0x20, 0x68, 0x74, 0x74, 0x70, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x20, 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x66, - 0x6f, 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, 0x6b, 0x65, 0x79, 0x20, - 0x69, 0x6e, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x29, 0x20, - 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, - 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x68, 0x61, 0x73, 0x4f, - 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x28, 0x6b, - 0x65, 0x79, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, - 0x20, 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, 0x5b, 0x6b, 0x65, 0x79, 0x5d, - 0x20, 0x3d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5b, 0x6b, - 0x65, 0x79, 0x5d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x7d, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x76, - 0x61, 0x72, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x20, 0x3d, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x78, 0x68, 0x72, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x28, 0x77, 0x69, 0x6e, 0x64, 0x6f, - 0x77, 0x2e, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x20, 0x3f, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x58, - 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x28, 0x29, 0x20, 0x3a, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x41, 0x63, - 0x74, 0x69, 0x76, 0x65, 0x58, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x28, - 0x22, 0x4d, 0x53, 0x58, 0x4d, 0x4c, 0x32, 0x2e, 0x58, 0x4d, 0x4c, 0x48, - 0x54, 0x54, 0x50, 0x2e, 0x33, 0x2e, 0x30, 0x22, 0x29, 0x29, 0x3b, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, - 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x20, 0x3d, 0x20, 0x28, 0x77, 0x69, - 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x3f, 0x20, 0x6e, 0x65, - 0x77, 0x20, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x28, 0x29, 0x20, 0x3a, 0x20, 0x6e, 0x65, 0x77, - 0x20, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x58, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x28, 0x22, 0x4d, 0x53, 0x58, 0x4d, 0x4c, 0x32, 0x2e, 0x58, - 0x4d, 0x4c, 0x48, 0x54, 0x54, 0x50, 0x2e, 0x33, 0x2e, 0x30, 0x22, 0x29, - 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x74, 0x68, 0x69, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, - 0x55, 0x49, 0x44, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x2f, 0x2f, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, - 0x77, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x72, - 0x66, 0x63, 0x2f, 0x72, 0x66, 0x63, 0x34, 0x31, 0x32, 0x32, 0x2e, 0x74, - 0x78, 0x74, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, - 0x73, 0x20, 0x3d, 0x20, 0x5b, 0x5d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x76, 0x61, 0x72, 0x20, 0x68, 0x65, 0x78, 0x44, 0x69, 0x67, 0x69, - 0x74, 0x73, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, - 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x22, 0x3b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x76, - 0x61, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x20, 0x69, 0x20, - 0x3c, 0x20, 0x33, 0x36, 0x3b, 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x20, 0x7b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x5b, 0x69, 0x5d, 0x20, - 0x3d, 0x20, 0x68, 0x65, 0x78, 0x44, 0x69, 0x67, 0x69, 0x74, 0x73, 0x2e, - 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x28, 0x4d, 0x61, 0x74, 0x68, 0x2e, - 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x4d, 0x61, 0x74, 0x68, 0x2e, 0x72, - 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x28, 0x29, 0x20, 0x2a, 0x20, 0x30, 0x78, - 0x31, 0x30, 0x29, 0x2c, 0x20, 0x31, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x5b, 0x31, - 0x34, 0x5d, 0x20, 0x3d, 0x20, 0x22, 0x34, 0x22, 0x3b, 0x20, 0x20, 0x2f, - 0x2f, 0x20, 0x62, 0x69, 0x74, 0x73, 0x20, 0x31, 0x32, 0x2d, 0x31, 0x35, - 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, - 0x5f, 0x68, 0x69, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x74, 0x6f, - 0x20, 0x30, 0x30, 0x31, 0x30, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, - 0x5b, 0x31, 0x39, 0x5d, 0x20, 0x3d, 0x20, 0x68, 0x65, 0x78, 0x44, 0x69, - 0x67, 0x69, 0x74, 0x73, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x28, - 0x28, 0x73, 0x5b, 0x31, 0x39, 0x5d, 0x20, 0x26, 0x20, 0x30, 0x78, 0x33, - 0x29, 0x20, 0x7c, 0x20, 0x30, 0x78, 0x38, 0x2c, 0x20, 0x31, 0x29, 0x3b, - 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x62, 0x69, 0x74, 0x73, 0x20, 0x36, 0x2d, - 0x37, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x6f, - 0x63, 0x6b, 0x5f, 0x73, 0x65, 0x71, 0x5f, 0x68, 0x69, 0x5f, 0x61, 0x6e, - 0x64, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x74, - 0x6f, 0x20, 0x30, 0x31, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x5b, - 0x38, 0x5d, 0x20, 0x3d, 0x20, 0x73, 0x5b, 0x31, 0x33, 0x5d, 0x20, 0x3d, - 0x20, 0x73, 0x5b, 0x31, 0x38, 0x5d, 0x20, 0x3d, 0x20, 0x73, 0x5b, 0x32, - 0x33, 0x5d, 0x20, 0x3d, 0x20, 0x22, 0x2d, 0x22, 0x3b, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x75, 0x75, 0x69, 0x64, 0x20, - 0x3d, 0x20, 0x73, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x28, 0x22, 0x22, 0x29, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, - 0x6e, 0x20, 0x75, 0x75, 0x69, 0x64, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x74, - 0x68, 0x69, 0x73, 0x2e, 0x6c, 0x6f, 0x6e, 0x67, 0x70, 0x6f, 0x6c, 0x6c, - 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, - 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, - 0x66, 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, - 0x6f, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x73, 0x65, 0x6c, 0x66, - 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x72, - 0x65, 0x61, 0x64, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, - 0x3d, 0x20, 0x34, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x63, - 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x20, 0x21, 0x3d, 0x3d, 0x20, 0x32, 0x30, 0x30, 0x29, - 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, - 0x65, 0x6c, 0x66, 0x2e, 0x6c, 0x6f, 0x6e, 0x67, 0x70, 0x6f, 0x6c, 0x6c, - 0x28, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x6f, 0x6e, 0x52, 0x63, 0x76, 0x64, 0x28, - 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, - 0x6c, 0x6c, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x6c, 0x6f, 0x6e, 0x67, 0x70, 0x6f, 0x6c, - 0x6c, 0x28, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x20, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x20, 0x77, 0x65, 0x20, - 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, - 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x6f, - 0x70, 0x65, 0x6e, 0x28, 0x22, 0x47, 0x45, 0x54, 0x22, 0x2c, 0x20, 0x73, - 0x65, 0x6c, 0x66, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2b, - 0x20, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x20, 0x3f, 0x20, 0x22, 0x3f, 0x22, 0x20, 0x2b, 0x20, 0x73, 0x65, 0x6c, - 0x66, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x3a, 0x20, 0x22, 0x22, - 0x29, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, - 0x66, 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, - 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x57, 0x69, 0x74, 0x68, 0x27, 0x2c, - 0x20, 0x27, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x27, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x63, 0x6f, 0x6d, - 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x65, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, - 0x22, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x2c, 0x20, 0x22, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x22, 0x29, 0x3b, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x63, 0x6f, - 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x65, 0x6e, 0x64, - 0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x74, 0x68, 0x69, 0x73, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, 0x6f, 0x70, 0x65, - 0x6e, 0x28, 0x22, 0x50, 0x4f, 0x53, 0x54, 0x22, 0x2c, 0x20, 0x73, 0x65, - 0x6c, 0x66, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2b, 0x20, - 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, - 0x3f, 0x20, 0x22, 0x3f, 0x22, 0x20, 0x2b, 0x20, 0x73, 0x65, 0x6c, 0x66, - 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x3a, 0x20, 0x22, 0x22, 0x29, - 0x2c, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, - 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x57, 0x69, 0x74, 0x68, 0x27, 0x2c, - 0x20, 0x27, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x27, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, 0x73, 0x65, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x53, 0x43, 0x58, 0x4d, 0x4c, 0x2d, - 0x4e, 0x61, 0x6d, 0x65, 0x27, 0x2c, 0x20, 0x22, 0x64, 0x6f, 0x6e, 0x65, - 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x22, 0x29, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, - 0x2e, 0x73, 0x65, 0x6e, 0x64, 0x28, 0x4e, 0x55, 0x4c, 0x4c, 0x29, 0x3b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x6f, - 0x73, 0x74, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x64, 0x61, 0x74, 0x61, - 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, - 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x28, - 0x22, 0x50, 0x4f, 0x53, 0x54, 0x22, 0x2c, 0x20, 0x73, 0x65, 0x6c, 0x66, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2b, 0x20, 0x28, 0x73, - 0x65, 0x6c, 0x66, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x3f, 0x20, - 0x22, 0x3f, 0x22, 0x20, 0x2b, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x71, - 0x75, 0x65, 0x72, 0x79, 0x20, 0x3a, 0x20, 0x22, 0x22, 0x29, 0x29, 0x3b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, - 0x68, 0x72, 0x2e, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x57, 0x69, 0x74, - 0x68, 0x27, 0x2c, 0x20, 0x27, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x27, 0x29, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, - 0x2e, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x53, 0x43, 0x58, - 0x4d, 0x4c, 0x2d, 0x4e, 0x61, 0x6d, 0x65, 0x27, 0x2c, 0x20, 0x6e, 0x61, - 0x6d, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, - 0x20, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, - 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, 0x73, 0x65, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x28, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, - 0x70, 0x65, 0x27, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x54, 0x79, 0x70, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, - 0x78, 0x68, 0x72, 0x2e, 0x73, 0x65, 0x6e, 0x64, 0x28, 0x64, 0x61, 0x74, - 0x61, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, - 0x73, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x74, 0x68, 0x69, - 0x73, 0x2e, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, - 0x74, 0x68, 0x69, 0x6e, 0x67, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x64, 0x61, 0x74, 0x61, 0x3b, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x65, 0x20, - 0x61, 0x6c, 0x73, 0x6f, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x72, 0x61, 0x77, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x6f, 0x75, 0x67, 0x6c, 0x61, 0x73, - 0x63, 0x72, 0x6f, 0x63, 0x6b, 0x66, 0x6f, 0x72, 0x64, 0x2f, 0x4a, 0x53, - 0x4f, 0x4e, 0x2d, 0x6a, 0x73, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, - 0x2f, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2e, 0x6a, 0x73, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, - 0x63, 0x68, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68, 0x69, 0x6e, - 0x67, 0x79, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x74, 0x79, - 0x70, 0x65, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x3d, - 0x3d, 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x29, - 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, - 0x20, 0x73, 0x65, 0x65, 0x6e, 0x20, 0x3d, 0x20, 0x5b, 0x5d, 0x3b, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x77, 0x69, 0x6c, - 0x6c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x6f, - 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x20, 0x61, 0x73, 0x20, 0x74, - 0x68, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x79, 0x63, 0x6c, - 0x65, 0x73, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3d, 0x20, 0x4a, 0x53, - 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, - 0x28, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2c, 0x20, 0x66, 0x75, 0x6e, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6b, 0x65, 0x79, 0x2c, 0x20, 0x76, 0x61, - 0x6c, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x69, 0x66, 0x20, 0x28, 0x69, 0x73, 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x76, - 0x61, 0x6c, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, - 0x20, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x6f, 0x66, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x73, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, - 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x6c, - 0x2e, 0x69, 0x64, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x76, - 0x61, 0x6c, 0x2e, 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x2c, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x76, 0x61, 0x6c, 0x2e, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x69, 0x66, 0x20, 0x28, 0x69, 0x73, 0x57, 0x69, 0x6e, 0x64, 0x6f, - 0x77, 0x28, 0x76, 0x61, 0x6c, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x74, 0x79, - 0x70, 0x65, 0x6f, 0x66, 0x20, 0x76, 0x61, 0x6c, 0x20, 0x3d, 0x3d, 0x3d, - 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x29, 0x20, 0x7b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, - 0x28, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, - 0x66, 0x28, 0x76, 0x61, 0x6c, 0x29, 0x20, 0x3e, 0x3d, 0x20, 0x30, 0x29, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, - 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x28, - 0x76, 0x61, 0x6c, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, - 0x75, 0x72, 0x6e, 0x20, 0x76, 0x61, 0x6c, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x7d, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, - 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3d, 0x20, 0x74, 0x68, 0x69, - 0x6e, 0x67, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x6f, 0x73, - 0x74, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x64, 0x61, 0x74, 0x61, - 0x2c, 0x20, 0x22, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x22, 0x29, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x20, - 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, - 0x64, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x65, 0x20, 0x77, 0x68, - 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x74, 0x68, - 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x68, 0x74, 0x6d, - 0x6c, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x76, - 0x61, 0x72, 0x20, 0x69, 0x73, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, - 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6f, 0x29, 0x7b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, - 0x6e, 0x20, 0x28, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, - 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x20, - 0x3d, 0x3d, 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, - 0x20, 0x3f, 0x20, 0x6f, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x6f, 0x66, 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x3a, 0x20, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x26, 0x26, - 0x20, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, 0x20, 0x3d, 0x3d, - 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x20, 0x26, - 0x26, 0x20, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, 0x2e, 0x6e, - 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x3d, 0x3d, 0x20, - 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x20, 0x26, 0x26, 0x20, - 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, 0x2e, 0x6e, 0x6f, 0x64, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x3d, 0x3d, 0x3d, 0x22, 0x73, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x22, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x29, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x2f, 0x2f, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x20, 0x66, - 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x64, - 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x65, 0x20, 0x77, 0x68, 0x65, - 0x74, 0x68, 0x65, 0x72, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x69, - 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x77, 0x69, 0x6e, 0x64, - 0x6f, 0x77, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x69, - 0x73, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x3d, 0x20, 0x66, 0x75, - 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6f, 0x29, 0x7b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, - 0x28, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, - 0x70, 0x65, 0x6f, 0x66, 0x20, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, - 0x3d, 0x3d, 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, - 0x20, 0x3f, 0x20, 0x6f, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x6f, 0x66, 0x20, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x3a, - 0x20, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, - 0x26, 0x26, 0x20, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, 0x20, - 0x3d, 0x3d, 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, - 0x20, 0x26, 0x26, 0x20, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, - 0x2e, 0x6d, 0x65, 0x6e, 0x75, 0x62, 0x61, 0x72, 0x20, 0x3d, 0x3d, 0x3d, - 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x20, 0x20, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, - 0x09, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x3e, 0x0a, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, - 0x74, 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x22, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x64, 0x6f, 0x6d, 0x4c, 0x6f, 0x61, - 0x64, 0x65, 0x64, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x73, 0x63, - 0x78, 0x6d, 0x6c, 0x20, 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x43, 0x6f, - 0x6d, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x7b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x65, 0x6c, 0x65, 0x6d, - 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x24, 0x7b, 0x73, 0x63, 0x78, 0x6d, - 0x6c, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x49, 0x64, 0x7d, 0x22, - 0x29, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x55, 0x52, 0x4c, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x6f, - 0x6e, 0x52, 0x63, 0x76, 0x64, 0x20, 0x3a, 0x20, 0x66, 0x75, 0x6e, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x64, 0x61, 0x74, 0x61, 0x29, 0x20, 0x7b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x58, 0x4d, 0x4c, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, - 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, - 0x22, 0x58, 0x2d, 0x53, 0x43, 0x58, 0x4d, 0x4c, 0x2d, 0x54, 0x79, 0x70, - 0x65, 0x22, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x22, 0x72, 0x65, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x22, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, - 0x20, 0x64, 0x6f, 0x6d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x3d, - 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, - 0x22, 0x58, 0x2d, 0x53, 0x43, 0x58, 0x4d, 0x4c, 0x2d, 0x58, 0x50, 0x61, - 0x74, 0x68, 0x22, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x22, 0x2f, 0x68, 0x74, - 0x6d, 0x6c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x3b, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x64, 0x6f, 0x6d, - 0x41, 0x74, 0x74, 0x72, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, - 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, 0x22, 0x58, 0x2d, 0x53, 0x43, 0x58, - 0x4d, 0x4c, 0x2d, 0x41, 0x74, 0x74, 0x72, 0x22, 0x29, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x72, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, - 0x65, 0x6e, 0x74, 0x2e, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, - 0x28, 0x64, 0x6f, 0x6d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2c, 0x20, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2c, 0x20, 0x6e, 0x75, - 0x6c, 0x6c, 0x2c, 0x20, 0x58, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x2e, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x45, 0x44, 0x5f, - 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, - 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x2c, 0x20, 0x6e, 0x75, 0x6c, 0x6c, - 0x29, 0x3b, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x66, - 0x6f, 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, - 0x30, 0x2c, 0x20, 0x6c, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x2e, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x4c, 0x65, - 0x6e, 0x67, 0x74, 0x68, 0x3b, 0x20, 0x69, 0x20, 0x3c, 0x20, 0x6c, 0x3b, - 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x69, 0x74, 0x65, 0x6d, - 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x73, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x28, 0x69, - 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, - 0x61, 0x72, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x6f, - 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, - 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x58, 0x4d, 0x4c, 0x2e, 0x66, - 0x69, 0x72, 0x73, 0x74, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x2c, 0x20, 0x74, - 0x72, 0x75, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x20, 0x28, 0x74, 0x79, - 0x70, 0x65, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x66, 0x69, 0x72, - 0x73, 0x74, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x22, 0x3a, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, - 0x2e, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, - 0x65, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, - 0x2e, 0x66, 0x69, 0x72, 0x73, 0x74, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x29, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, - 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x6c, 0x61, 0x73, - 0x74, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, - 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, - 0x6e, 0x6f, 0x64, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, - 0x20, 0x22, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x73, 0x69, - 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x70, - 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x69, 0x6e, - 0x73, 0x65, 0x72, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x28, 0x6e, - 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x29, 0x3b, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, - 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x6e, 0x65, 0x78, 0x74, 0x73, - 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, - 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x69, - 0x6e, 0x73, 0x65, 0x72, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x28, - 0x6e, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x6e, - 0x65, 0x78, 0x74, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x29, 0x3b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, - 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x72, 0x65, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x70, 0x61, 0x72, - 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x72, 0x65, 0x70, 0x6c, - 0x61, 0x63, 0x65, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x6e, 0x6f, 0x64, - 0x65, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x29, 0x3b, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, 0x61, 0x6b, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, - 0x61, 0x73, 0x65, 0x20, 0x22, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x22, - 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4e, - 0x6f, 0x64, 0x65, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, - 0x69, 0x6c, 0x64, 0x28, 0x69, 0x74, 0x65, 0x6d, 0x29, 0x3b, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, 0x61, - 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x61, 0x64, 0x64, 0x61, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, - 0x73, 0x65, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x28, 0x64, 0x6f, 0x6d, 0x41, 0x74, 0x74, 0x72, 0x2c, 0x20, 0x6e, 0x6f, - 0x64, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, - 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x63, 0x68, 0x69, 0x6c, 0x64, - 0x72, 0x65, 0x6e, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x28, 0x69, 0x74, - 0x65, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x4e, - 0x6f, 0x64, 0x65, 0x73, 0x28, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, - 0x6d, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, 0x69, 0x6c, - 0x64, 0x28, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x66, 0x69, 0x72, 0x73, 0x74, - 0x43, 0x68, 0x69, 0x6c, 0x64, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x61, - 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x6e, - 0x6f, 0x64, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x09, 0x09, 0x09, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x3a, 0x0a, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, - 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, - 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, - 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x29, 0x3b, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x73, 0x63, 0x78, 0x6d, 0x6c, 0x2e, 0x6c, 0x6f, 0x6e, 0x67, - 0x70, 0x6f, 0x6c, 0x6c, 0x28, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, - 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x73, 0x63, - 0x78, 0x6d, 0x6c, 0x3b, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x77, 0x69, - 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6f, 0x6e, 0x62, 0x65, 0x66, 0x6f, 0x72, - 0x65, 0x75, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, - 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x29, 0x20, 0x7b, - 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x63, 0x78, 0x6d, 0x6c, 0x2e, - 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x28, 0x29, - 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x72, 0x65, - 0x74, 0x75, 0x72, 0x6e, 0x20, 0x27, 0x59, 0x6f, 0x75, 0x20, 0x68, 0x61, - 0x76, 0x65, 0x20, 0x75, 0x6e, 0x73, 0x61, 0x76, 0x65, 0x64, 0x20, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x21, 0x27, 0x3b, 0x0a, 0x09, 0x09, - 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x7d, 0x29, 0x3b, 0x0a, - 0x09, 0x09, 0x3c, 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x0a, - 0x0a, 0x09, 0x3c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x3e, 0x0a, 0x09, 0x3c, - 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e, - 0x0a, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0a + 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3d, + 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, + 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x31, 0x39, 0x39, 0x39, 0x2f, + 0x78, 0x68, 0x74, 0x6d, 0x6c, 0x22, 0x3e, 0x0a, 0x09, 0x3c, 0x68, 0x65, + 0x61, 0x64, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x6d, 0x65, 0x74, 0x61, 0x20, + 0x68, 0x74, 0x74, 0x70, 0x2d, 0x65, 0x71, 0x75, 0x69, 0x76, 0x3d, 0x22, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, + 0x22, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x3d, 0x22, 0x74, + 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x63, 0x68, 0x61, + 0x72, 0x73, 0x65, 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x22, 0x20, + 0x2f, 0x3e, 0x0a, 0x0a, 0x09, 0x09, 0x3c, 0x21, 0x2d, 0x2d, 0x20, 0x47, + 0x65, 0x74, 0x20, 0x64, 0x6f, 0x6d, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x64, + 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x63, 0x72, 0x6f, 0x73, + 0x73, 0x20, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x73, 0x20, 0x2d, + 0x2d, 0x3e, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, + 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x3e, + 0x0a, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x65, 0x20, 0x68, + 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x6f, + 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x31, 0x32, + 0x30, 0x36, 0x39, 0x33, 0x37, 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x2d, 0x64, 0x6f, 0x6d, 0x72, 0x65, 0x61, 0x64, + 0x79, 0x0a, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x64, 0x6f, 0x6d, + 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, + 0x63, 0x6b, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2a, + 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x45, 0x78, + 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x20, 0x2a, 0x2f, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x2f, 0x2a, 0x40, 0x63, 0x63, 0x5f, 0x6f, 0x6e, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x40, 0x69, 0x66, 0x20, 0x28, 0x40, 0x5f, 0x77, 0x69, + 0x6e, 0x33, 0x32, 0x20, 0x7c, 0x7c, 0x20, 0x40, 0x5f, 0x77, 0x69, 0x6e, + 0x36, 0x34, 0x29, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x77, + 0x72, 0x69, 0x74, 0x65, 0x28, 0x27, 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x69, 0x65, 0x53, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x22, 0x20, 0x64, 0x65, 0x66, 0x65, + 0x72, 0x20, 0x73, 0x72, 0x63, 0x3d, 0x22, 0x2f, 0x2f, 0x3a, 0x22, 0x3e, + 0x3c, 0x5c, 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x27, 0x29, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x27, 0x69, 0x65, 0x53, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x27, 0x29, 0x2e, 0x6f, + 0x6e, 0x72, 0x65, 0x61, 0x64, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, + 0x72, 0x65, 0x61, 0x64, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, + 0x3d, 0x20, 0x27, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x27, + 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, + 0x61, 0x72, 0x20, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x20, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x54, 0x61, 0x67, 0x4e, 0x61, + 0x6d, 0x65, 0x28, 0x27, 0x68, 0x65, 0x61, 0x64, 0x27, 0x29, 0x5b, 0x30, + 0x5d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, + 0x72, 0x20, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3d, 0x20, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x27, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x27, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x3d, 0x20, 0x27, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x6a, 0x61, + 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x27, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x2e, 0x73, 0x72, 0x63, 0x3d, 0x20, 0x27, 0x68, 0x74, 0x74, 0x70, 0x3a, + 0x2f, 0x2f, 0x77, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x2d, 0x67, 0x6f, 0x6f, + 0x64, 0x2d, 0x78, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, + 0x69, 0x6c, 0x65, 0x73, 0x2f, 0x77, 0x67, 0x78, 0x70, 0x61, 0x74, 0x68, + 0x2e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x2e, 0x6a, 0x73, 0x27, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x68, 0x65, 0x61, + 0x64, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, + 0x64, 0x28, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x29, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x77, 0x67, 0x78, 0x70, 0x61, 0x74, + 0x68, 0x2e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x28, 0x29, 0x3b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x65, 0x20, 0x68, + 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x6f, + 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x31, 0x38, + 0x31, 0x31, 0x31, 0x31, 0x36, 0x2f, 0x69, 0x65, 0x2d, 0x73, 0x75, 0x70, + 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x66, 0x6f, 0x72, 0x2d, 0x64, 0x6f, 0x6d, + 0x2d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x6e, 0x6f, 0x64, 0x65, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x64, 0x6f, 0x63, 0x75, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x6f, + 0x64, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x61, 0x6c, 0x6c, 0x43, + 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x29, 0x20, 0x7b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x73, 0x77, 0x69, 0x74, + 0x63, 0x68, 0x20, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x6f, 0x64, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x63, 0x61, 0x73, 0x65, + 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x4c, + 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, 0x4f, 0x44, 0x45, 0x3a, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x76, 0x61, 0x72, 0x20, 0x6e, 0x65, 0x77, 0x4e, 0x6f, 0x64, 0x65, + 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x4e, 0x53, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x55, 0x52, 0x49, 0x2c, 0x20, 0x6e, + 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x20, 0x26, + 0x26, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, + 0x20, 0x3e, 0x20, 0x30, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, + 0x28, 0x76, 0x61, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x30, 0x2c, 0x20, + 0x69, 0x6c, 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6c, 0x65, 0x6e, + 0x67, 0x74, 0x68, 0x3b, 0x20, 0x69, 0x20, 0x3c, 0x20, 0x69, 0x6c, 0x3b, + 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, + 0x65, 0x77, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x73, 0x65, 0x74, 0x41, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x6e, 0x6f, 0x64, 0x65, + 0x2e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5b, + 0x69, 0x5d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x2c, + 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x67, 0x65, 0x74, 0x41, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5b, 0x69, + 0x5d, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x29, 0x29, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x69, 0x66, 0x28, 0x61, 0x6c, 0x6c, 0x43, 0x68, 0x69, + 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x20, 0x26, 0x26, 0x20, 0x6e, 0x6f, 0x64, + 0x65, 0x2e, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, + 0x20, 0x26, 0x26, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x68, 0x69, + 0x6c, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x2e, 0x6c, 0x65, 0x6e, 0x67, + 0x74, 0x68, 0x20, 0x3e, 0x20, 0x30, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, + 0x6f, 0x72, 0x28, 0x76, 0x61, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x30, + 0x2c, 0x20, 0x69, 0x6c, 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, + 0x63, 0x68, 0x69, 0x6c, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x2e, 0x6c, + 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3b, 0x20, 0x69, 0x20, 0x3c, 0x20, 0x69, + 0x6c, 0x3b, 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x6e, 0x65, 0x77, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x61, 0x70, 0x70, + 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x63, 0x68, + 0x69, 0x6c, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x5b, 0x69, 0x5d, 0x2c, + 0x20, 0x61, 0x6c, 0x6c, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, + 0x29, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, + 0x6e, 0x65, 0x77, 0x4e, 0x6f, 0x64, 0x65, 0x3b, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x72, + 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x20, 0x20, 0x20, 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x4e, + 0x4f, 0x44, 0x45, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x20, 0x20, 0x20, 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x44, 0x41, 0x54, 0x41, 0x5f, + 0x53, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x44, 0x45, + 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, + 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x43, 0x4f, 0x4d, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4e, + 0x4f, 0x44, 0x45, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, + 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x78, 0x74, 0x4e, 0x6f, 0x64, 0x65, + 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x6e, 0x6f, 0x64, 0x65, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x7d, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x28, 0x29, 0x3b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x40, 0x65, 0x6e, + 0x64, 0x20, 0x40, 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2a, + 0x20, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2c, 0x20, 0x43, 0x68, + 0x72, 0x6f, 0x6d, 0x65, 0x2c, 0x20, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x20, + 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x61, 0x64, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, + 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x61, 0x64, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, 0x27, + 0x44, 0x4f, 0x4d, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4c, 0x6f, + 0x61, 0x64, 0x65, 0x64, 0x27, 0x2c, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x62, + 0x61, 0x63, 0x6b, 0x2c, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x2f, 0x2a, 0x20, 0x53, 0x61, 0x66, 0x61, 0x72, 0x69, 0x2c, 0x20, 0x69, + 0x43, 0x61, 0x62, 0x2c, 0x20, 0x4b, 0x6f, 0x6e, 0x71, 0x75, 0x65, 0x72, + 0x6f, 0x72, 0x20, 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, + 0x20, 0x28, 0x2f, 0x4b, 0x48, 0x54, 0x4d, 0x4c, 0x7c, 0x57, 0x65, 0x62, + 0x4b, 0x69, 0x74, 0x7c, 0x69, 0x43, 0x61, 0x62, 0x2f, 0x69, 0x2e, 0x74, + 0x65, 0x73, 0x74, 0x28, 0x6e, 0x61, 0x76, 0x69, 0x67, 0x61, 0x74, 0x6f, + 0x72, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x29, + 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, + 0x20, 0x44, 0x4f, 0x4d, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, + 0x72, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x69, 0x66, 0x20, 0x28, 0x2f, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x7c, + 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2f, 0x69, 0x2e, 0x74, + 0x65, 0x73, 0x74, 0x28, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x72, 0x65, 0x61, 0x64, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x29, + 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, + 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x28, 0x29, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x28, 0x44, 0x4f, 0x4d, 0x4c, + 0x6f, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x29, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x7d, 0x2c, 0x20, 0x31, 0x30, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2a, 0x20, 0x4f, 0x74, + 0x68, 0x65, 0x72, 0x20, 0x77, 0x65, 0x62, 0x20, 0x62, 0x72, 0x6f, 0x77, + 0x73, 0x65, 0x72, 0x73, 0x20, 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6f, 0x6e, 0x6c, 0x6f, 0x61, + 0x64, 0x20, 0x3d, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x3c, + 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x0a, 0x0a, 0x09, 0x09, + 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x0a, + 0x09, 0x09, 0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x43, 0x6f, 0x6d, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x28, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x29, 0x20, 0x7b, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2a, 0x2a, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x20, 0x2a, 0x20, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x74, 0x77, 0x6f, 0x2d, 0x63, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x20, 0x61, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x72, 0x6f, 0x6e, + 0x6f, 0x75, 0x73, 0x20, 0x68, 0x74, 0x74, 0x70, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x20, 0x2a, 0x2f, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x66, + 0x6f, 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, 0x6b, 0x65, 0x79, 0x20, + 0x69, 0x6e, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x29, 0x20, + 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x68, 0x61, 0x73, 0x4f, + 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x28, 0x6b, + 0x65, 0x79, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, + 0x20, 0x20, 0x20, 0x74, 0x68, 0x69, 0x73, 0x5b, 0x6b, 0x65, 0x79, 0x5d, + 0x20, 0x3d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5b, 0x6b, + 0x65, 0x79, 0x5d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x7d, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x76, + 0x61, 0x72, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x20, 0x3d, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x78, 0x68, 0x72, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x28, 0x77, 0x69, 0x6e, 0x64, 0x6f, + 0x77, 0x2e, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x20, 0x3f, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x58, + 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x28, 0x29, 0x20, 0x3a, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x41, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x58, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x28, + 0x22, 0x4d, 0x53, 0x58, 0x4d, 0x4c, 0x32, 0x2e, 0x58, 0x4d, 0x4c, 0x48, + 0x54, 0x54, 0x50, 0x2e, 0x33, 0x2e, 0x30, 0x22, 0x29, 0x29, 0x3b, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, + 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x20, 0x3d, 0x20, 0x28, 0x77, 0x69, + 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x3f, 0x20, 0x6e, 0x65, + 0x77, 0x20, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x28, 0x29, 0x20, 0x3a, 0x20, 0x6e, 0x65, 0x77, + 0x20, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x58, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x28, 0x22, 0x4d, 0x53, 0x58, 0x4d, 0x4c, 0x32, 0x2e, 0x58, + 0x4d, 0x4c, 0x48, 0x54, 0x54, 0x50, 0x2e, 0x33, 0x2e, 0x30, 0x22, 0x29, + 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x74, 0x68, 0x69, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, + 0x55, 0x49, 0x44, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x2f, 0x2f, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, + 0x77, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x72, + 0x66, 0x63, 0x2f, 0x72, 0x66, 0x63, 0x34, 0x31, 0x32, 0x32, 0x2e, 0x74, + 0x78, 0x74, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, + 0x73, 0x20, 0x3d, 0x20, 0x5b, 0x5d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x76, 0x61, 0x72, 0x20, 0x68, 0x65, 0x78, 0x44, 0x69, 0x67, 0x69, + 0x74, 0x73, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x22, 0x3b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x76, + 0x61, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x20, 0x69, 0x20, + 0x3c, 0x20, 0x33, 0x36, 0x3b, 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x20, 0x7b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x5b, 0x69, 0x5d, 0x20, + 0x3d, 0x20, 0x68, 0x65, 0x78, 0x44, 0x69, 0x67, 0x69, 0x74, 0x73, 0x2e, + 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x28, 0x4d, 0x61, 0x74, 0x68, 0x2e, + 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x4d, 0x61, 0x74, 0x68, 0x2e, 0x72, + 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x28, 0x29, 0x20, 0x2a, 0x20, 0x30, 0x78, + 0x31, 0x30, 0x29, 0x2c, 0x20, 0x31, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x5b, 0x31, + 0x34, 0x5d, 0x20, 0x3d, 0x20, 0x22, 0x34, 0x22, 0x3b, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x62, 0x69, 0x74, 0x73, 0x20, 0x31, 0x32, 0x2d, 0x31, 0x35, + 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, + 0x5f, 0x68, 0x69, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x74, 0x6f, + 0x20, 0x30, 0x30, 0x31, 0x30, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, + 0x5b, 0x31, 0x39, 0x5d, 0x20, 0x3d, 0x20, 0x68, 0x65, 0x78, 0x44, 0x69, + 0x67, 0x69, 0x74, 0x73, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x28, + 0x28, 0x73, 0x5b, 0x31, 0x39, 0x5d, 0x20, 0x26, 0x20, 0x30, 0x78, 0x33, + 0x29, 0x20, 0x7c, 0x20, 0x30, 0x78, 0x38, 0x2c, 0x20, 0x31, 0x29, 0x3b, + 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x62, 0x69, 0x74, 0x73, 0x20, 0x36, 0x2d, + 0x37, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x6f, + 0x63, 0x6b, 0x5f, 0x73, 0x65, 0x71, 0x5f, 0x68, 0x69, 0x5f, 0x61, 0x6e, + 0x64, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x74, + 0x6f, 0x20, 0x30, 0x31, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x5b, + 0x38, 0x5d, 0x20, 0x3d, 0x20, 0x73, 0x5b, 0x31, 0x33, 0x5d, 0x20, 0x3d, + 0x20, 0x73, 0x5b, 0x31, 0x38, 0x5d, 0x20, 0x3d, 0x20, 0x73, 0x5b, 0x32, + 0x33, 0x5d, 0x20, 0x3d, 0x20, 0x22, 0x2d, 0x22, 0x3b, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x75, 0x75, 0x69, 0x64, 0x20, + 0x3d, 0x20, 0x73, 0x2e, 0x6a, 0x6f, 0x69, 0x6e, 0x28, 0x22, 0x22, 0x29, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, + 0x6e, 0x20, 0x75, 0x75, 0x69, 0x64, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x74, + 0x68, 0x69, 0x73, 0x2e, 0x6c, 0x6f, 0x6e, 0x67, 0x70, 0x6f, 0x6c, 0x6c, + 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, + 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, + 0x66, 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, + 0x6f, 0x6e, 0x72, 0x65, 0x61, 0x64, 0x79, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x73, 0x65, 0x6c, 0x66, + 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x72, + 0x65, 0x61, 0x64, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, + 0x3d, 0x20, 0x34, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x63, + 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x20, 0x21, 0x3d, 0x3d, 0x20, 0x32, 0x30, 0x30, 0x29, + 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, + 0x65, 0x6c, 0x66, 0x2e, 0x6c, 0x6f, 0x6e, 0x67, 0x70, 0x6f, 0x6c, 0x6c, + 0x28, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x6f, 0x6e, 0x52, 0x63, 0x76, 0x64, 0x28, + 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, + 0x6c, 0x6c, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x6c, 0x6f, 0x6e, 0x67, 0x70, 0x6f, 0x6c, + 0x6c, 0x28, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x20, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x20, 0x77, 0x65, 0x20, + 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, + 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x28, 0x22, 0x47, 0x45, 0x54, 0x22, 0x2c, 0x20, 0x73, + 0x65, 0x6c, 0x66, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2b, + 0x20, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, + 0x20, 0x3f, 0x20, 0x22, 0x3f, 0x22, 0x20, 0x2b, 0x20, 0x73, 0x65, 0x6c, + 0x66, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x3a, 0x20, 0x22, 0x22, + 0x29, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, + 0x66, 0x2e, 0x63, 0x6f, 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, + 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x57, 0x69, 0x74, 0x68, 0x27, 0x2c, + 0x20, 0x27, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x27, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x63, 0x6f, 0x6d, + 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x65, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, + 0x22, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0x2c, 0x20, 0x22, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x22, 0x29, 0x3b, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x63, 0x6f, + 0x6d, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x65, 0x6e, 0x64, + 0x28, 0x6e, 0x75, 0x6c, 0x6c, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x74, 0x68, 0x69, 0x73, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x28, 0x22, 0x50, 0x4f, 0x53, 0x54, 0x22, 0x2c, 0x20, 0x73, 0x65, + 0x6c, 0x66, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2b, 0x20, + 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, + 0x3f, 0x20, 0x22, 0x3f, 0x22, 0x20, 0x2b, 0x20, 0x73, 0x65, 0x6c, 0x66, + 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x3a, 0x20, 0x22, 0x22, 0x29, + 0x2c, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, + 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x57, 0x69, 0x74, 0x68, 0x27, 0x2c, + 0x20, 0x27, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x27, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, 0x73, 0x65, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x53, 0x43, 0x58, 0x4d, 0x4c, 0x2d, + 0x4e, 0x61, 0x6d, 0x65, 0x27, 0x2c, 0x20, 0x22, 0x64, 0x6f, 0x6e, 0x65, + 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x22, 0x29, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, + 0x2e, 0x73, 0x65, 0x6e, 0x64, 0x28, 0x4e, 0x55, 0x4c, 0x4c, 0x29, 0x3b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x6f, + 0x73, 0x74, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x64, 0x61, 0x74, 0x61, + 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, + 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x28, + 0x22, 0x50, 0x4f, 0x53, 0x54, 0x22, 0x2c, 0x20, 0x73, 0x65, 0x6c, 0x66, + 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2b, 0x20, 0x28, 0x73, + 0x65, 0x6c, 0x66, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x20, 0x3f, 0x20, + 0x22, 0x3f, 0x22, 0x20, 0x2b, 0x20, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x20, 0x3a, 0x20, 0x22, 0x22, 0x29, 0x29, 0x3b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, + 0x68, 0x72, 0x2e, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x2d, 0x57, 0x69, 0x74, + 0x68, 0x27, 0x2c, 0x20, 0x27, 0x58, 0x4d, 0x4c, 0x48, 0x74, 0x74, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x27, 0x29, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, + 0x2e, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, 0x27, 0x58, 0x2d, 0x53, 0x43, 0x58, + 0x4d, 0x4c, 0x2d, 0x4e, 0x61, 0x6d, 0x65, 0x27, 0x2c, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, + 0x20, 0x28, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, + 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x68, 0x72, 0x2e, 0x73, 0x65, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x28, 0x27, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, + 0x70, 0x65, 0x27, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, + 0x78, 0x68, 0x72, 0x2e, 0x73, 0x65, 0x6e, 0x64, 0x28, 0x64, 0x61, 0x74, + 0x61, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, + 0x73, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x74, 0x68, 0x69, + 0x73, 0x2e, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, + 0x74, 0x68, 0x69, 0x6e, 0x67, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x64, 0x61, 0x74, 0x61, 0x3b, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x65, 0x20, + 0x61, 0x6c, 0x73, 0x6f, 0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, + 0x2f, 0x2f, 0x72, 0x61, 0x77, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x6f, 0x75, 0x67, 0x6c, 0x61, 0x73, + 0x63, 0x72, 0x6f, 0x63, 0x6b, 0x66, 0x6f, 0x72, 0x64, 0x2f, 0x4a, 0x53, + 0x4f, 0x4e, 0x2d, 0x6a, 0x73, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, + 0x2f, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x2e, 0x6a, 0x73, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x64, 0x69, 0x73, 0x70, 0x61, 0x74, + 0x63, 0x68, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68, 0x69, 0x6e, + 0x67, 0x79, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x74, 0x79, + 0x70, 0x65, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x3d, + 0x3d, 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x29, + 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, + 0x20, 0x73, 0x65, 0x65, 0x6e, 0x20, 0x3d, 0x20, 0x5b, 0x5d, 0x3b, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x6f, + 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x20, 0x61, 0x73, 0x20, 0x74, + 0x68, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x79, 0x63, 0x6c, + 0x65, 0x73, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3d, 0x20, 0x4a, 0x53, + 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, + 0x28, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2c, 0x20, 0x66, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6b, 0x65, 0x79, 0x2c, 0x20, 0x76, 0x61, + 0x6c, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x69, 0x66, 0x20, 0x28, 0x69, 0x73, 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x76, + 0x61, 0x6c, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, + 0x20, 0x61, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x6f, 0x66, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, + 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x6c, + 0x2e, 0x69, 0x64, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x76, + 0x61, 0x6c, 0x2e, 0x74, 0x61, 0x67, 0x4e, 0x61, 0x6d, 0x65, 0x2c, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x3a, 0x20, 0x76, 0x61, 0x6c, 0x2e, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x69, 0x66, 0x20, 0x28, 0x69, 0x73, 0x57, 0x69, 0x6e, 0x64, 0x6f, + 0x77, 0x28, 0x76, 0x61, 0x6c, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x74, 0x79, + 0x70, 0x65, 0x6f, 0x66, 0x20, 0x76, 0x61, 0x6c, 0x20, 0x3d, 0x3d, 0x3d, + 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x29, 0x20, 0x7b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, + 0x28, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x4f, + 0x66, 0x28, 0x76, 0x61, 0x6c, 0x29, 0x20, 0x3e, 0x3d, 0x20, 0x30, 0x29, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, + 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x73, 0x65, 0x65, 0x6e, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x28, + 0x76, 0x61, 0x6c, 0x29, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, + 0x75, 0x72, 0x6e, 0x20, 0x76, 0x61, 0x6c, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x7d, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, + 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3d, 0x20, 0x74, 0x68, 0x69, + 0x6e, 0x67, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x6f, 0x73, + 0x74, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x64, 0x61, 0x74, 0x61, + 0x2c, 0x20, 0x22, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x22, 0x29, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x20, + 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, + 0x64, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x65, 0x20, 0x77, 0x68, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x74, 0x68, + 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x68, 0x74, 0x6d, + 0x6c, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x76, + 0x61, 0x72, 0x20, 0x69, 0x73, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, + 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6f, 0x29, 0x7b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, + 0x6e, 0x20, 0x28, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x20, + 0x3d, 0x3d, 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, + 0x20, 0x3f, 0x20, 0x6f, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x6f, 0x66, 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x3a, 0x20, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x26, 0x26, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, 0x20, 0x3d, 0x3d, + 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x20, 0x26, + 0x26, 0x20, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, 0x2e, 0x6e, + 0x6f, 0x64, 0x65, 0x54, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x3d, 0x3d, 0x20, + 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x20, 0x26, 0x26, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, 0x2e, 0x6e, 0x6f, 0x64, + 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x3d, 0x3d, 0x3d, 0x22, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x22, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x29, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x2f, 0x2f, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x20, 0x66, + 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x64, + 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x65, 0x20, 0x77, 0x68, 0x65, + 0x74, 0x68, 0x65, 0x72, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x69, + 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x77, 0x69, 0x6e, 0x64, + 0x6f, 0x77, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x69, + 0x73, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x3d, 0x20, 0x66, 0x75, + 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x6f, 0x29, 0x7b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, + 0x28, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, + 0x70, 0x65, 0x6f, 0x66, 0x20, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, + 0x3d, 0x3d, 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, + 0x20, 0x3f, 0x20, 0x6f, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x6f, 0x66, 0x20, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x3a, + 0x20, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, + 0x26, 0x26, 0x20, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, 0x20, + 0x3d, 0x3d, 0x3d, 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, + 0x20, 0x26, 0x26, 0x20, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6f, + 0x2e, 0x6d, 0x65, 0x6e, 0x75, 0x62, 0x61, 0x72, 0x20, 0x3d, 0x3d, 0x3d, + 0x20, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x20, 0x20, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, + 0x09, 0x0a, 0x09, 0x09, 0x3c, 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x3e, 0x0a, 0x09, 0x09, 0x0a, 0x09, 0x09, 0x3c, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, + 0x74, 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x22, 0x3e, 0x0a, 0x09, 0x09, 0x09, 0x64, 0x6f, 0x6d, 0x4c, 0x6f, 0x61, + 0x64, 0x65, 0x64, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x73, 0x63, + 0x78, 0x6d, 0x6c, 0x20, 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x43, 0x6f, + 0x6d, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x7b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x24, 0x7b, 0x73, 0x63, 0x78, 0x6d, + 0x6c, 0x2e, 0x69, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x49, 0x64, 0x7d, 0x22, + 0x29, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x55, 0x52, 0x4c, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x6f, + 0x6e, 0x52, 0x63, 0x76, 0x64, 0x20, 0x3a, 0x20, 0x66, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x64, 0x61, 0x74, 0x61, 0x29, 0x20, 0x7b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x28, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x58, 0x4d, 0x4c, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, + 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, + 0x22, 0x58, 0x2d, 0x53, 0x43, 0x58, 0x4d, 0x4c, 0x2d, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x22, 0x72, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x22, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, + 0x20, 0x64, 0x6f, 0x6d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x3d, + 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, + 0x22, 0x58, 0x2d, 0x53, 0x43, 0x58, 0x4d, 0x4c, 0x2d, 0x58, 0x50, 0x61, + 0x74, 0x68, 0x22, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x22, 0x2f, 0x68, 0x74, + 0x6d, 0x6c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x3b, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x64, 0x6f, 0x6d, + 0x41, 0x74, 0x74, 0x72, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, + 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, 0x22, 0x58, 0x2d, 0x53, 0x43, 0x58, + 0x4d, 0x4c, 0x2d, 0x41, 0x74, 0x74, 0x72, 0x22, 0x29, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, + 0x28, 0x64, 0x6f, 0x6d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2c, 0x20, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2c, 0x20, 0x6e, 0x75, + 0x6c, 0x6c, 0x2c, 0x20, 0x58, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x2e, 0x4f, 0x52, 0x44, 0x45, 0x52, 0x45, 0x44, 0x5f, + 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, + 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x2c, 0x20, 0x6e, 0x75, 0x6c, 0x6c, + 0x29, 0x3b, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x66, + 0x6f, 0x72, 0x20, 0x28, 0x76, 0x61, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, + 0x30, 0x2c, 0x20, 0x6c, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x2e, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x4c, 0x65, + 0x6e, 0x67, 0x74, 0x68, 0x3b, 0x20, 0x69, 0x20, 0x3c, 0x20, 0x6c, 0x3b, + 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x76, 0x61, 0x72, 0x20, 0x69, 0x74, 0x65, 0x6d, + 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x73, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x28, 0x69, + 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x76, + 0x61, 0x72, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, + 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x28, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x58, 0x4d, 0x4c, 0x2e, 0x66, + 0x69, 0x72, 0x73, 0x74, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x2c, 0x20, 0x74, + 0x72, 0x75, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x20, 0x28, 0x74, 0x79, + 0x70, 0x65, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x66, 0x69, 0x72, + 0x73, 0x74, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x22, 0x3a, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, + 0x2e, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, + 0x65, 0x28, 0x6e, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, + 0x2e, 0x66, 0x69, 0x72, 0x73, 0x74, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x29, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, + 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x6c, 0x61, 0x73, + 0x74, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, + 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, + 0x6e, 0x6f, 0x64, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, + 0x20, 0x22, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x73, 0x69, + 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x69, 0x6e, + 0x73, 0x65, 0x72, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x28, 0x6e, + 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x29, 0x3b, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, + 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x6e, 0x65, 0x78, 0x74, 0x73, + 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, + 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x69, + 0x6e, 0x73, 0x65, 0x72, 0x74, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x28, + 0x6e, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x6e, + 0x65, 0x78, 0x74, 0x53, 0x69, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x29, 0x3b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, + 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x72, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x72, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x6e, 0x6f, 0x64, + 0x65, 0x2c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x29, 0x3b, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, 0x61, 0x6b, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, + 0x61, 0x73, 0x65, 0x20, 0x22, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x22, + 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4e, + 0x6f, 0x64, 0x65, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, + 0x69, 0x6c, 0x64, 0x28, 0x69, 0x74, 0x65, 0x6d, 0x29, 0x3b, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, 0x61, + 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, 0x61, 0x64, 0x64, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, + 0x73, 0x65, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x28, 0x64, 0x6f, 0x6d, 0x41, 0x74, 0x74, 0x72, 0x2c, 0x20, 0x6e, 0x6f, + 0x64, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x63, 0x61, 0x73, 0x65, 0x20, 0x22, + 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x63, 0x68, 0x69, 0x6c, 0x64, + 0x72, 0x65, 0x6e, 0x22, 0x3a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x28, 0x69, 0x74, + 0x65, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x4e, + 0x6f, 0x64, 0x65, 0x73, 0x28, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, + 0x6d, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x43, 0x68, 0x69, 0x6c, + 0x64, 0x28, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x66, 0x69, 0x72, 0x73, 0x74, + 0x43, 0x68, 0x69, 0x6c, 0x64, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x2e, 0x61, + 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x6e, + 0x6f, 0x64, 0x65, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x3a, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x62, 0x72, 0x65, + 0x61, 0x6b, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x7d, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x7d, 0x29, 0x3b, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x73, 0x63, 0x78, 0x6d, 0x6c, 0x2e, 0x6c, 0x6f, 0x6e, 0x67, + 0x70, 0x6f, 0x6c, 0x6c, 0x28, 0x29, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, + 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x73, 0x63, + 0x78, 0x6d, 0x6c, 0x3b, 0x0a, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x77, 0x69, + 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6f, 0x6e, 0x62, 0x65, 0x66, 0x6f, 0x72, + 0x65, 0x75, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, + 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x29, 0x20, 0x7b, + 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x73, 0x63, 0x78, 0x6d, 0x6c, 0x2e, + 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x28, 0x29, + 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x09, 0x09, 0x2f, 0x2f, 0x20, 0x72, 0x65, + 0x74, 0x75, 0x72, 0x6e, 0x20, 0x27, 0x59, 0x6f, 0x75, 0x20, 0x68, 0x61, + 0x76, 0x65, 0x20, 0x75, 0x6e, 0x73, 0x61, 0x76, 0x65, 0x64, 0x20, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x21, 0x27, 0x3b, 0x0a, 0x09, 0x09, + 0x09, 0x09, 0x7d, 0x3b, 0x0a, 0x09, 0x09, 0x09, 0x7d, 0x29, 0x3b, 0x0a, + 0x09, 0x09, 0x3c, 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x0a, + 0x0a, 0x09, 0x3c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x3e, 0x0a, 0x09, 0x3c, + 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e, + 0x0a, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0a }; unsigned int template_xhtml_invoker_html_len = 8253; diff --git a/src/uscxml/transform/ChartToCPP.cpp b/src/uscxml/transform/ChartToCPP.cpp deleted file mode 100644 index 6b78374..0000000 --- a/src/uscxml/transform/ChartToCPP.cpp +++ /dev/null @@ -1,546 +0,0 @@ -/** - * @file - * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see . - * @endcond - */ - -#include "uscxml/transform/ChartToFSM.h" -#include "uscxml/transform/ChartToCPP.h" -#include -#include -#include "uscxml/UUID.h" -#include -#include -#include - -namespace uscxml { - -using namespace Arabica::DOM; -using namespace Arabica::XPath; - -void ChartToCPP::writeProgram(std::ostream& stream, - const Interpreter& interpreter) { - ChartToCPP promelaWriter; - promelaWriter.cloneFrom(interpreter.getImpl()); - promelaWriter.writeProgram(stream); -} - -ChartToCPP::ChartToCPP() : _eventTrie(".") { -} - -void ChartToCPP::writeEvents(std::ostream& stream) { - std::list eventNames = _eventTrie.getWordsWithPrefix(""); - std::list::iterator eventIter = eventNames.begin(); - stream << "// event name identifiers" << std::endl; - while(eventIter != eventNames.end()) { - stream << "#define " << "e" << (*eventIter)->identifier << " " << (*eventIter)->identifier; - stream << " // from \"" << (*eventIter)->value << "\"" << std::endl; - eventIter++; - } -} - -void ChartToCPP::writeStates(std::ostream& stream) { - stream << "// state name identifiers" << std::endl; - for (int i = 0; i < _globalStates.size(); i++) { - stream << "#define " << "s" << i << " " << i; - stream << " // from \"" << ATTR_CAST(_globalStates[i], "id") << "\"" << std::endl; - } - -} - -Arabica::XPath::NodeSet ChartToCPP::getTransientContent(const Arabica::DOM::Element& state) { - Arabica::XPath::NodeSet content; - Arabica::DOM::Element currState = state; - for (;;) { - if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) - break; - content.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "invoke", currState)); - content.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onentry", currState)); - content.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onexit", currState)); - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); - currState = _states[ATTR_CAST(transitions[0], "target")]; - } - - return content; -} - -Node ChartToCPP::getUltimateTarget(const Arabica::DOM::Element& transition) { - Arabica::DOM::Element currState = _states[ATTR(transition, "target")]; - - for (;;) { - if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) - return currState; - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); - currState = _states[ATTR_CAST(transitions[0], "target")]; - } -} - -void ChartToCPP::writeInlineComment(std::ostream& stream, const Arabica::DOM::Element& node) { - if (node.getNodeType() != Node_base::COMMENT_NODE) - return; - - std::string comment = node.getNodeValue(); - boost::trim(comment); - if (!boost::starts_with(comment, "promela-inline:")) - return; - - std::stringstream ssLine(comment); - std::string line; - std::getline(ssLine, line); // consume first line - while(std::getline(ssLine, line)) { - if (line.length() == 0) - continue; - stream << line; - } -} - -void ChartToCPP::writeExecutableContent(std::ostream& stream, const Arabica::DOM::Element& node, int indent) { - - std::string padding; - for (int i = 0; i < indent; i++) { - padding += " "; - } - - if (node.getNodeType() == Node_base::COMMENT_NODE) { - std::string comment = node.getNodeValue(); - boost::trim(comment); - std::stringstream inlinePromela; - if (!boost::starts_with(comment, "promela-inline:")) - return; - std::stringstream ssLine(comment); - std::string line; - std::getline(ssLine, line); // consume first line - while(std::getline(ssLine, line)) { - if (line.length() == 0) - continue; - inlinePromela << line << std::endl; - } - stream << padding << "skip;" << std::endl; - stream << beautifyIndentation(inlinePromela.str(), indent) << std::endl; - } - - if (node.getNodeType() != Node_base::ELEMENT_NODE) - return; - - if (false) { - } else if(TAGNAME(node) == "state") { - if (HAS_ATTR(node, "transient") && DOMUtils::attributeIsTrue(ATTR(node, "transient"))) { - Arabica::XPath::NodeSet execContent = getTransientContent(node); - for (int i = 0; i < execContent.size(); i++) { - writeExecutableContent(stream, Arabica::DOM::Element(execContent[i]), indent); - } - } else { - Arabica::DOM::Node child = node.getFirstChild(); - while(child) { - writeExecutableContent(stream, Arabica::DOM::Element(child), indent); - child = child.getNextSibling(); - } - } - } else if(TAGNAME(node) == "transition") { - stream << "t" << _transitions[node] << ":" << std::endl; - - stream << padding << "atomic {" << std::endl; - writeExecutableContent(stream, _states[ATTR(node, "target")], indent+1); - stream << padding << " skip;" << std::endl; - - Node newState = getUltimateTarget(node); - for (int i = 0; i < _globalStates.size(); i++) { - if (newState != _globalStates[i]) - continue; - stream << padding << " s = s" << i << ";" << std::endl; - } - - stream << padding << "}" << std::endl; - if (isFinal(Element(newState))) { - stream << padding << "goto terminate;" << std::endl; - } else { - stream << padding << "goto nextStep;" << std::endl; - } - - } else if(TAGNAME(node) == "onentry" || TAGNAME(node) == "onexit") { - Arabica::DOM::Node child = node.getFirstChild(); - while(child) { - writeExecutableContent(stream, Arabica::DOM::Element(child), indent); - child = child.getNextSibling(); - } - - } else if(TAGNAME(node) == "script") { - NodeSet scriptText = filterChildType(Node_base::TEXT_NODE, node, true); - for (int i = 0; i < scriptText.size(); i++) { - stream << beautifyIndentation(scriptText[i].getNodeValue(), indent) << std::endl; - } - - } else if(TAGNAME(node) == "log") { - // ignore - - } else if(TAGNAME(node) == "foreach") { - if (HAS_ATTR(node, "index")) - stream << padding << ATTR(node, "index") << " = 0;" << std::endl; - stream << padding << "for (" << ATTR(node, "item") << " in " << ATTR(node, "array") << ") {" << std::endl; - Arabica::DOM::Node child = node.getFirstChild(); - while(child) { - writeExecutableContent(stream, Arabica::DOM::Element(child), indent + 1); - child = child.getNextSibling(); - } - if (HAS_ATTR(node, "index")) - stream << padding << " " << ATTR(node, "index") << "++;" << std::endl; - stream << padding << "}" << std::endl; - - } else if(TAGNAME(node) == "if") { - NodeSet condChain; - condChain.push_back(node); - condChain.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "elseif", node)); - condChain.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "else", node)); - - writeIfBlock(stream, condChain, indent); - - } else if(TAGNAME(node) == "raise") { - TrieNode* trieNode = _eventTrie.getNodeWithPrefix(ATTR(node, "event")); - stream << padding << "iQ!e" << trieNode->identifier << ";" << std::endl; - } else if(TAGNAME(node) == "send") { - if (!HAS_ATTR(node, "target")) { - // this is for our external queue - TrieNode* trieNode = _eventTrie.getNodeWithPrefix(ATTR(node, "event")); - stream << padding << "tmpQ!e" << trieNode->identifier << ";" << std::endl; - } - } else if(TAGNAME(node) == "invoke") { - } else if(TAGNAME(node) == "uninvoke") { - stream << padding << ATTR(node, "invokeid") << "EventSourceDone" << "= 1;" << std::endl; - } else { - - std::cerr << "'" << TAGNAME(node) << "'" << std::endl << node << std::endl; - assert(false); - } - -} - -void ChartToCPP::writeIfBlock(std::ostream& stream, const Arabica::XPath::NodeSet& condChain, int indent) { - if (condChain.size() == 0) - return; - - std::string padding; - for (int i = 0; i < indent; i++) { - padding += " "; - } - - bool noNext = condChain.size() == 1; - bool nextIsElse = false; - if (condChain.size() > 1) { - if (TAGNAME_CAST(condChain[1]) == "else") { - nextIsElse = true; - } - } - - Node ifNode = condChain[0]; - - stream << padding << "if" << std::endl; - // we need to nest the elseifs to resolve promela if semantics - stream << padding << ":: (" << ATTR_CAST(ifNode, "cond") << ") -> {" << std::endl; - - Arabica::DOM::Node child; - if (TAGNAME_CAST(ifNode) == "if") { - child = ifNode.getFirstChild(); - } else { - child = ifNode.getNextSibling(); - } - while(child) { - if (child.getNodeType() == Node_base::ELEMENT_NODE) { - if (TAGNAME_CAST(child) == "elseif" || TAGNAME_CAST(child) == "else") - break; - } - writeExecutableContent(stream, Arabica::DOM::Element(child), indent + 1); - child = child.getNextSibling(); - } - stream << padding << "}" << std::endl; - stream << padding << ":: else -> "; - - if (nextIsElse) { - child = condChain[1].getNextSibling(); - stream << "{" << std::endl; - while(child) { - writeExecutableContent(stream, Arabica::DOM::Element(child), indent + 1); - child = child.getNextSibling(); - } - stream << padding << "}" << std::endl; - - } else if (noNext) { - stream << "skip;" << std::endl; - } else { - stream << "{" << std::endl; - - Arabica::XPath::NodeSet cdrCondChain; - for (int i = 1; i < condChain.size(); i++) { - cdrCondChain.push_back(condChain[i]); - } - writeIfBlock(stream, cdrCondChain, indent + 1); - stream << padding << "}" << std::endl; - - } - - stream << padding << "fi;" << std::endl; - -} - -std::string ChartToCPP::beautifyIndentation(const std::string& code, int indent) { - - std::string padding; - for (int i = 0; i < indent; i++) { - padding += " "; - } - - // remove topmost indentation from every line and reindent - std::stringstream beautifiedSS; - - std::string initialIndent; - bool gotIndent = false; - bool isFirstLine = true; - std::stringstream ssLine(code); - std::string line; - - while(std::getline(ssLine, line)) { - size_t firstChar = line.find_first_not_of(" \t\r\n"); - if (firstChar != std::string::npos) { - if (!gotIndent) { - initialIndent = line.substr(0, firstChar); - gotIndent = true; - } - beautifiedSS << (isFirstLine ? "" : "\n") << padding << boost::replace_first_copy(line, initialIndent, ""); - isFirstLine = false; - } - } - - return beautifiedSS.str(); -} - -void ChartToCPP::writeDeclarations(std::ostream& stream) { - - // get all data elements - NodeSet datas = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); - NodeSet dataText = filterChildType(Node_base::TEXT_NODE, datas, true); - - // write their text content - stream << "// datamodel variables" << std::endl; - for (int i = 0; i < dataText.size(); i++) { - Node data = dataText[i]; - stream << beautifyIndentation(data.getNodeValue()) << std::endl; - } - - stream << std::endl; - stream << "// global variables" << std::endl; - stream << "int e; /* current event */" << std::endl; - stream << "int s; /* current state */" << std::endl; - stream << "chan iQ = [100] of {int} /* internal queue */" << std::endl; - stream << "chan eQ = [100] of {int} /* external queue */" << std::endl; - stream << "chan tmpQ = [100] of {int} /* temporary queue for external events in transitions */" << std::endl; - stream << "int tmpQItem;" << std::endl; - - stream << std::endl; - stream << "// event sources" << std::endl; - -} - -void ChartToCPP::writeFSM(std::ostream& stream) { - NodeSet transitions; - - stream << "proctype step() {" << std::endl; - // write initial transition - transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _startState); - assert(transitions.size() == 1); - stream << " // transition's executable content" << std::endl; - writeExecutableContent(stream, Arabica::DOM::Element(transitions[0]), 1); - - for (int i = 0; i < _globalStates.size(); i++) { - if (_globalStates[i] == _startState) - continue; - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); - for (int j = 0; j < transitions.size(); j++) { - writeExecutableContent(stream, Arabica::DOM::Element(transitions[j]), 1); - } - } - - stream << std::endl; - stream << "nextStep:" << std::endl; - stream << " // push send events to external queue" << std::endl; - stream << " if" << std::endl; - stream << " :: len(tmpQ) != 0 -> { tmpQ?e; eQ!e }" << std::endl; - stream << " :: else -> skip;" << std::endl; - stream << " fi;" << std::endl << std::endl; - - stream << " /* pop an event */" << std::endl; - stream << " if" << std::endl; - stream << " :: len(iQ) != 0 -> iQ ? e /* from internal queue */" << std::endl; - stream << " :: else -> eQ ? e /* from external queue */" << std::endl; - stream << " fi;" << std::endl; - stream << " /* event dispatching per state */" << std::endl; - stream << " if" << std::endl; - - writeEventDispatching(stream); - - stream << " :: else -> goto nextStep;" << std::endl; - stream << " fi;" << std::endl; - stream << "terminate: skip;" << std::endl; - - - stream << "}" << std::endl; -} - -void ChartToCPP::writeEventDispatching(std::ostream& stream) { - for (int i = 0; i < _globalStates.size(); i++) { - if (_globalStates[i] == _startState) - continue; - - stream << " :: (s == s" << i << ") -> {" << std::endl; - - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); - writeDispatchingBlock(stream, transitions, 2); - stream << " goto nextStep;" << std::endl; - stream << " }" << std::endl; - } -} - -void ChartToCPP::writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet& transChain, int indent) { - if (transChain.size() == 0) - return; - - std::string padding; - for (int i = 0; i < indent; i++) { - padding += " "; - } - - stream << padding << "if" << std::endl; - stream << padding << ":: ((0"; - - Node currTrans = transChain[0]; - std::string eventDesc = ATTR_CAST(currTrans, "event"); - if (boost::ends_with(eventDesc, "*")) - eventDesc = eventDesc.substr(0, eventDesc.size() - 1); - if (boost::ends_with(eventDesc, ".")) - eventDesc = eventDesc.substr(0, eventDesc.size() - 1); - - if (eventDesc.size() == 0) { - stream << " || 1"; - } else { - std::list trieNodes = _eventTrie.getWordsWithPrefix(eventDesc); - - std::list::iterator trieIter = trieNodes.begin(); - while(trieIter != trieNodes.end()) { - stream << " || e == e" << (*trieIter)->identifier; - trieIter++; - } - } - - stream << ") && "; - stream << (HAS_ATTR_CAST(currTrans, "cond") ? ATTR_CAST(currTrans, "cond") : "1"); - stream << ") -> goto t" << _transitions[Arabica::DOM::Element(currTrans)] << ";" << std::endl; - ; - - stream << padding << ":: else {" << std::endl; - - Arabica::XPath::NodeSet cdrTransChain; - for (int i = 1; i < transChain.size(); i++) { - cdrTransChain.push_back(transChain[i]); - } - writeDispatchingBlock(stream, cdrTransChain, indent + 1); - - stream << padding << " goto nextStep;" << std::endl; - stream << padding << "}" << std::endl; - stream << padding << "fi;" << std::endl; -} - - -void ChartToCPP::writeMain(std::ostream& stream) { - stream << std::endl; - stream << "init {" << std::endl; - stream << " run step();" << std::endl; - stream << "}" << std::endl; - -} - -void ChartToCPP::initNodes() { - // get all states - NodeSet states = filterChildElements(_nsInfo.xmlNSPrefix + "state", _scxml); - for (int i = 0; i < states.size(); i++) { - Arabica::DOM::Element stateElem = Arabica::DOM::Element(states[i]); - _states[ATTR(stateElem, "id")] = stateElem; - if (HAS_ATTR(stateElem, "transient") && DOMUtils::attributeIsTrue(ATTR(stateElem, "transient"))) - continue; - _globalStates.push_back(states[i]); - } - _startState = _states[ATTR(_scxml, "initial")]; - - // initialize event trie with all events that might occur - NodeSet internalEventNames; - internalEventNames.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "transition", _scxml).asNodeSet()); - internalEventNames.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "raise", _scxml).asNodeSet()); - internalEventNames.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "send", _scxml).asNodeSet()); - - for (int i = 0; i < internalEventNames.size(); i++) { - if (HAS_ATTR_CAST(internalEventNames[i], "event")) { - std::string eventNames = ATTR_CAST(internalEventNames[i], "event"); - std::list events = tokenizeIdRefs(eventNames); - for (std::list::iterator eventIter = events.begin(); - eventIter != events.end(); eventIter++) { - std::string eventName = *eventIter; - if (boost::ends_with(eventName, "*")) - eventName = eventName.substr(0, eventName.size() - 1); - if (boost::ends_with(eventName, ".")) - eventName = eventName.substr(0, eventName.size() - 1); - _eventTrie.addWord(eventName); - } - } - } - - // enumerate transitions - NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); - int index = 0; - for (int i = 0; i < transitions.size(); i++) { - _transitions[Arabica::DOM::Element(transitions[i])] = index++; - } -} - -void ChartToCPP::writeProgram(std::ostream& stream) { - - if (!HAS_ATTR(_scxml, "flat") || !DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) { - LOG(ERROR) << "Given SCXML document was not flattened"; - return; - } - - if (!HAS_ATTR(_scxml, "datamodel") || ATTR(_scxml, "datamodel") != "promela") { - LOG(ERROR) << "Can only convert SCXML documents with \"promela\" datamodel"; - return; - } - - if (HAS_ATTR(_scxml, "binding") && ATTR(_scxml, "binding") != "early") { - LOG(ERROR) << "Can only convert for early data bindings"; - return; - } - - initNodes(); - - writeEvents(stream); - stream << std::endl; - writeStates(stream); - stream << std::endl; - writeDeclarations(stream); - stream << std::endl; - writeFSM(stream); - stream << std::endl; - writeMain(stream); - stream << std::endl; - -} - -} \ No newline at end of file diff --git a/src/uscxml/transform/ChartToCPP.cpp.todo b/src/uscxml/transform/ChartToCPP.cpp.todo new file mode 100644 index 0000000..6b78374 --- /dev/null +++ b/src/uscxml/transform/ChartToCPP.cpp.todo @@ -0,0 +1,546 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include "uscxml/transform/ChartToFSM.h" +#include "uscxml/transform/ChartToCPP.h" +#include +#include +#include "uscxml/UUID.h" +#include +#include +#include + +namespace uscxml { + +using namespace Arabica::DOM; +using namespace Arabica::XPath; + +void ChartToCPP::writeProgram(std::ostream& stream, + const Interpreter& interpreter) { + ChartToCPP promelaWriter; + promelaWriter.cloneFrom(interpreter.getImpl()); + promelaWriter.writeProgram(stream); +} + +ChartToCPP::ChartToCPP() : _eventTrie(".") { +} + +void ChartToCPP::writeEvents(std::ostream& stream) { + std::list eventNames = _eventTrie.getWordsWithPrefix(""); + std::list::iterator eventIter = eventNames.begin(); + stream << "// event name identifiers" << std::endl; + while(eventIter != eventNames.end()) { + stream << "#define " << "e" << (*eventIter)->identifier << " " << (*eventIter)->identifier; + stream << " // from \"" << (*eventIter)->value << "\"" << std::endl; + eventIter++; + } +} + +void ChartToCPP::writeStates(std::ostream& stream) { + stream << "// state name identifiers" << std::endl; + for (int i = 0; i < _globalStates.size(); i++) { + stream << "#define " << "s" << i << " " << i; + stream << " // from \"" << ATTR_CAST(_globalStates[i], "id") << "\"" << std::endl; + } + +} + +Arabica::XPath::NodeSet ChartToCPP::getTransientContent(const Arabica::DOM::Element& state) { + Arabica::XPath::NodeSet content; + Arabica::DOM::Element currState = state; + for (;;) { + if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) + break; + content.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "invoke", currState)); + content.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onentry", currState)); + content.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onexit", currState)); + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); + currState = _states[ATTR_CAST(transitions[0], "target")]; + } + + return content; +} + +Node ChartToCPP::getUltimateTarget(const Arabica::DOM::Element& transition) { + Arabica::DOM::Element currState = _states[ATTR(transition, "target")]; + + for (;;) { + if (!HAS_ATTR(currState, "transient") || !DOMUtils::attributeIsTrue(ATTR(currState, "transient"))) + return currState; + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", currState); + currState = _states[ATTR_CAST(transitions[0], "target")]; + } +} + +void ChartToCPP::writeInlineComment(std::ostream& stream, const Arabica::DOM::Element& node) { + if (node.getNodeType() != Node_base::COMMENT_NODE) + return; + + std::string comment = node.getNodeValue(); + boost::trim(comment); + if (!boost::starts_with(comment, "promela-inline:")) + return; + + std::stringstream ssLine(comment); + std::string line; + std::getline(ssLine, line); // consume first line + while(std::getline(ssLine, line)) { + if (line.length() == 0) + continue; + stream << line; + } +} + +void ChartToCPP::writeExecutableContent(std::ostream& stream, const Arabica::DOM::Element& node, int indent) { + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + if (node.getNodeType() == Node_base::COMMENT_NODE) { + std::string comment = node.getNodeValue(); + boost::trim(comment); + std::stringstream inlinePromela; + if (!boost::starts_with(comment, "promela-inline:")) + return; + std::stringstream ssLine(comment); + std::string line; + std::getline(ssLine, line); // consume first line + while(std::getline(ssLine, line)) { + if (line.length() == 0) + continue; + inlinePromela << line << std::endl; + } + stream << padding << "skip;" << std::endl; + stream << beautifyIndentation(inlinePromela.str(), indent) << std::endl; + } + + if (node.getNodeType() != Node_base::ELEMENT_NODE) + return; + + if (false) { + } else if(TAGNAME(node) == "state") { + if (HAS_ATTR(node, "transient") && DOMUtils::attributeIsTrue(ATTR(node, "transient"))) { + Arabica::XPath::NodeSet execContent = getTransientContent(node); + for (int i = 0; i < execContent.size(); i++) { + writeExecutableContent(stream, Arabica::DOM::Element(execContent[i]), indent); + } + } else { + Arabica::DOM::Node child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, Arabica::DOM::Element(child), indent); + child = child.getNextSibling(); + } + } + } else if(TAGNAME(node) == "transition") { + stream << "t" << _transitions[node] << ":" << std::endl; + + stream << padding << "atomic {" << std::endl; + writeExecutableContent(stream, _states[ATTR(node, "target")], indent+1); + stream << padding << " skip;" << std::endl; + + Node newState = getUltimateTarget(node); + for (int i = 0; i < _globalStates.size(); i++) { + if (newState != _globalStates[i]) + continue; + stream << padding << " s = s" << i << ";" << std::endl; + } + + stream << padding << "}" << std::endl; + if (isFinal(Element(newState))) { + stream << padding << "goto terminate;" << std::endl; + } else { + stream << padding << "goto nextStep;" << std::endl; + } + + } else if(TAGNAME(node) == "onentry" || TAGNAME(node) == "onexit") { + Arabica::DOM::Node child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, Arabica::DOM::Element(child), indent); + child = child.getNextSibling(); + } + + } else if(TAGNAME(node) == "script") { + NodeSet scriptText = filterChildType(Node_base::TEXT_NODE, node, true); + for (int i = 0; i < scriptText.size(); i++) { + stream << beautifyIndentation(scriptText[i].getNodeValue(), indent) << std::endl; + } + + } else if(TAGNAME(node) == "log") { + // ignore + + } else if(TAGNAME(node) == "foreach") { + if (HAS_ATTR(node, "index")) + stream << padding << ATTR(node, "index") << " = 0;" << std::endl; + stream << padding << "for (" << ATTR(node, "item") << " in " << ATTR(node, "array") << ") {" << std::endl; + Arabica::DOM::Node child = node.getFirstChild(); + while(child) { + writeExecutableContent(stream, Arabica::DOM::Element(child), indent + 1); + child = child.getNextSibling(); + } + if (HAS_ATTR(node, "index")) + stream << padding << " " << ATTR(node, "index") << "++;" << std::endl; + stream << padding << "}" << std::endl; + + } else if(TAGNAME(node) == "if") { + NodeSet condChain; + condChain.push_back(node); + condChain.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "elseif", node)); + condChain.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "else", node)); + + writeIfBlock(stream, condChain, indent); + + } else if(TAGNAME(node) == "raise") { + TrieNode* trieNode = _eventTrie.getNodeWithPrefix(ATTR(node, "event")); + stream << padding << "iQ!e" << trieNode->identifier << ";" << std::endl; + } else if(TAGNAME(node) == "send") { + if (!HAS_ATTR(node, "target")) { + // this is for our external queue + TrieNode* trieNode = _eventTrie.getNodeWithPrefix(ATTR(node, "event")); + stream << padding << "tmpQ!e" << trieNode->identifier << ";" << std::endl; + } + } else if(TAGNAME(node) == "invoke") { + } else if(TAGNAME(node) == "uninvoke") { + stream << padding << ATTR(node, "invokeid") << "EventSourceDone" << "= 1;" << std::endl; + } else { + + std::cerr << "'" << TAGNAME(node) << "'" << std::endl << node << std::endl; + assert(false); + } + +} + +void ChartToCPP::writeIfBlock(std::ostream& stream, const Arabica::XPath::NodeSet& condChain, int indent) { + if (condChain.size() == 0) + return; + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + bool noNext = condChain.size() == 1; + bool nextIsElse = false; + if (condChain.size() > 1) { + if (TAGNAME_CAST(condChain[1]) == "else") { + nextIsElse = true; + } + } + + Node ifNode = condChain[0]; + + stream << padding << "if" << std::endl; + // we need to nest the elseifs to resolve promela if semantics + stream << padding << ":: (" << ATTR_CAST(ifNode, "cond") << ") -> {" << std::endl; + + Arabica::DOM::Node child; + if (TAGNAME_CAST(ifNode) == "if") { + child = ifNode.getFirstChild(); + } else { + child = ifNode.getNextSibling(); + } + while(child) { + if (child.getNodeType() == Node_base::ELEMENT_NODE) { + if (TAGNAME_CAST(child) == "elseif" || TAGNAME_CAST(child) == "else") + break; + } + writeExecutableContent(stream, Arabica::DOM::Element(child), indent + 1); + child = child.getNextSibling(); + } + stream << padding << "}" << std::endl; + stream << padding << ":: else -> "; + + if (nextIsElse) { + child = condChain[1].getNextSibling(); + stream << "{" << std::endl; + while(child) { + writeExecutableContent(stream, Arabica::DOM::Element(child), indent + 1); + child = child.getNextSibling(); + } + stream << padding << "}" << std::endl; + + } else if (noNext) { + stream << "skip;" << std::endl; + } else { + stream << "{" << std::endl; + + Arabica::XPath::NodeSet cdrCondChain; + for (int i = 1; i < condChain.size(); i++) { + cdrCondChain.push_back(condChain[i]); + } + writeIfBlock(stream, cdrCondChain, indent + 1); + stream << padding << "}" << std::endl; + + } + + stream << padding << "fi;" << std::endl; + +} + +std::string ChartToCPP::beautifyIndentation(const std::string& code, int indent) { + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + // remove topmost indentation from every line and reindent + std::stringstream beautifiedSS; + + std::string initialIndent; + bool gotIndent = false; + bool isFirstLine = true; + std::stringstream ssLine(code); + std::string line; + + while(std::getline(ssLine, line)) { + size_t firstChar = line.find_first_not_of(" \t\r\n"); + if (firstChar != std::string::npos) { + if (!gotIndent) { + initialIndent = line.substr(0, firstChar); + gotIndent = true; + } + beautifiedSS << (isFirstLine ? "" : "\n") << padding << boost::replace_first_copy(line, initialIndent, ""); + isFirstLine = false; + } + } + + return beautifiedSS.str(); +} + +void ChartToCPP::writeDeclarations(std::ostream& stream) { + + // get all data elements + NodeSet datas = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); + NodeSet dataText = filterChildType(Node_base::TEXT_NODE, datas, true); + + // write their text content + stream << "// datamodel variables" << std::endl; + for (int i = 0; i < dataText.size(); i++) { + Node data = dataText[i]; + stream << beautifyIndentation(data.getNodeValue()) << std::endl; + } + + stream << std::endl; + stream << "// global variables" << std::endl; + stream << "int e; /* current event */" << std::endl; + stream << "int s; /* current state */" << std::endl; + stream << "chan iQ = [100] of {int} /* internal queue */" << std::endl; + stream << "chan eQ = [100] of {int} /* external queue */" << std::endl; + stream << "chan tmpQ = [100] of {int} /* temporary queue for external events in transitions */" << std::endl; + stream << "int tmpQItem;" << std::endl; + + stream << std::endl; + stream << "// event sources" << std::endl; + +} + +void ChartToCPP::writeFSM(std::ostream& stream) { + NodeSet transitions; + + stream << "proctype step() {" << std::endl; + // write initial transition + transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _startState); + assert(transitions.size() == 1); + stream << " // transition's executable content" << std::endl; + writeExecutableContent(stream, Arabica::DOM::Element(transitions[0]), 1); + + for (int i = 0; i < _globalStates.size(); i++) { + if (_globalStates[i] == _startState) + continue; + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); + for (int j = 0; j < transitions.size(); j++) { + writeExecutableContent(stream, Arabica::DOM::Element(transitions[j]), 1); + } + } + + stream << std::endl; + stream << "nextStep:" << std::endl; + stream << " // push send events to external queue" << std::endl; + stream << " if" << std::endl; + stream << " :: len(tmpQ) != 0 -> { tmpQ?e; eQ!e }" << std::endl; + stream << " :: else -> skip;" << std::endl; + stream << " fi;" << std::endl << std::endl; + + stream << " /* pop an event */" << std::endl; + stream << " if" << std::endl; + stream << " :: len(iQ) != 0 -> iQ ? e /* from internal queue */" << std::endl; + stream << " :: else -> eQ ? e /* from external queue */" << std::endl; + stream << " fi;" << std::endl; + stream << " /* event dispatching per state */" << std::endl; + stream << " if" << std::endl; + + writeEventDispatching(stream); + + stream << " :: else -> goto nextStep;" << std::endl; + stream << " fi;" << std::endl; + stream << "terminate: skip;" << std::endl; + + + stream << "}" << std::endl; +} + +void ChartToCPP::writeEventDispatching(std::ostream& stream) { + for (int i = 0; i < _globalStates.size(); i++) { + if (_globalStates[i] == _startState) + continue; + + stream << " :: (s == s" << i << ") -> {" << std::endl; + + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _globalStates[i]); + writeDispatchingBlock(stream, transitions, 2); + stream << " goto nextStep;" << std::endl; + stream << " }" << std::endl; + } +} + +void ChartToCPP::writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet& transChain, int indent) { + if (transChain.size() == 0) + return; + + std::string padding; + for (int i = 0; i < indent; i++) { + padding += " "; + } + + stream << padding << "if" << std::endl; + stream << padding << ":: ((0"; + + Node currTrans = transChain[0]; + std::string eventDesc = ATTR_CAST(currTrans, "event"); + if (boost::ends_with(eventDesc, "*")) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + if (boost::ends_with(eventDesc, ".")) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + + if (eventDesc.size() == 0) { + stream << " || 1"; + } else { + std::list trieNodes = _eventTrie.getWordsWithPrefix(eventDesc); + + std::list::iterator trieIter = trieNodes.begin(); + while(trieIter != trieNodes.end()) { + stream << " || e == e" << (*trieIter)->identifier; + trieIter++; + } + } + + stream << ") && "; + stream << (HAS_ATTR_CAST(currTrans, "cond") ? ATTR_CAST(currTrans, "cond") : "1"); + stream << ") -> goto t" << _transitions[Arabica::DOM::Element(currTrans)] << ";" << std::endl; + ; + + stream << padding << ":: else {" << std::endl; + + Arabica::XPath::NodeSet cdrTransChain; + for (int i = 1; i < transChain.size(); i++) { + cdrTransChain.push_back(transChain[i]); + } + writeDispatchingBlock(stream, cdrTransChain, indent + 1); + + stream << padding << " goto nextStep;" << std::endl; + stream << padding << "}" << std::endl; + stream << padding << "fi;" << std::endl; +} + + +void ChartToCPP::writeMain(std::ostream& stream) { + stream << std::endl; + stream << "init {" << std::endl; + stream << " run step();" << std::endl; + stream << "}" << std::endl; + +} + +void ChartToCPP::initNodes() { + // get all states + NodeSet states = filterChildElements(_nsInfo.xmlNSPrefix + "state", _scxml); + for (int i = 0; i < states.size(); i++) { + Arabica::DOM::Element stateElem = Arabica::DOM::Element(states[i]); + _states[ATTR(stateElem, "id")] = stateElem; + if (HAS_ATTR(stateElem, "transient") && DOMUtils::attributeIsTrue(ATTR(stateElem, "transient"))) + continue; + _globalStates.push_back(states[i]); + } + _startState = _states[ATTR(_scxml, "initial")]; + + // initialize event trie with all events that might occur + NodeSet internalEventNames; + internalEventNames.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "transition", _scxml).asNodeSet()); + internalEventNames.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "raise", _scxml).asNodeSet()); + internalEventNames.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "send", _scxml).asNodeSet()); + + for (int i = 0; i < internalEventNames.size(); i++) { + if (HAS_ATTR_CAST(internalEventNames[i], "event")) { + std::string eventNames = ATTR_CAST(internalEventNames[i], "event"); + std::list events = tokenizeIdRefs(eventNames); + for (std::list::iterator eventIter = events.begin(); + eventIter != events.end(); eventIter++) { + std::string eventName = *eventIter; + if (boost::ends_with(eventName, "*")) + eventName = eventName.substr(0, eventName.size() - 1); + if (boost::ends_with(eventName, ".")) + eventName = eventName.substr(0, eventName.size() - 1); + _eventTrie.addWord(eventName); + } + } + } + + // enumerate transitions + NodeSet transitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); + int index = 0; + for (int i = 0; i < transitions.size(); i++) { + _transitions[Arabica::DOM::Element(transitions[i])] = index++; + } +} + +void ChartToCPP::writeProgram(std::ostream& stream) { + + if (!HAS_ATTR(_scxml, "flat") || !DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) { + LOG(ERROR) << "Given SCXML document was not flattened"; + return; + } + + if (!HAS_ATTR(_scxml, "datamodel") || ATTR(_scxml, "datamodel") != "promela") { + LOG(ERROR) << "Can only convert SCXML documents with \"promela\" datamodel"; + return; + } + + if (HAS_ATTR(_scxml, "binding") && ATTR(_scxml, "binding") != "early") { + LOG(ERROR) << "Can only convert for early data bindings"; + return; + } + + initNodes(); + + writeEvents(stream); + stream << std::endl; + writeStates(stream); + stream << std::endl; + writeDeclarations(stream); + stream << std::endl; + writeFSM(stream); + stream << std::endl; + writeMain(stream); + stream << std::endl; + +} + +} \ No newline at end of file diff --git a/src/uscxml/transform/ChartToCPP.h b/src/uscxml/transform/ChartToCPP.h deleted file mode 100644 index 8cdebb9..0000000 --- a/src/uscxml/transform/ChartToCPP.h +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @file - * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) - * @copyright Simplified BSD - * - * @cond - * This program is free software: you can redistribute it and/or modify - * it under the terms of the FreeBSD license as published by the FreeBSD - * project. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * - * You should have received a copy of the FreeBSD license along with this - * program. If not, see . - * @endcond - */ - -#ifndef FSMTOCPP_H_201672B0 -#define FSMTOCPP_H_201672B0 - -#include "uscxml/interpreter/InterpreterDraft6.h" -#include "uscxml/DOMUtils.h" -#include "uscxml/util/Trie.h" - -#include -#include -#include -#include - -namespace uscxml { - -class USCXML_API ChartToCPP : public InterpreterDraft6 { -public: - static void writeProgram(std::ostream& stream, - const Interpreter& interpreter); - - static std::string beautifyIndentation(const std::string& code, int indent = 0); - -protected: - ChartToCPP(); - void writeProgram(std::ostream& stream); - - void initNodes(); - - void writeEvents(std::ostream& stream); - void writeStates(std::ostream& stream); - void writeDeclarations(std::ostream& stream); - void writeExecutableContent(std::ostream& stream, const Arabica::DOM::Element& node, int indent = 0); - void writeInlineComment(std::ostream& stream, const Arabica::DOM::Element& node); - void writeFSM(std::ostream& stream); - void writeEventDispatching(std::ostream& stream); - void writeMain(std::ostream& stream); - - void writeIfBlock(std::ostream& stream, const Arabica::XPath::NodeSet& condChain, int indent = 0); - void writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet& transChain, int indent = 0); - - Arabica::XPath::NodeSet getTransientContent(const Arabica::DOM::Element& state); - Arabica::DOM::Node getUltimateTarget(const Arabica::DOM::Element& transition); - - Trie _eventTrie; - Arabica::XPath::NodeSet _globalStates; - Arabica::DOM::Element _startState; - std::map > _states; - std::map, int> _transitions; - -}; - -} - -#endif /* end of include guard: FSMTOCPP_H_201672B0 */ diff --git a/src/uscxml/transform/ChartToCPP.h.todo b/src/uscxml/transform/ChartToCPP.h.todo new file mode 100644 index 0000000..8cdebb9 --- /dev/null +++ b/src/uscxml/transform/ChartToCPP.h.todo @@ -0,0 +1,72 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#ifndef FSMTOCPP_H_201672B0 +#define FSMTOCPP_H_201672B0 + +#include "uscxml/interpreter/InterpreterDraft6.h" +#include "uscxml/DOMUtils.h" +#include "uscxml/util/Trie.h" + +#include +#include +#include +#include + +namespace uscxml { + +class USCXML_API ChartToCPP : public InterpreterDraft6 { +public: + static void writeProgram(std::ostream& stream, + const Interpreter& interpreter); + + static std::string beautifyIndentation(const std::string& code, int indent = 0); + +protected: + ChartToCPP(); + void writeProgram(std::ostream& stream); + + void initNodes(); + + void writeEvents(std::ostream& stream); + void writeStates(std::ostream& stream); + void writeDeclarations(std::ostream& stream); + void writeExecutableContent(std::ostream& stream, const Arabica::DOM::Element& node, int indent = 0); + void writeInlineComment(std::ostream& stream, const Arabica::DOM::Element& node); + void writeFSM(std::ostream& stream); + void writeEventDispatching(std::ostream& stream); + void writeMain(std::ostream& stream); + + void writeIfBlock(std::ostream& stream, const Arabica::XPath::NodeSet& condChain, int indent = 0); + void writeDispatchingBlock(std::ostream& stream, const Arabica::XPath::NodeSet& transChain, int indent = 0); + + Arabica::XPath::NodeSet getTransientContent(const Arabica::DOM::Element& state); + Arabica::DOM::Node getUltimateTarget(const Arabica::DOM::Element& transition); + + Trie _eventTrie; + Arabica::XPath::NodeSet _globalStates; + Arabica::DOM::Element _startState; + std::map > _states; + std::map, int> _transitions; + +}; + +} + +#endif /* end of include guard: FSMTOCPP_H_201672B0 */ diff --git a/src/uscxml/transform/ChartToFSM.cpp b/src/uscxml/transform/ChartToFSM.cpp index 8597211..38262db 100644 --- a/src/uscxml/transform/ChartToFSM.cpp +++ b/src/uscxml/transform/ChartToFSM.cpp @@ -36,21 +36,22 @@ #define UNDECIDABLE 2147483647 #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) -#define DUMP_STATS(nrTrans) \ +#define DUMP_STATS(nrTrans, disregardTime) \ uint64_t now = tthread::chrono::system_clock::now(); \ -if (now - _lastTimeStamp > 1000) { \ +if (now - _lastTimeStamp > 1000 || disregardTime) { \ std::cerr << "## Transition: " << _perfTransUsed << " / " << _perfTransTotal << " [" << _perfTransProcessed << "/sec]"; \ if (nrTrans > 0) { \ std::cerr << " - 2**" << nrTrans << " = " << pow(2.0, static_cast(nrTrans)); \ } \ std::cerr << std::endl; \ - std::cerr << "## State : " << _globalConf.size() << " [" << _perfStatesProcessed << "/sec]" << std::endl; \ + std::cerr << "## State : " << _globalConf.size() << " found / " << _perfStackSize << " stacked / " << _perfStatesTotal << " seen [" << _perfStatesProcessed << "/sec]" << std::endl; \ std::cerr << "## Microstep : " << _perfMicroStepTotal << " [" << _perfMicroStepProcessed << "/sec]" << std::endl; \ std::cerr << "## Cached : " << _perfStatesCachedTotal << " [" << _perfStatesCachedProcessed << "/sec]" << std::endl; \ std::cerr << "## Skipped : " << _perfStatesSkippedTotal << " [" << _perfStatesSkippedProcessed << "/sec]" << std::endl; \ std::cerr << "## Queues : " << (_maxEventRaisedChain == UNDECIDABLE ? "UNK" : toStr(_maxEventRaisedChain)) << " / " << (_maxEventSentChain == UNDECIDABLE ? "UNK" : toStr(_maxEventSentChain)) << std::endl; \ + std::cerr << "toFSM: "; \ std::cerr << _perfTransUsed << ", " << _perfTransTotal << ", " << _perfTransProcessed << ", "; \ - std::cerr << _globalConf.size() << ", " << _perfStatesProcessed << ", "; \ + std::cerr << _globalConf.size() << ", " << _perfStackSize << ", " << _perfStatesTotal << ", " << _perfStatesProcessed << ", "; \ std::cerr << _perfMicroStepTotal << ", " << _perfMicroStepProcessed << ", "; \ std::cerr << _perfStatesCachedTotal << ", " << _perfStatesCachedProcessed << ", " << _perfStatesSkippedTotal << ", " << _perfStatesSkippedProcessed << ", "; \ std::cerr << (_maxEventRaisedChain == UNDECIDABLE ? "UNK" : toStr(_maxEventRaisedChain)) << ", " << (_maxEventSentChain == UNDECIDABLE ? "UNK" : toStr(_maxEventSentChain)) << std::endl; \ @@ -60,7 +61,8 @@ if (now - _lastTimeStamp > 1000) { \ _perfStatesCachedProcessed = 0; \ _perfStatesSkippedProcessed = 0; \ _perfMicroStepProcessed = 0; \ - _lastTimeStamp = now; \ + if (!disregardTime)\ + _lastTimeStamp = now; \ } //std::cerr << "Q: " << (_maxEventRaisedChain == UNDECIDABLE ? "UNK" : toStr(_maxEventRaisedChain)) << " / " << (_maxEventSentChain == UNDECIDABLE ? "UNK" : toStr(_maxEventSentChain)) << std::endl; @@ -89,6 +91,83 @@ for (int i = 0; i < contents.size(); i++) { \ } \ std::cerr << ")"; +std::list > > Complexity::getAllConfigurations(const Arabica::DOM::Element& root) { + + std::list > > allConfigurations; + std::string nsPrefix = (root.getPrefix().size() > 0 ? root.getPrefix() + ":" : ""); + std::string localName = root.getLocalName(); + bool isAtomic = true; + + NodeList children = root.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + if (children.item(i).getNodeType() != Node_base::ELEMENT_NODE) + continue; + Element childElem(children.item(i)); + if (childElem.getTagName() == nsPrefix + "state" || + childElem.getTagName() == nsPrefix + "parallel" || + childElem.getTagName() == nsPrefix + "final") { + // nested child state + std::list > > nestedConfigurations = getAllConfigurations(childElem); + isAtomic = false; + if (localName == "parallel" && allConfigurations.size() > 0) { + // for every nested configuration, every new nested is valid + std::list > > combinedConfigurations; + for (std::list > >::iterator existIter = allConfigurations.begin(); existIter != allConfigurations.end(); existIter++) { + std::set > existingConfig = *existIter; + + for (std::list > >::iterator newIter = nestedConfigurations.begin(); newIter != nestedConfigurations.end(); newIter++) { + + std::set > newConfig = *newIter; + std::set > combinedSet; + combinedSet.insert(existingConfig.begin(), existingConfig.end()); + combinedSet.insert(newConfig.begin(), newConfig.end()); + + combinedConfigurations.push_back(combinedSet); + } + } + allConfigurations = combinedConfigurations; + } else { + // just add nested configurations and this + for (std::list > >::iterator newIter = nestedConfigurations.begin(); newIter != nestedConfigurations.end(); newIter++) { + std::set > newConfig = *newIter; + if (localName != "scxml") + newConfig.insert(root); + allConfigurations.push_back(newConfig); + } + } + } + } + + if (isAtomic) { + std::set > soleConfig; + soleConfig.insert(root); + allConfigurations.push_back(soleConfig); + } + return allConfigurations; +} + +std::map Complexity::getTransitionHistogramm(const Arabica::DOM::Element& root) { + std::map histogram; + std::string nameSpace; + + std::list > > allConfig = Complexity::getAllConfigurations(root); + + // for every legal configuration, count the transitions + for (std::list > >::iterator confIter = allConfig.begin(); confIter != allConfig.end(); confIter++) { + NodeSet configNodeSet; + std::set > config = *confIter; + for (std::set >::iterator elemIter = config.begin(); elemIter != config.end(); elemIter++) { + configNodeSet.push_back(*elemIter); + if (nameSpace.size() == 0 && elemIter->getPrefix().size() > 0) + nameSpace = elemIter->getPrefix() + ":"; + } + NodeSet transitions = InterpreterImpl::filterChildElements(nameSpace + "transition", configNodeSet); + histogram[transitions.size()]++; + } + + return histogram; +} + uint64_t Complexity::stateMachineComplexity(const Arabica::DOM::Element& root, Variant variant) { Complexity complexity = calculateStateMachineComplexity(root); @@ -183,11 +262,15 @@ ChartToFSM::ChartToFSM(const Interpreter& other) { cloneFrom(other.getImpl()); + _transitionsFromTree = true; + _keepInvalidTransitions = false; _lastTimeStamp = tthread::chrono::system_clock::now(); _perfTransProcessed = 0; _perfTransTotal = 0; _perfTransUsed = 0; + _perfStatesTotal = 0; _perfStatesProcessed = 0; + _perfStackSize = 0; _perfStatesSkippedProcessed = 0; _perfStatesSkippedTotal = 0; _perfStatesCachedProcessed = 0; @@ -195,9 +278,13 @@ ChartToFSM::ChartToFSM(const Interpreter& other) { _perfMicroStepProcessed = 0; _perfMicroStepTotal = 0; + if (envVarIEquals("USCXML_TRANSFORM_TRANS_FROM", "powerset")) + _transitionsFromTree = false; + _start = NULL; _currGlobalTransition = NULL; - + _transTree = NULL; + _lastStateIndex = 0; _lastActiveIndex = 0; _lastTransIndex = 0; @@ -207,10 +294,6 @@ ChartToFSM::ChartToFSM(const Interpreter& other) { _doneEventRaiseTolerance = 0; _skipEventChainCalculations = false; - // create a _flatDoc for the FSM - DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); - _flatDoc = domFactory.createDocument(other.getDocument().getNamespaceURI(), "", 0); - addMonitor(this); } @@ -235,14 +318,38 @@ ChartToFSM::~ChartToFSM() { } Document ChartToFSM::getDocument() const { - return _flatDoc; + if (_flatDoc) + return _flatDoc; + return _document; } InterpreterState ChartToFSM::interpret() { + // create a _flatDoc for the FSM + DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); + _flatDoc = domFactory.createDocument(_document.getNamespaceURI(), "", 0); + init(); setupIOProcessors(); + { + std::list > > allConfig = Complexity::getAllConfigurations(_scxml); + for (std::list > >::iterator confIter = allConfig.begin(); confIter != allConfig.end(); confIter++) { + std::string seperator; + NodeSet configNodeSet; + std::set > config = *confIter; + for (std::set >::iterator elemIter = config.begin(); elemIter != config.end(); elemIter++) { +// std::cerr << seperator << ATTR((*elemIter), "id"); + seperator = ","; + configNodeSet.push_back(*elemIter); + } + assert(isLegalConfiguration(configNodeSet)); +// std::cerr << std::endl; + } + } + std::map histoGramm = Complexity::getTransitionHistogramm(_scxml); +// abort(); + uint64_t complexity = Complexity::stateMachineComplexity(_scxml) + 1; std::cerr << "Approximate Complexity: " << complexity << std::endl; std::cerr << "Approximate Active Complexity: " << Complexity::stateMachineComplexity(_scxml, Complexity::IGNORE_HISTORY_AND_NESTED_DATA) + 1 << std::endl; @@ -288,7 +395,7 @@ InterpreterState ChartToFSM::interpret() { } _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); - _alreadyFlat = (HAS_ATTR(_scxml, "flat") && DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))); + _alreadyFlat = (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat"))); if (_alreadyFlat) { reassembleFromFlat(); @@ -333,10 +440,7 @@ InterpreterState ChartToFSM::interpret() { // std::cout << _scxml << std::endl; - indexTransitions(_scxml); - - // reverse indices for most prior to be in front - std::reverse(indexedTransitions.begin(), indexedTransitions.end()); + indexTransitions(); // add initial transitions as least prior for (int i = 0; i < initialTransitions.size() ; i++) { @@ -345,6 +449,8 @@ InterpreterState ChartToFSM::interpret() { // set index attribute for transitions for (int i = 0; i < indexedTransitions.size(); i++) { +// std::cerr << toStr(i) << ":" << (HAS_ATTR(indexedTransitions[i], "line_start") ? ATTR(indexedTransitions[i], "line_start") : ""); +// std::cerr << "\t" << DOMUtils::xPathForNode(indexedTransitions[i]) << std::endl; indexedTransitions[i].setAttribute("index", toStr(i)); } @@ -385,6 +491,8 @@ InterpreterState ChartToFSM::interpret() { explode(); + DUMP_STATS(0, true); + #if 0 // print set of global configurations for(std::map::iterator globalConfIter = _globalConf.begin(); @@ -400,8 +508,8 @@ InterpreterState ChartToFSM::interpret() { std::cerr << "Internal Queue: " << _maxEventRaisedChain << std::endl; std::cerr << "External Queue: " << _maxEventSentChain << std::endl; - if (complexity < _globalConf.size()) - throw std::runtime_error("Upper bound for states exceeded"); +// if (complexity < _globalConf.size()) +// throw std::runtime_error("Upper bound for states exceeded"); return _state; } @@ -476,6 +584,7 @@ void ChartToFSM::internalDoneSend(const Arabica::DOM::Element& stat if (parentIsScxmlState(state)) return; +// return; // std::cerr << "internalDoneSend: " << state << std::endl; // create onentry with a raise element @@ -505,7 +614,7 @@ void ChartToFSM::internalDoneSend(const Arabica::DOM::Element& stat raise.setAttribute("event", "done.state." + ATTR_CAST(state.getParentNode(), "id")); // parent?! GlobalTransition::Action action; - action.onEntry = onentry; + action.raiseDone = onentry; // HERE! _currGlobalTransition->actions.push_back(action); if (!_skipEventChainCalculations && @@ -517,10 +626,10 @@ void ChartToFSM::internalDoneSend(const Arabica::DOM::Element& stat static bool isSuperset(const GlobalTransition* t1, const GlobalTransition* t2) { bool isSuperset = true; - + if (t1->transitionRefs.size() >= t2->transitionRefs.size()) return false; - + NodeSet t1Trans = t1->getTransitions(); NodeSet t2Trans = t2->getTransitions(); @@ -551,6 +660,25 @@ bool ChartToFSM::filterSameState(const NodeSet& transitions) { return true; } +static bool filterSameHierarchy(const NodeSet& transitions) { + for (unsigned int i = 0; i < transitions.size(); i++) { + Node t1 = transitions[i]; + Node p1 = InterpreterImpl::getParentState(t1); + for (unsigned int j = i + 1; j < transitions.size(); j++) { + Node t2 = transitions[j]; + Node p2 = InterpreterImpl::getParentState(t2); + while(p2) { + if (p1 == p2) { + return false; + } + p2 = p2.getParentNode(); + } + } + } + return true; +} + + static bool filterChildEnabled(const NodeSet& transitions) { // drop any transition that is already enabled by a child NodeSet filteredTransitions; @@ -634,6 +762,19 @@ void ChartToFSM::annotateRaiseAndSend(const Arabica::DOM::Element& } } +void ChartToFSM::indexTransitions() { + indexTransitions(_scxml); + + size_t index = 0; + for (std::vector >::iterator transIter = indexedTransitions.begin(); transIter != indexedTransitions.end(); transIter++) { + transIter->setAttribute("priority", toStr(index)); + index++; + } + + // reverse indices for most prior to be in front + std::reverse(indexedTransitions.begin(), indexedTransitions.end()); +} + void ChartToFSM::indexTransitions(const Arabica::DOM::Element& root) { // breadth first traversal of transitions Arabica::XPath::NodeSet levelTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", root); @@ -676,7 +817,7 @@ template bool PtrComp(const T * const & a, const T * const & b) { /** * subset only removes transitions without cond -> superset will always be enabled */ -bool hasUnconditionalSuperset (GlobalTransition* first, GlobalTransition* second) { +bool hasUnconditionalSuperset(GlobalTransition* first, GlobalTransition* second) { NodeSet firstTransitions = first->getTransitions(); NodeSet secondTransitions = first->getTransitions(); @@ -694,6 +835,9 @@ bool hasUnconditionalSuperset (GlobalTransition* first, GlobalTransition* second return false; //second can't be removed } +/** + * earlier transition is conditionless for same event + */ bool hasEarlierUnconditionalMatch(GlobalTransition* first, GlobalTransition* second) { if (first->eventDesc == second->eventDesc) { if (first->condition.size() == 0) @@ -702,9 +846,43 @@ bool hasEarlierUnconditionalMatch(GlobalTransition* first, GlobalTransition* sec return false; } -// for some reason, unique is not quite up to the task -std::list reapplyUniquePredicates(std::list list) { +std::list redundantRemove(std::list list) { +#if 1 + std::list::iterator outerIter; + std::list::iterator innerIter; + + outerIter = list.begin(); + while(outerIter != list.end()) { + innerIter = outerIter; + + while(innerIter != list.end()) { + if (innerIter == outerIter) { + innerIter++; + continue; + } + + GlobalTransition* t1 = *outerIter; + GlobalTransition* t2 = *innerIter; + + if (hasUnconditionalSuperset(t1, t2)) { + list.erase(innerIter++); + continue; + } else if (hasUnconditionalSuperset(t2, t1)) { + list.erase(outerIter++); + break; + } + if (hasEarlierUnconditionalMatch(t1, t2)) { + list.erase(innerIter++); + continue; + } + innerIter++; + } + + outerIter++; + } +#else + for (std::list::iterator outerIter = list.begin(); outerIter != list.end(); outerIter++) { @@ -719,25 +897,425 @@ std::list reapplyUniquePredicates(std::list redundantMark(std::list list) { +#if 1 + std::list::iterator outerIter; + std::list::iterator innerIter; + outerIter = list.begin(); + while(outerIter != list.end()) { + innerIter = outerIter; + + while(innerIter != list.end()) { + if (innerIter == outerIter) { + innerIter++; + continue; + } + + GlobalTransition* t1 = *outerIter; + GlobalTransition* t2 = *innerIter; + + if (!t1->isValid || !t2->isValid) { + innerIter++; + continue; + } + + if (hasUnconditionalSuperset(t1, t2)) { + t2->isValid = false; + t2->invalidMsg = "Unconditional superset"; + t2->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; + innerIter++; + continue; + } else if (hasUnconditionalSuperset(t2, t1)) { + t1->isValid = false; + t1->invalidMsg = "Unconditional superset"; + t1->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; + outerIter++; + break; + } + if (hasEarlierUnconditionalMatch(t1, t2)) { + t2->isValid = false; + t2->invalidMsg = "Earlier unconditional match"; + t2->invalidReason = GlobalTransition::UNCONDITIONAL_MATCH; + innerIter++; + continue; + } + innerIter++; + } + + outerIter++; + } + +#else + + for (std::list::iterator outerIter = list.begin(); + outerIter != list.end(); + outerIter++) { + for (std::list::iterator innerIter = outerIter; + innerIter != list.end(); + innerIter++) { + + if (innerIter == outerIter) + continue; + + GlobalTransition* t1 = *outerIter; + GlobalTransition* t2 = *innerIter; + + if (!t1->isValid || !t2->isValid) + continue; + + if (hasUnconditionalSuperset(t1, t2)) { + t2->isValid = false; + t2->invalidMsg = "Unconditional superset"; + t2->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; + continue; + } else if (hasUnconditionalSuperset(t2, t1)) { + t1->isValid = false; + t1->invalidMsg = "Unconditional superset"; + t1->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; + continue; + } + if (hasEarlierUnconditionalMatch(t1, t2)) { + t2->isValid = false; + t2->invalidMsg = "Earlier unconditional match"; + t2->invalidReason = GlobalTransition::UNCONDITIONAL_MATCH; + continue; + } + } + } +#endif return list; } -void ChartToFSM::getPotentialTransitionsForConf(const Arabica::XPath::NodeSet& conf, std::map& outMap) { + +void TransitionTreeNode::dump(int indent) { + std::string padding; + for (int i = 0; i + 1 < indent; i++) { + padding += "| "; + } + if (indent > 0) + padding += "|-"; + + std::string typeString; + switch (type) { + case TYPE_NESTED: + typeString = "NESTED"; break; + case TYPE_PARALLEL: + typeString = "PARALLEL"; break; + case TYPE_TRANSITION: + typeString = "TRANSITION"; break; + case TYPE_UNDEFINED: + typeString = "UNDEFINED"; break; + break; + default: + break; + } + + + if (transition) { + std::cerr << padding << "t" << ATTR(transition, "index") << " " << typeString << ": "; +// std::cerr << (prevTransition != NULL ? " (" + prevTransition->nodeId + ") <-" : ""); + std::cerr << "[" << nodeId << "]"; +// std::cerr << (nextTransition != NULL ? " -> (" + nextTransition->nodeId + ")" : ""); + std::cerr << std::endl; + } else { + std::cerr << padding << ATTR(state, "id") << " " << typeString << ": " << "[" << nodeId << "]"; +// std::cerr << (firstTransition != NULL ? " -> " + firstTransition->nodeId : ""); + std::cerr << std::endl; + } + + for (std::list::iterator childIter = children.begin(); childIter != children.end(); childIter++) { + (*childIter)->dump(indent + 1); + } +} + +void ChartToFSM::getPotentialTransitionsForConfFromTree(const Arabica::XPath::NodeSet& conf, std::map& outMap) { + if (_transTree == NULL) { + _transTree = buildTransTree(_scxml, "0"); +// _transTree->dump(); + } + std::string seperator; + + +// std::cerr << "--- "; + + // recursion start + std::set transLeafs; + + for (int i = 0; i < conf.size(); i++) { + DUMP_STATS(conf.size(), false); + + Element confElem(conf[i]); + assert(_stateToTransTreeNode.find(confElem) != _stateToTransTreeNode.end()); + TransitionTreeNode* node = _stateToTransTreeNode[confElem]; + if (node->firstState == NULL) { // a leaf - ignore intermediates + // ascend to the first parent with transitions but stop at parallel nodes + while(node != NULL && node->firstTransition == NULL) { + if (node->parent && node->parent->type == TransitionTreeNode::TYPE_PARALLEL) + break; + node = node->parent; + } + if (node != NULL) { + transLeafs.insert(node); + } else { + //std::cerr << ATTR(confElem, "id") << " does not cause transitions" << std::endl; + } + } + } + + std::list > stack; + stack.push_back(transLeafs); // push follow-up configurations onto stack + + while (stack.size() > 0) { + // pop from front of stack + std::set stateList = stack.front(); + stack.pop_front(); + + DUMP_STATS(conf.size(), false); + +#if 0 + seperator = ""; + std::cerr << "Current set: "; + for (std::set::iterator transIter = stateList.begin(); transIter != stateList.end(); transIter++) { + std::cerr << seperator << (*transIter)->nodeId; + seperator = ", "; + } + std::cerr << std::endl; +#endif + + /* + * TransNodes contains a set of lists of transitions. + * In the inner stack we build every possible combination + * of picking at-most one from each list. + */ + + /* create global transitions for every n-tuple in current set of lists */ + std::list, std::set > > innerStack; + innerStack.push_back(std::make_pair(std::set(), stateList)); + + while(innerStack.size() > 0) { + + // picking at-most one from each list + std::set remainingStates = innerStack.front().second; + std::set fixedTransitions = innerStack.front().first; + innerStack.pop_front(); + + if (remainingStates.size() > 0) { + // iterate for each first element fixed + TransitionTreeNode* firstRemainingState = *remainingStates.begin(); + remainingStates.erase(remainingStates.begin()); + + if (firstRemainingState->firstTransition == NULL) { + // no transitions at this state - reenqueue with NULL selection from this + innerStack.push_back(std::make_pair(fixedTransitions, remainingStates)); + continue; + } + + TransitionTreeNode* currTrans = firstRemainingState->firstTransition; + + // choose none from firstList + innerStack.push_back(std::make_pair(fixedTransitions, remainingStates)); + + while(currTrans != NULL) { + std::set fixedAndThis(fixedTransitions); + fixedAndThis.insert(currTrans); + innerStack.push_back(std::make_pair(fixedAndThis, remainingStates)); + currTrans = currTrans->nextTransition; + } + } else { + DUMP_STATS(conf.size(), false); + + if (fixedTransitions.size() > 0) { + + _perfTransTotal++; + _perfTransProcessed++; + + NodeSet fixed; + +#if 0 + seperator = ""; + for (std::set::iterator itemIter = fixedTransitions.begin(); itemIter != fixedTransitions.end(); itemIter++) { + TransitionTreeNode* currItem = *itemIter; + std::cerr << seperator << currItem->nodeId; + seperator = ", "; + } + std::cerr << " ## "; +#endif + + seperator = ""; + for (std::set::iterator itemIter = fixedTransitions.begin(); itemIter != fixedTransitions.end(); itemIter++) { + TransitionTreeNode* currItem = *itemIter; + fixed.push_back(currItem->transition); +// std::cerr << seperator << ATTR(currItem->transition, "index"); + seperator = ", "; + } +// std::cerr << std::endl; + + // fixed contains a transiton set! + assert(filterSameState(fixed)); +// assert(filterChildEnabled(fixed)); + assert(filterSameHierarchy(fixed)); + // do not add if they preempt + if (fixed.size() != removeConflictingTransitions(fixed).size()) { +// std::cerr << " - PREEMPTS" << std::endl; + continue; + } + + GlobalTransition* transition = new GlobalTransition(fixed, _dataModel, this); + transition->index = _lastTransIndex++; + +// assert(outMap.find(transition->transitionId) == outMap.end()); + + if (!transition->isValid && !_keepInvalidTransitions) { + delete(transition); +// std::cerr << " - INVALID" << std::endl; + continue; + } + + _perfTransUsed++; + + outMap[transition->transitionId] = transition; +// std::cerr << " - GOOD" << std::endl; + } + } + } + + // create new set of transition lists by moving to parent states + for (std::set::iterator stateIter = stateList.begin(); stateIter != stateList.end(); stateIter++) { + TransitionTreeNode* origState = *stateIter; + TransitionTreeNode* currState = origState; + TransitionTreeNode* parentState = currState->parent; + + /** + * We ascend the current state via its parent and add the parent with transitions. + * However, we break if we reached the top or if we passed a parallel state for + * wich we are not the first child + */ + + while(parentState != NULL) { + if (parentState->type == TransitionTreeNode::TYPE_PARALLEL && parentState->firstState != currState) { + // the first child of the parallel state will continue this transition - we made sure to keep them + break; + } + + if (parentState->firstTransition != NULL) { +// std::cerr << "#### Adding new parent lists for " << origState->nodeId << std::endl; + + std::set newStateList; + newStateList.insert(parentState); + + // add all other states that are not a child of the parent state + for (std::set::iterator newlistIter = stateList.begin(); newlistIter != stateList.end(); newlistIter++) { + TransitionTreeNode* otherState = *newlistIter; + while(otherState != NULL && otherState != parentState) { + otherState = otherState->parent; + } + if (otherState == NULL) + newStateList.insert(*newlistIter); + } + if (newStateList.size() > 0) + stack.push_back(newStateList); + break; + } + + currState = currState->parent; + parentState = currState->parent; + } + } + } +} + +TransitionTreeNode* ChartToFSM::buildTransTree(const Arabica::DOM::Element& root, const std::string& nodeId) { + TransitionTreeNode* stateNode = new TransitionTreeNode(); + stateNode->nodeId = nodeId; + stateNode->state = root; + + if (TAGNAME(root) == _nsInfo.xmlNSPrefix + "parallel") { + stateNode->type = TransitionTreeNode::TYPE_PARALLEL; + } else { + stateNode->type = TransitionTreeNode::TYPE_NESTED; + } + + // get all transitions and states from root without recursing + NodeSet nested; + nested.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "transition", root)); + nested.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "state", root)); + nested.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "final", root)); + nested.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "parallel", root)); + nested.to_document_order(); + + TransitionTreeNode* lastNode = NULL; + + for (int i = 0; i < nested.size(); i++) { + Element nestedElem(nested[i]); + if (TAGNAME(nestedElem) == _nsInfo.xmlNSPrefix + "transition") { + TransitionTreeNode* transNode = new TransitionTreeNode(); + transNode->transition = nestedElem; + transNode->parent = stateNode; + transNode->nodeId = nodeId + "-" + toStr(i); + transNode->type = TransitionTreeNode::TYPE_TRANSITION; + + if (stateNode->firstTransition == NULL) { + stateNode->firstTransition = transNode; + } + stateNode->children.push_back(transNode); + stateNode->lastTransition = transNode; + + if (lastNode != NULL) { + lastNode->nextTransition = transNode; + transNode->prevTransition = lastNode; + } + lastNode = transNode; + + + } else { + TransitionTreeNode* deeperNode = buildTransTree(nestedElem, nodeId + "-" + toStr(i)); + if (stateNode->firstState == NULL) { + stateNode->firstState = deeperNode; + } + + deeperNode->parent = stateNode; + stateNode->children.push_back(deeperNode); + } + } + + _stateToTransTreeNode[root] = stateNode; + + return stateNode; +} + +void ChartToFSM::getPotentialTransitionsForConfFromPowerSet(const Arabica::XPath::NodeSet& conf, std::map& outMap) { // get all transition elements from states in the current configuration NodeSet allTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", conf); + + { + std::string seperator = ""; + for (int i = 0; i < allTransitions.size(); i++) { + std::cerr << seperator << ATTR_CAST(allTransitions[i], "index"); + seperator=", "; + } + std::cerr << std::endl; + } + +// if (true) { +// outMap = _confToTransitions[""]; +// } if (allTransitions.size() == 0) return; // no transitions @@ -747,6 +1325,12 @@ void ChartToFSM::getPotentialTransitionsForConf(const Arabica::XPath::NodeSet transitions; - // std::cerr << globalState->stateId << " [" << nrElements << "]: " << std::endl; +// std::cerr << globalState->stateId << " [" << nrElements << "]: " << std::endl; for (int i = 1; i <= k; i++) { // std::cerr << stack[i] - 1 << ", "; transitions.push_back(allTransitions[stack[i] - 1]); @@ -788,32 +1372,59 @@ void ChartToFSM::getPotentialTransitionsForConf(const Arabica::XPath::NodeSet 0); - - // create a GlobalTransition object from the set - GlobalTransition* transition = new GlobalTransition(transitions, _dataModel, this); - if (!transition->isValid) { - // this set of transitions can not be enabled together - delete transition; - continue; + if (!_keepInvalidTransitions) { + // remove transitions in the same state + if(!filterSameState(transitions)) + continue; + if (dump) DUMP_TRANSSET("after same state filtered"); + + // remove those transitions with a child transition +// if(!filterChildEnabled(transitions)) + if(!filterSameHierarchy(transitions)) + continue; + if (dump) DUMP_TRANSSET("after child enabled filtered"); + + transitions = removeConflictingTransitions(transitions); + if (dump) DUMP_TRANSSET("after conflicting filtered"); + // algorithm can never reduce to empty set + assert(transitions.size() > 0); + + // create a GlobalTransition object from the set + transition = new GlobalTransition(transitions, _dataModel, this); + if (!transition->isValid) { + // this set of transitions can not be enabled together + delete transition; + continue; + } + } else { + transition = new GlobalTransition(transitions, _dataModel, this); + + // remove transitions in the same state + if(!filterSameState(transitions)) { + transition->isValid = false; + transition->invalidReason = GlobalTransition::SAME_SOURCE_STATE; + transition->invalidMsg = "Same source state"; + +// } else if(!filterChildEnabled(transitions)) { + } else if(!filterSameHierarchy(transitions)) { + transition->isValid = false; + transition->invalidReason = GlobalTransition::CHILD_ENABLED; + transition->invalidMsg = "Nested transitions"; + } else { + NodeSet nonPreemptingTransitions = removeConflictingTransitions(transitions); + if (nonPreemptingTransitions.size() != transitions.size()) { + transition->isValid = false; + transition->invalidReason = GlobalTransition::PREEMPTING_MEMBERS; + transition->invalidMsg = "Preempting members"; + } + } + } // two combinations might have projected onto the same conflict-free set @@ -830,6 +1441,7 @@ void ChartToFSM::getPotentialTransitionsForConf(const Arabica::XPath::NodeSettransitionId << ":" << transition->eventDesc << std::endl; outMap[transition->transitionId] = transition; } +// _confToTransitions[""] = outMap; return; } @@ -853,7 +1465,11 @@ void ChartToFSM::explode() { // append new global states and pop from front while(statesRemaining.size() > 0) { - DUMP_STATS(0); + _perfStackSize = statesRemaining.size(); + _perfStatesTotal++; + _perfStatesProcessed++; + + DUMP_STATS(0, false); GlobalState* globalState = statesRemaining.front().second; _currGlobalTransition = statesRemaining.front().first; @@ -875,8 +1491,6 @@ void ChartToFSM::explode() { continue; // we have already been here } - _perfStatesProcessed++; - _configuration = globalState->getActiveStates(); _alreadyEntered = globalState->getAlreadyEnteredStates(); _historyValue = globalState->getHistoryStates(); @@ -910,7 +1524,12 @@ void ChartToFSM::explode() { } else { // we need to calculate the potential optimal transition sets std::map transitionSets; - getPotentialTransitionsForConf(refsToStates(globalState->activeStatesRefs), transitionSets); + // std::cerr << globalState->stateId << std::endl; + if (_transitionsFromTree) { + getPotentialTransitionsForConfFromTree(refsToStates(globalState->activeStatesRefs), transitionSets); + } else { + getPotentialTransitionsForConfFromPowerSet(refsToStates(globalState->activeStatesRefs), transitionSets); + } // reduce and sort transition sets for(std::map::iterator transSetIter = transitionSets.begin(); @@ -920,10 +1539,20 @@ void ChartToFSM::explode() { } globalState->sortedOutgoing.sort(PtrComp); - globalState->sortedOutgoing.unique(hasUnconditionalSuperset); - globalState->sortedOutgoing.unique(hasEarlierUnconditionalMatch); +// globalState->sortedOutgoing.unique(hasUnconditionalSuperset); +// globalState->sortedOutgoing.unique(hasEarlierUnconditionalMatch); // unique is not quite like what we need, but it was a start - globalState->sortedOutgoing = reapplyUniquePredicates(globalState->sortedOutgoing); + if (_keepInvalidTransitions) { + globalState->sortedOutgoing = redundantMark(globalState->sortedOutgoing); + } else { +// globalState->sortedOutgoing.unique(hasUnconditionalSuperset); +// globalState->sortedOutgoing.unique(hasEarlierUnconditionalMatch); + globalState->sortedOutgoing = redundantRemove(globalState->sortedOutgoing); + } +// globalState->sortedOutgoing = redundantRemove(globalState->sortedOutgoing); +// globalState->sortedOutgoing = redundantRemove(globalState->sortedOutgoing); +// +// std::cout << globalState->sortedOutgoing.size() << std::endl; assert(_activeConf.find(globalState->activeId) == _activeConf.end()); assert(globalState->activeIndex == -1); @@ -940,11 +1569,15 @@ void ChartToFSM::explode() { GlobalTransition* outgoingTrans = *transIter; outgoingTrans->source = globalState->stateId; + + if (_keepInvalidTransitions && !outgoingTrans->isValid) + continue; + _currGlobalTransition = outgoingTrans; microstep(refsToTransitions(outgoingTrans->transitionRefs)); - assert(isLegalConfiguration(_configuration)); - +// assert(isLegalConfiguration(_configuration)); + _perfMicroStepProcessed++; _perfMicroStepTotal++; @@ -1086,6 +1719,26 @@ void ChartToFSM::beforeEnteringState(Interpreter interpreter, const Arabica::DOM void ChartToFSM::beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) { } +std::ostream& operator<< (std::ostream& os, const GlobalTransition::Action& action) { + if (action.onEntry) + os << "onEntry: " << action.onEntry; + if (action.onExit) + os << "onExit: " << action.onExit; + if (action.transition) + os << "transition: " << action.transition; + if (action.entered) + os << "entered: " << action.entered; + if (action.exited) + os << "exited: " << action.exited; + if (action.invoke) + os << "invoke: " << action.invoke; + if (action.uninvoke) + os << "uninvoke: " << action.uninvoke; + if (action.raiseDone) + os << "raiseDone: " << action.raiseDone; + return os; +} + GlobalState::GlobalState(const Arabica::XPath::NodeSet& activeStates_, const Arabica::XPath::NodeSet& alreadyEnteredStates_, // we need to remember for binding=late const std::map >& historyStates_, @@ -1181,6 +1834,11 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t bool foundWithTarget = false; bool foundTargetLess = false; + Arabica::DOM::Element withEvent; + Arabica::DOM::Element noneEvent; + Arabica::DOM::Element withTarget; + Arabica::DOM::Element noneTarget; + for (int i = 0; i < transitionSet.size(); i++) { Arabica::DOM::Element transElem = Arabica::DOM::Element(transitionSet[i]); if (HAS_ATTR(transElem, "eventexpr")) { @@ -1188,19 +1846,23 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t } if (HAS_ATTR(transElem, "event")) { foundWithEvent = true; + withEvent = transElem; if (foundEventLess) break; } else { foundEventLess = true; + noneEvent = transElem; if (foundWithEvent) break; } if (HAS_ATTR(transElem, "target")) { foundWithTarget = true; + withTarget = transElem; if (foundTargetLess) break; } else { foundTargetLess = true; + noneTarget = transElem; if (foundWithTarget) break; } @@ -1209,6 +1871,10 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t // do not mix eventless and event transitions if (foundEventLess && foundWithEvent) { + if (flattener->_keepInvalidTransitions) { + invalidReason = MIXES_EVENT_SPONTANEOUS; + invalidMsg = "Mixes (non-)spontaneous"; + } isValid = false; return; } @@ -1228,6 +1894,10 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t eventNames = getCommonEvents(transitionSet); if (eventNames.size() == 0) { // LOG(INFO) << "No event will activate this conflict-free subset" << std::endl; + if (flattener->_keepInvalidTransitions) { + invalidReason = NO_COMMON_EVENT; + invalidMsg = "No common event"; + } isValid = false; return; } else { @@ -1264,20 +1934,20 @@ GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& t // std::cout << std::endl << std::endl; } - int index = 0; seperator = ""; for (std::vector >::iterator transIter = interpreter->indexedTransitions.begin(); transIter != interpreter->indexedTransitions.end(); transIter++) { const Element& refTrans = *transIter; + if (!HAS_ATTR(refTrans, "priority")) + continue; if (InterpreterImpl::isMember(refTrans, transitionSet)) { - members += seperator + toStr(index); + members += seperator + ATTR(refTrans, "priority"); } else { members += seperator; - for (int i = 0; i < toStr(index).size(); i++) { + for (int i = 0; i < ATTR(refTrans, "priority").size(); i++) { members += " "; } } seperator = " "; - index++; } // if (members == " 4 6 7 ") diff --git a/src/uscxml/transform/ChartToFSM.cpp.new b/src/uscxml/transform/ChartToFSM.cpp.new new file mode 100644 index 0000000..1dcf16c --- /dev/null +++ b/src/uscxml/transform/ChartToFSM.cpp.new @@ -0,0 +1,1482 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include "uscxml/transform/ChartToFSM.h" +#include "uscxml/transform/FlatStateIdentifier.h" +#include "uscxml/Convenience.h" +#include "uscxml/Factory.h" + +#include +#include + +#include +#include "uscxml/UUID.h" +#include +#include +#include +#undef max +#include + +#define UNDECIDABLE 2147483647 +#define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) + +#define DUMP_STATS(nrTrans) \ +uint64_t now = tthread::chrono::system_clock::now(); \ +if (now - _lastTimeStamp > 1000) { \ + std::cerr << "## Transition: " << _perfTransUsed << " / " << _perfTransTotal << " [" << _perfTransProcessed << "/sec]"; \ + if (nrTrans > 0) { \ + std::cerr << " - 2**" << nrTrans << " = " << pow(2.0, static_cast(nrTrans)); \ + } \ + std::cerr << std::endl; \ + std::cerr << "## State : " << _globalConf.size() << " [" << _perfStatesProcessed << "/sec]" << std::endl; \ + std::cerr << "## Microstep : " << _perfMicroStepTotal << " [" << _perfMicroStepProcessed << "/sec]" << std::endl; \ + std::cerr << "## Cached : " << _perfStatesCachedTotal << " [" << _perfStatesCachedProcessed << "/sec]" << std::endl; \ + std::cerr << "## Skipped : " << _perfStatesSkippedTotal << " [" << _perfStatesSkippedProcessed << "/sec]" << std::endl; \ + std::cerr << "## Queues : " << (_maxEventRaisedChain == UNDECIDABLE ? "UNK" : toStr(_maxEventRaisedChain)) << " / " << (_maxEventSentChain == UNDECIDABLE ? "UNK" : toStr(_maxEventSentChain)) << std::endl; \ + std::cerr << _perfTransUsed << ", " << _perfTransTotal << ", " << _perfTransProcessed << ", "; \ + std::cerr << _globalConf.size() << ", " << _perfStatesProcessed << ", "; \ + std::cerr << _perfMicroStepTotal << ", " << _perfMicroStepProcessed << ", "; \ + std::cerr << _perfStatesCachedTotal << ", " << _perfStatesCachedProcessed << ", " << _perfStatesSkippedTotal << ", " << _perfStatesSkippedProcessed << ", "; \ + std::cerr << (_maxEventRaisedChain == UNDECIDABLE ? "UNK" : toStr(_maxEventRaisedChain)) << ", " << (_maxEventSentChain == UNDECIDABLE ? "UNK" : toStr(_maxEventSentChain)) << std::endl; \ + std::cerr << std::endl; \ + _perfTransProcessed = 0; \ + _perfStatesProcessed = 0; \ + _perfStatesCachedProcessed = 0; \ + _perfStatesSkippedProcessed = 0; \ + _perfMicroStepProcessed = 0; \ + _lastTimeStamp = now; \ +} + +//std::cerr << "Q: " << (_maxEventRaisedChain == UNDECIDABLE ? "UNK" : toStr(_maxEventRaisedChain)) << " / " << (_maxEventSentChain == UNDECIDABLE ? "UNK" : toStr(_maxEventSentChain)) << std::endl; + +#define DUMP_TRANSSET(where) \ +{\ +std::cout << std::endl;\ +std::cout << "** " << transitions.size() << " ** " << where << std::endl;\ + for (int m = 0; m < transitions.size(); m++) {\ + std::cout << transitions[m] << std::endl;\ + }\ +} + +namespace uscxml { + + +using namespace Arabica::DOM; +using namespace Arabica::XPath; + + +#define DETAIL_EXEC_CONTENT(field, actPtr) \ +std::cerr << " " << #field << " / " << TAGNAME_CAST(actPtr->field) << " ("; \ +NodeSet contents = filterChildType(Node_base::ELEMENT_NODE, actPtr->field, true); \ +for (int i = 0; i < contents.size(); i++) { \ + std::cerr << " " << TAGNAME_CAST(contents[i]); \ +} \ +std::cerr << ")"; + + +uint64_t Complexity::stateMachineComplexity(const Arabica::DOM::Element& root, Variant variant) { + Complexity complexity = calculateStateMachineComplexity(root); + uint64_t value = complexity.value; + + if (variant != IGNORE_HISTORY_AND_NESTED_DATA && variant != IGNORE_HISTORY) { + for (std::list::const_iterator histIter = complexity.history.begin(); histIter != complexity.history.end(); histIter++) { + value *= *histIter; + } + } + + if (variant != IGNORE_HISTORY_AND_NESTED_DATA && variant != IGNORE_NESTED_DATA) { + bool ignoreNestedData = false; + if (root.getLocalName() == "scxml" && (!HAS_ATTR_CAST(root, "binding") || boost::to_lower_copy(ATTR_CAST(root, "binding")) == "early")) { + ignoreNestedData = true; + } + + if (!ignoreNestedData) { + uint64_t power = complexity.nestedData; + while(power--) { + value *= 2; + } + } + } + + return value; +} + +Complexity Complexity::calculateStateMachineComplexity(const Arabica::DOM::Element& root) { + Complexity complexity; + + bool hasFlatHistory = false; + bool hasDeepHistory = false; + bool hasNestedData = false; + + Arabica::DOM::NodeList childElems = root.getChildNodes(); + for (int i = 0; i < childElems.getLength(); i++) { + if (childElems.item(i).getNodeType() != Node_base::ELEMENT_NODE) + continue; + Element childElem = Element(childElems.item(i)); + if (InterpreterImpl::isHistory(childElem)) { + if (HAS_ATTR(childElem, "type") && ATTR(childElem, "type") == "deep") { + hasDeepHistory = true; + } else { + hasFlatHistory = true; + } + } + if (!hasNestedData && childElem.getLocalName() == "datamodel") { + Arabica::DOM::NodeList dataElemChilds = childElem.getChildNodes(); + for (int j = 0; j < dataElemChilds.getLength(); j++) { + if (dataElemChilds.item(j).getLocalName() == "data") + hasNestedData = true; + } + } + } + + if (hasNestedData) + complexity.nestedData++; + + if (InterpreterImpl::isCompound(root) || TAGNAME(root) == "scxml") { + // compounds can be in any of the child state -> add + NodeSet childs = InterpreterImpl::getChildStates(root); + for (int i = 0; i < childs.size(); i++) { + complexity += calculateStateMachineComplexity(Element(childs[i])); + } + if (hasFlatHistory) { + complexity.history.push_back(childs.size()); + } + if (hasDeepHistory) { + complexity.history.push_back(complexity.value); + } + } else if (InterpreterImpl::isParallel(root)) { + // parallels are in all states -> multiply + NodeSet childs = InterpreterImpl::getChildStates(root); + complexity.value = 1; + for (int i = 0; i < childs.size(); i++) { + complexity *= calculateStateMachineComplexity(Element(childs[i])); + } + if (hasDeepHistory) { + complexity.history.push_back(complexity.value); + } + + } else if (InterpreterImpl::isAtomic(root)) { + return 1; + } + + return complexity; +} + + +ChartToFSM::ChartToFSM(const Interpreter& other) { + + cloneFrom(other.getImpl()); + + _keepInvalidTransitions = false; + _lastTimeStamp = tthread::chrono::system_clock::now(); + _perfTransProcessed = 0; + _perfTransTotal = 0; + _perfTransUsed = 0; + _perfStatesProcessed = 0; + _perfStatesSkippedProcessed = 0; + _perfStatesSkippedTotal = 0; + _perfStatesCachedProcessed = 0; + _perfStatesCachedTotal = 0; + _perfMicroStepProcessed = 0; + _perfMicroStepTotal = 0; + + _start = NULL; + _currGlobalTransition = NULL; + + _lastStateIndex = 0; + _lastActiveIndex = 0; + _lastTransIndex = 0; + + _maxEventSentChain = 0; + _maxEventRaisedChain = 0; + _doneEventRaiseTolerance = 0; + _skipEventChainCalculations = false; + + addMonitor(this); +} + +ChartToFSM::~ChartToFSM() { + std::map::iterator confIter = _globalConf.begin(); + while(confIter != _globalConf.end()) { + std::list::iterator transIter = confIter->second->sortedOutgoing.begin(); + while (transIter != confIter->second->sortedOutgoing.end()) { + delete *transIter; + transIter++; + } + delete confIter->second; + confIter++; + } + + // tear down caches + Arabica::XPath::NodeSet allTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); + for (int i = 0; i < allTransitions.size(); i++) { + _transParents.erase(allTransitions[i]); + } + +} + +Document ChartToFSM::getDocument() const { + if (_flatDoc) + return _flatDoc; + return _document; +} + +InterpreterState ChartToFSM::interpret() { + + // create a _flatDoc for the FSM + DOMImplementation domFactory = Arabica::SimpleDOM::DOMImplementation::getDOMImplementation(); + _flatDoc = domFactory.createDocument(_document.getNamespaceURI(), "", 0); + + init(); + setupIOProcessors(); + + uint64_t complexity = Complexity::stateMachineComplexity(_scxml) + 1; + std::cerr << "Approximate Complexity: " << complexity << std::endl; + std::cerr << "Approximate Active Complexity: " << Complexity::stateMachineComplexity(_scxml, Complexity::IGNORE_HISTORY_AND_NESTED_DATA) + 1 << std::endl; + + if (complexity > 1000) { + _skipEventChainCalculations = true; + _maxEventRaisedChain = UNDECIDABLE; + _maxEventSentChain = UNDECIDABLE; + } + // initialize the datamodel + std::string datamodelName; + if (datamodelName.length() == 0 && HAS_ATTR(_scxml, "datamodel")) + datamodelName = ATTR(_scxml, "datamodel"); + if (datamodelName.length() == 0 && HAS_ATTR(_scxml, "profile")) // SCION SCXML uses profile to specify datamodel + datamodelName = ATTR(_scxml, "profile"); + if(datamodelName.length() > 0) { + _dataModel = _factory->createDataModel(datamodelName, this); + if (!_dataModel) { + Event e; + e.data.compound["cause"] = Data("Cannot instantiate datamodel", Data::VERBATIM); + throw e; + } + } else { + _dataModel = _factory->createDataModel("null", this); + } + if(datamodelName.length() > 0 && !_dataModel) { + LOG(ERROR) << "No datamodel for " << datamodelName << " registered"; + } + + // setup caches + { + Arabica::XPath::NodeSet allTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true); + indexedTransitions.reserve(allTransitions.size()); + for (int i = 0; i < allTransitions.size(); i++) { + _transParents[allTransitions[i]] = InterpreterImpl::getParentState(allTransitions[i]); + } + } + + // identify all history elements + NodeSet histories = filterChildElements(_nsInfo.xmlNSPrefix + "history", _scxml, true); + for (int i = 0; i < histories.size(); i++) { + _historyTargets[ATTR_CAST(histories[i], "id")] = Element(histories[i]); + } + + _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); + _alreadyFlat = (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat"))); + + if (_alreadyFlat) { + reassembleFromFlat(); + return _state; + } + + // set invokeid for all invokers to parent state if none given + NodeSet invokers = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _scxml, true); + for (int i = 0; i < invokers.size(); i++) { + Element invokerElem = Element(invokers[i]); + invokerElem.setAttribute("parent", ATTR_CAST(invokerElem.getParentNode(), "id")); + } + // reset + _globalConf.clear(); + _currGlobalTransition = NULL; + + // very first state + _start = new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix, this); + _globalConf[_start->stateId] = _start; + _globalConf[_start->stateId]->index = _lastStateIndex++; + + NodeSet initialTransitions; + + // enter initial configuration + Arabica::XPath::NodeSet initialStates; + initialStates = getInitialStates(); + assert(initialStates.size() > 0); + for (int i = 0; i < initialStates.size(); i++) { + Element initialElem = _document.createElementNS(_nsInfo.nsURL, "initial"); + _nsInfo.setPrefix(initialElem); + initialElem.setAttribute("generated", "true"); + Element transitionElem = _document.createElementNS(_nsInfo.nsURL, "transition"); + _nsInfo.setPrefix(transitionElem); + transitionElem.setAttribute("target", ATTR_CAST(initialStates[i], "id")); + initialElem.appendChild(transitionElem); + _scxml.appendChild(initialElem); + initialTransitions.push_back(transitionElem); + } + + if (!_skipEventChainCalculations) + annotateRaiseAndSend(_scxml); + +// std::cout << _scxml << std::endl; + + indexTransitions(); + + // add initial transitions as least prior + for (int i = 0; i < initialTransitions.size() ; i++) { + indexedTransitions.push_back(Element(initialTransitions[i])); + } + + // set index attribute for transitions + for (int i = 0; i < indexedTransitions.size(); i++) { + std::cerr << toStr(i) << ":" << (HAS_ATTR(indexedTransitions[i], "line_start") ? ATTR(indexedTransitions[i], "line_start") : ""); + std::cerr << "\t" << DOMUtils::xPathForNode(indexedTransitions[i]) << std::endl; + indexedTransitions[i].setAttribute("index", toStr(i)); + } + +// int lastTransIndex = indexedTransitions.size(); +// for (int i = 0; i < initialTransitions.size() ; i++, lastTransIndex++) { +// indexedTransitions[i].setAttribute("index", toStr(indexedTransitions.size() - 1 - i)); +// } + + // gather and set index attribute o states + NodeSet allStates = getAllStates(); + allStates.to_document_order(); + + indexedStates.resize(allStates.size()); + for (int i = 0; i < allStates.size(); i++) { + Element state = Element(allStates[i]); + + // while we are iterating, determine deepest nested level + size_t nrAncs = getProperAncestors(state, _scxml).size(); + if (_doneEventRaiseTolerance < nrAncs) + _doneEventRaiseTolerance = nrAncs; + + state.setAttribute("index", toStr(i)); + indexedStates[i] = state; + } + +// std::cerr << _scxml << std::endl; + + GlobalTransition* globalTransition = new GlobalTransition(initialTransitions, _dataModel, this); + globalTransition->index = _lastTransIndex++; + + _start->sortedOutgoing.push_back(globalTransition); + globalTransition->source = _start->stateId; + _currGlobalTransition = globalTransition; + + enterStates(initialTransitions); + globalTransition->destination = FlatStateIdentifier::toStateId(_configuration); + globalTransition->activeDestination = globalTransition->destination; + + explode(); + +#if 0 + // print set of global configurations + for(std::map::iterator globalConfIter = _globalConf.begin(); + globalConfIter != _globalConf.end(); + globalConfIter++) { + std::cerr << globalConfIter->first << std::endl; + } + std::cerr << _globalConf.size() << std::endl; +#endif + + std::cerr << "Actual Complexity: " << _globalConf.size() << std::endl; + std::cerr << "Actual Active Complexity: " << _activeConf.size() << std::endl; + std::cerr << "Internal Queue: " << _maxEventRaisedChain << std::endl; + std::cerr << "External Queue: " << _maxEventSentChain << std::endl; + + if (complexity < _globalConf.size()) + throw std::runtime_error("Upper bound for states exceeded"); + + return _state; +} + +void ChartToFSM::executeContent(const Arabica::DOM::Element& content, bool rethrow) { +// std::cerr << content << std::endl; +// std::cerr << TAGNAME(content) << std::endl; + + GlobalTransition::Action action; + + NodeList childs = content.getChildNodes(); + for (unsigned int i = 0; i < childs.getLength(); i++) { + Node_base::Type type = childs.item(i).getNodeType(); + if (type == Node_base::ELEMENT_NODE || type == Node_base::COMMENT_NODE || type == Node_base::TEXT_NODE) { + goto HAS_VALID_CHILDREN; + } + } + return; + +HAS_VALID_CHILDREN: + if (false) { + } else if (TAGNAME(content) == "transition") { + action.transition = content; + } else if (TAGNAME(content) == "onexit") { + action.onExit = content; + } else if (TAGNAME(content) == "onentry") { + action.onEntry = content; + } else if (TAGNAME(content) == "history") { + assert(false); + } else { // e.g. global script elements + return; + } + + if (!_skipEventChainCalculations && + (_maxEventRaisedChain != UNDECIDABLE || _maxEventSentChain != UNDECIDABLE)) { + assert(content.hasAttribute("raise") && content.hasAttribute("send")); + + std::string raiseAttr = content.getAttribute("raise"); + std::string sendAttr = content.getAttribute("send"); + + _currGlobalTransition->eventsRaised = (raiseAttr == "-1" ? UNDECIDABLE : _currGlobalTransition->eventsRaised + strTo(raiseAttr)); + _currGlobalTransition->eventsSent = (sendAttr == "-1" ? UNDECIDABLE : _currGlobalTransition->eventsSent + strTo(sendAttr)); + + if (_currGlobalTransition->eventsRaised > _maxEventRaisedChain) + _maxEventRaisedChain = _currGlobalTransition->eventsRaised; + if (_currGlobalTransition->eventsSent > _maxEventSentChain) + _maxEventSentChain = _currGlobalTransition->eventsSent; + } + + _currGlobalTransition->actions.push_back(action); + _currGlobalTransition->hasExecutableContent = true; +} + +void ChartToFSM::invoke(const Arabica::DOM::Element& element) { + GlobalTransition::Action action; + action.invoke = element; + _currGlobalTransition->actions.push_back(action); + _currGlobalTransition->hasExecutableContent = true; +} + +void ChartToFSM::cancelInvoke(const Arabica::DOM::Element& element) { + GlobalTransition::Action action; + action.uninvoke = element; + _currGlobalTransition->actions.push_back(action); + _currGlobalTransition->hasExecutableContent = true; +} + +void ChartToFSM::internalDoneSend(const Arabica::DOM::Element& state) { + if (!isState(state)) + return; + + if (parentIsScxmlState(state)) + return; + +// std::cerr << "internalDoneSend: " << state << std::endl; + + // create onentry with a raise element + Element onentry = _flatDoc.createElementNS(_nsInfo.nsURL, "onentry"); + _nsInfo.setPrefix(onentry); + + Element raise = _flatDoc.createElementNS(_nsInfo.nsURL, "raise"); + _nsInfo.setPrefix(raise); + + onentry.appendChild(raise); + + Arabica::XPath::NodeSet doneDatas = filterChildElements(_nsInfo.xmlNSPrefix + "donedata", state); + if (doneDatas.size() > 0) { + Arabica::DOM::Node doneData = doneDatas[0]; + Arabica::XPath::NodeSet contents = filterChildElements(_nsInfo.xmlNSPrefix + "content", doneDatas[0]); + if (contents.size() > 0) { + Node imported = _flatDoc.importNode(contents[0], true); + raise.appendChild(imported); + } + Arabica::XPath::NodeSet params = filterChildElements(_nsInfo.xmlNSPrefix + "param", doneDatas[0]); + if (params.size() > 0) { + Node imported = _flatDoc.importNode(params[0], true); + raise.appendChild(imported); + } + } + + raise.setAttribute("event", "done.state." + ATTR_CAST(state.getParentNode(), "id")); // parent?! + + GlobalTransition::Action action; + action.onEntry = onentry; + + _currGlobalTransition->actions.push_back(action); + if (!_skipEventChainCalculations && + (_maxEventRaisedChain != UNDECIDABLE || _maxEventSentChain != UNDECIDABLE)) + _currGlobalTransition->eventsRaised++; + _currGlobalTransition->hasExecutableContent = true; + +} + +static bool isSuperset(const GlobalTransition* t1, const GlobalTransition* t2) { + bool isSuperset = true; + + if (t1->transitionRefs.size() >= t2->transitionRefs.size()) + return false; + + NodeSet t1Trans = t1->getTransitions(); + NodeSet t2Trans = t2->getTransitions(); + + for (int i = 0; i < t1Trans.size(); i++) { + if (!InterpreterImpl::isMember(t1Trans[i], t2Trans)) { + isSuperset = false; + } + } + return isSuperset; +} + +// return false if two transitions have the same source +std::map, Arabica::DOM::Node > ChartToFSM::_transParents; +bool ChartToFSM::filterSameState(const NodeSet& transitions) { + + for (unsigned int i = 0; i < transitions.size(); i++) { + Node p1 = _transParents[transitions[i]]; + + for (unsigned int j = i + 1; j < transitions.size(); j++) { +// if (i == j) +// continue; + Node p2 = _transParents[transitions[j]]; + + if (p1 == p2) + return false; + } + } + return true; +} + +static bool filterChildEnabled(const NodeSet& transitions) { + // drop any transition that is already enabled by a child + NodeSet filteredTransitions; + for (unsigned int i = 0; i < transitions.size(); i++) { + Node t1 = transitions[i]; + Node p1 = InterpreterImpl::getParentState(t1); + for (unsigned int j = 0; j < transitions.size(); j++) { + if (i == j) + continue; + Node t2 = transitions[j]; + Node p2 = InterpreterImpl::getParentState(t2); + p2 = p2.getParentNode(); // TODO: think about again! + while(p2) { + if (p1 == p2) { + std::string eventDesc1 = ATTR_CAST(t1, "event"); + std::string eventDesc2 = ATTR_CAST(t2, "event"); + if (InterpreterImpl::nameMatch(eventDesc1, eventDesc2)) { + return false; + } + } + p2 = p2.getParentNode(); + } + } + filteredTransitions.push_back(t1); + ; + } + return true; +} + +bool ChartToFSM::hasForeachInBetween(const Arabica::DOM::Node& ancestor, const Arabica::DOM::Node& child) { + if (!ancestor || !child) + return false; + + Node currChild = child; + while(currChild != ancestor) { + if (!currChild.getParentNode()) + return false; + if (TAGNAME_CAST(currChild) == "foreach") + return true; + currChild = currChild.getParentNode(); + } + return false; +} + +void ChartToFSM::annotateRaiseAndSend(const Arabica::DOM::Element& root) { + NodeSet execContent; + execContent.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "transition", _scxml, true)); + execContent.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onentry", _scxml, true)); + execContent.push_back(filterChildElements(_nsInfo.xmlNSPrefix + "onexit", _scxml, true)); + for (int i = 0; i < execContent.size(); i++) { + Element execContentElem(execContent[i]); + + int nrRaise = 0; + NodeSet raise = filterChildElements(_nsInfo.xmlNSPrefix + "raise", execContent[i], true); + for (int j = 0; j < raise.size(); j++) { + if (hasForeachInBetween(execContent[i], raise[j])) { + execContentElem.setAttribute("raise", "-1"); + goto DONE_COUNT_RAISE; + } else { + nrRaise++; + } + } + execContentElem.setAttribute("raise", toStr(nrRaise)); + + DONE_COUNT_RAISE: + + int nrSend = 0; + NodeSet sends = filterChildElements(_nsInfo.xmlNSPrefix + "send", execContent[i], true); + for (int j = 0; j < sends.size(); j++) { + if (hasForeachInBetween(execContent[i], sends[j])) { + execContentElem.setAttribute("send", "-1"); + goto DONE_COUNT_SEND; + } else { + nrSend++; + } + } + execContentElem.setAttribute("send", toStr(nrSend)); + + DONE_COUNT_SEND: + ; + } +} + +void ChartToFSM::indexTransitions() { + indexTransitions(_scxml); + // reverse indices for most prior to be in front + std::reverse(indexedTransitions.begin(), indexedTransitions.end()); + + size_t index = 1; + for (std::vector >::iterator transIter = indexedTransitions.begin(); transIter != indexedTransitions.end(); transIter++) { + transIter->setAttribute("priority", toStr(index)); + index++; + } + +} + +void ChartToFSM::indexTransitions(const Arabica::DOM::Element& root) { + // breadth first traversal of transitions + Arabica::XPath::NodeSet levelTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", root); + for (int i = levelTransitions.size() - 1; i >= 0; i--) { + // push into index starting with least prior + indexedTransitions.push_back(Element(levelTransitions[i])); + } + + Arabica::XPath::NodeSet nextLevel = filterChildType(Arabica::DOM::Node_base::ELEMENT_NODE, root); + for (int i = nextLevel.size() - 1; i >= 0; i--) { + Element stateElem = Element(nextLevel[i]); + if (isState(stateElem)) + indexTransitions(stateElem); + } +} + +bool GlobalTransition::operator< (const GlobalTransition& other) const { + const std::vector >& indexedTransitions = interpreter->indexedTransitions; + NodeSet transitions = getTransitions(); + + for (std::vector >::const_iterator transIter = indexedTransitions.begin(); transIter != indexedTransitions.end(); transIter++) { + const Element& refTrans = *transIter; + NodeSet otherTransitions = other.getTransitions(); + + if (InterpreterImpl::isMember(refTrans, transitions) && !InterpreterImpl::isMember(refTrans, otherTransitions)) { + return true; + } + if (!InterpreterImpl::isMember(refTrans, transitions) && InterpreterImpl::isMember(refTrans, otherTransitions)) { + return false; + } + } + return true; // actually, they are equal +} + +template bool PtrComp(const T * const & a, const T * const & b) { + return *a < *b; +} + + +/** + * subset only removes transitions without cond -> superset will always be enabled + */ +bool hasUnconditionalSuperset (GlobalTransition* first, GlobalTransition* second) { + + NodeSet firstTransitions = first->getTransitions(); + NodeSet secondTransitions = first->getTransitions(); + + if (isSuperset(second, first)) { + for (int i = 0; i < firstTransitions.size(); i++) { + if (!InterpreterImpl::isMember(firstTransitions[i], secondTransitions)) { + if (HAS_ATTR_CAST(firstTransitions[i], "cond")) { + return false; // second can't be removed + } + } + } + return true; // remove second + } + return false; //second can't be removed +} + +bool hasEarlierUnconditionalMatch(GlobalTransition* first, GlobalTransition* second) { + if (first->eventDesc == second->eventDesc) { + if (first->condition.size() == 0) + return true; + } + return false; +} + +std::list redundantRemove(std::list list) { + for (std::list::iterator outerIter = list.begin(); + outerIter != list.end(); + outerIter++) { + for (std::list::iterator innerIter = outerIter; + innerIter != list.end(); + innerIter++) { + + if (innerIter == outerIter) + continue; + + GlobalTransition* t1 = *outerIter; + GlobalTransition* t2 = *innerIter; + + if (hasUnconditionalSuperset(t1, t2)) { + list.erase(innerIter++); + continue; + } else if (hasUnconditionalSuperset(t2, t1)) { + list.erase(outerIter++); + continue; + } + if (hasEarlierUnconditionalMatch(t1, t2)) { + list.erase(innerIter++); + continue; + } + } + } + return list; +} + +std::list redundantMark(std::list list) { + for (std::list::iterator outerIter = list.begin(); + outerIter != list.end(); + outerIter++) { + for (std::list::iterator innerIter = outerIter; + innerIter != list.end(); + innerIter++) { + + if (innerIter == outerIter) + continue; + + GlobalTransition* t1 = *outerIter; + GlobalTransition* t2 = *innerIter; + + if (!t1->isValid || !t2->isValid) + continue; + + if (hasUnconditionalSuperset(t1, t2)) { + t2->isValid = false; + t2->invalidMsg = "Unconditional superset"; + t2->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; + continue; + } else if (hasUnconditionalSuperset(t2, t1)) { + t1->isValid = false; + t1->invalidMsg = "Unconditional superset"; + t1->invalidReason = GlobalTransition::UNCONDITIONAL_SUPERSET; + continue; + } + if (hasEarlierUnconditionalMatch(t1, t2)) { + t2->isValid = false; + t2->invalidMsg = "Earlier unconditional match"; + t2->invalidReason = GlobalTransition::UNCONDITIONAL_MATCH; + continue; + } + } + } + return list; +} + +void ChartToFSM::getPotentialTransitionsForConf(const Arabica::XPath::NodeSet& conf, std::map& outMap) { + // get all transition elements from states in the current configuration + NodeSet allTransitions = filterChildElements(_nsInfo.xmlNSPrefix + "transition", conf); + + + if (allTransitions.size() == 0) + return; // no transitions + + int nrElements = allTransitions.size(); + int k = 0; + int* stack = (int*)malloc((nrElements + 1) * sizeof(int)); + memset(stack, 0, (nrElements + 1) * sizeof(int)); + + while(1) { + // create the power set of all potential transitions - this is expensive! + // see: http://www.programminglogic.com/powerset-algorithm-in-c/ + + if (stack[k] < nrElements) { + stack[k+1] = stack[k] + 1; + k++; + } + + else { + stack[k-1]++; + k--; + } + + if (k==0) + break; + + NodeSet transitions; + // std::cerr << globalState->stateId << " [" << nrElements << "]: " << std::endl; + for (int i = 1; i <= k; i++) { + // std::cerr << stack[i] - 1 << ", "; + transitions.push_back(allTransitions[stack[i] - 1]); + } + // std::cerr << std::endl; + + // transitions.push_back(allTransitions[0]); + // transitions.push_back(allTransitions[4]); + // transitions.push_back(allTransitions[5]); + // transitions.push_back(allTransitions[7]); + + bool dump = false; + + // if (k == 4 && stack[1] == 1 && stack[2] == 5 && stack[3] == 6 && stack[4] == 8) { + // dump = true; + // } + + if (dump) DUMP_TRANSSET("at start"); + + _perfTransTotal++; + _perfTransProcessed++; + + DUMP_STATS(nrElements); + + + GlobalTransition* transition = NULL; + + // reduce to conflict-free subset + // transitions.to_document_order(); + if (!_keepInvalidTransitions) { + // remove transitions in the same state + if(!filterSameState(transitions)) + continue; + if (dump) DUMP_TRANSSET("after same state filtered"); + + // remove those transitions with a child transition + if(!filterChildEnabled(transitions)) + continue; + if (dump) DUMP_TRANSSET("after child enabled filtered"); + + transitions = removeConflictingTransitions(transitions); + if (dump) DUMP_TRANSSET("after conflicting filtered"); + // algorithm can never reduce to empty set + assert(transitions.size() > 0); + + // create a GlobalTransition object from the set + transition = new GlobalTransition(transitions, _dataModel, this); + if (!transition->isValid) { + // this set of transitions can not be enabled together + delete transition; + continue; + } + } else { + transition = new GlobalTransition(transitions, _dataModel, this); + + // remove transitions in the same state + if(!filterSameState(transitions)) { + transition->isValid = false; + transition->invalidReason = GlobalTransition::SAME_SOURCE_STATE; + transition->invalidMsg = "Same source state"; + + } else if(!filterChildEnabled(transitions)) { + transition->isValid = false; + transition->invalidReason = GlobalTransition::CHILD_ENABLED; + transition->invalidMsg = "Nested transition enabled"; + + } else { + NodeSet nonPreemptingTransitions = removeConflictingTransitions(transitions); + if (nonPreemptingTransitions.size() != transitions.size()) { + transition->isValid = false; + transition->invalidReason = GlobalTransition::PREEMPTING_MEMBERS; + transition->invalidMsg = "Preempting members"; + } + } + + } + + + // two combinations might have projected onto the same conflict-free set + if (outMap.find(transition->transitionId) != outMap.end()) { + // std::cerr << "skipping as projected onto existing conflict-free subset" << std::endl; + delete transition; + continue; + } + + transition->index = _lastTransIndex++; + _perfTransUsed++; + + // remember this conflict-free set + // std::cerr << "New conflict-free subset: " << transition->transitionId << ":" << transition->eventDesc << std::endl; + outMap[transition->transitionId] = transition; + } + return; +} + +void ChartToFSM::explode() { + + std::list > statesRemaining; + statesRemaining.push_back(std::make_pair(_currGlobalTransition, new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix, this))); + + // add all invokers for initial transition + for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { + NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); + for (unsigned int j = 0; j < invokes.size(); j++) { + invoke(Element(invokes[j])); + } + } + _statesToInvoke = NodeSet(); + + /** + We need to resolve the recursion in order not to exhaust the stack + */ + + // append new global states and pop from front + while(statesRemaining.size() > 0) { + DUMP_STATS(0); + + GlobalState* globalState = statesRemaining.front().second; + _currGlobalTransition = statesRemaining.front().first; + statesRemaining.pop_front(); + + // used to be conditionalized, we will just assume + assert(_currGlobalTransition); + + if (_globalConf.find(globalState->stateId) != _globalConf.end()) { + if (_currGlobalTransition->isEventless && + !_skipEventChainCalculations && + (_maxEventRaisedChain != UNDECIDABLE || _maxEventSentChain != UNDECIDABLE)) { + // we arrived via a spontaneaous transition, do we need to update? + updateRaisedAndSendChains(_globalConf[globalState->stateId], _currGlobalTransition, std::set()); + } + delete globalState; + _perfStatesSkippedTotal++; + _perfStatesSkippedProcessed++; + continue; // we have already been here + } + + _perfStatesProcessed++; + + _configuration = globalState->getActiveStates(); + _alreadyEntered = globalState->getAlreadyEnteredStates(); + _historyValue = globalState->getHistoryStates(); + + // remember as global configuration + _globalConf[globalState->stateId] = globalState; + _globalConf[globalState->stateId]->index = _lastStateIndex++; + + if(_globalConf[globalState->stateId]->isFinal) { + if (_activeConf.find(globalState->activeId) == _activeConf.end()) { + assert(globalState->activeIndex == -1); + globalState->activeIndex = _lastActiveIndex++; + _activeConf[globalState->activeId] = globalState; // remember as active configuration + exitInterpreter(); + } + continue; // done in this branch + } + + if (_activeConf.find(globalState->activeId) != _activeConf.end()) { + // we already know these transition sets, just copy over + std::list::iterator sortTransIter = _activeConf[globalState->activeId]->sortedOutgoing.begin(); + while(sortTransIter != _activeConf[globalState->activeId]->sortedOutgoing.end()) { + globalState->sortedOutgoing.push_back(GlobalTransition::copyWithoutExecContent(*sortTransIter)); + globalState->sortedOutgoing.back()->index = _lastTransIndex++; + _perfTransUsed++; + sortTransIter++; + } + _perfStatesCachedTotal++; + _perfStatesCachedProcessed++; + + } else { + // we need to calculate the potential optimal transition sets + std::map transitionSets; + getPotentialTransitionsForConf(refsToStates(globalState->activeStatesRefs), transitionSets); + + // reduce and sort transition sets + for(std::map::iterator transSetIter = transitionSets.begin(); + transSetIter != transitionSets.end(); + transSetIter++) { + globalState->sortedOutgoing.push_back(transSetIter->second); + } + + globalState->sortedOutgoing.sort(PtrComp); +// globalState->sortedOutgoing.unique(hasUnconditionalSuperset); +// globalState->sortedOutgoing.unique(hasEarlierUnconditionalMatch); + // unique is not quite like what we need, but it was a start + if (_keepInvalidTransitions) { + globalState->sortedOutgoing = redundantMark(globalState->sortedOutgoing); + } else { + globalState->sortedOutgoing.unique(hasUnconditionalSuperset); + globalState->sortedOutgoing.unique(hasEarlierUnconditionalMatch); + globalState->sortedOutgoing = redundantRemove(globalState->sortedOutgoing); + } +// globalState->sortedOutgoing = redundantRemove(globalState->sortedOutgoing); +// globalState->sortedOutgoing = redundantRemove(globalState->sortedOutgoing); + +// std::cout << globalState->sortedOutgoing.size() << std::endl; + + assert(_activeConf.find(globalState->activeId) == _activeConf.end()); + assert(globalState->activeIndex == -1); + globalState->activeIndex = _lastActiveIndex++; + _activeConf[globalState->activeId] = globalState; + } + + // take every transition set and append resulting new state + for(std::list::iterator transIter = globalState->sortedOutgoing.begin(); + transIter != globalState->sortedOutgoing.end(); + transIter++) { + + GlobalTransition* incomingTrans = _currGlobalTransition; + GlobalTransition* outgoingTrans = *transIter; + + outgoingTrans->source = globalState->stateId; + + if (_keepInvalidTransitions && !outgoingTrans->isValid) + continue; + + _currGlobalTransition = outgoingTrans; + + microstep(refsToTransitions(outgoingTrans->transitionRefs)); + assert(isLegalConfiguration(_configuration)); + + _perfMicroStepProcessed++; + _perfMicroStepTotal++; + + // if outgoing transition is spontaneous, add number of events to chain + if (outgoingTrans->isEventless && + !_skipEventChainCalculations && + (_maxEventRaisedChain != UNDECIDABLE || _maxEventSentChain != UNDECIDABLE)) { + outgoingTrans->eventsChainRaised = MIN(incomingTrans->eventsChainRaised + outgoingTrans->eventsRaised, UNDECIDABLE); + outgoingTrans->eventsChainSent = MIN(incomingTrans->eventsChainSent + outgoingTrans->eventsSent, UNDECIDABLE); + + if (outgoingTrans->eventsChainRaised > _maxEventRaisedChain) + _maxEventRaisedChain = outgoingTrans->eventsChainRaised; + if (outgoingTrans->eventsChainSent > _maxEventSentChain) + _maxEventSentChain = outgoingTrans->eventsChainSent; + + } + + statesRemaining.push_back(std::make_pair(outgoingTrans, new GlobalState(_configuration, _alreadyEntered, _historyValue, _nsInfo.xmlNSPrefix, this))); + + // add all invokers + for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { + NodeSet invokes = filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); + for (unsigned int j = 0; j < invokes.size(); j++) { + invoke(Element(invokes[j])); + } + } + _statesToInvoke = NodeSet(); + + // remember that the last transition lead here + outgoingTrans->destination = statesRemaining.back().second->stateId; + outgoingTrans->activeDestination = statesRemaining.back().second->activeId; + // reset state for next transition set + _configuration = globalState->getActiveStates(); + _alreadyEntered = globalState->getAlreadyEnteredStates(); + _historyValue = globalState->getHistoryStates(); + } + } + +} + +void ChartToFSM::updateRaisedAndSendChains(GlobalState* state, GlobalTransition* source, std::set visited) { + for (std::list::iterator transIter = state->sortedOutgoing.begin(); transIter != state->sortedOutgoing.end(); transIter++) { + GlobalTransition* transition = *transIter; + + if (!transition->isEventless) + continue; // we do not care for eventful transitions + + // source leads to spontaneous transition -> update event chains + bool eventChainsNeedUpdated = false; + + if (visited.find(transition) != visited.end()) { + // potential spontaneous transition cycle! + if (transition->eventsChainRaised > 0) + _maxEventRaisedChain = UNDECIDABLE; + if (transition->eventsChainSent > 0) + _maxEventSentChain = UNDECIDABLE; + return; + } + + // UNDECIDABLE means "undecidable / endless" + + // will source increase our event chain? + if (transition->eventsChainRaised != UNDECIDABLE && + transition->eventsChainRaised < source->eventsChainRaised + transition->eventsRaised) { + // taking transition after source causes more events in chain + transition->eventsChainRaised = MIN(source->eventsChainRaised + transition->eventsRaised, UNDECIDABLE); + eventChainsNeedUpdated = true; + } + if (transition->eventsChainSent != UNDECIDABLE && + transition->eventsChainSent < source->eventsChainSent + transition->eventsSent) { + // taking transition after source causes more events in chain + transition->eventsChainSent = MIN(source->eventsChainSent + transition->eventsSent, UNDECIDABLE); + eventChainsNeedUpdated = true; + } + + if (eventChainsNeedUpdated && + transition->destination.length() > 0 && + _globalConf.find(transition->destination) != _globalConf.end()) { + + visited.insert(transition); + // iterate all spontaneous transitions in destination and update event chains + updateRaisedAndSendChains(_globalConf[transition->destination], transition, visited); + } + + if (transition->eventsChainRaised > _maxEventRaisedChain) + _maxEventRaisedChain = transition->eventsChainRaised; + if (transition->eventsChainSent > _maxEventSentChain) + _maxEventSentChain = transition->eventsChainSent; + } +} + +uint32_t ChartToFSM::getMinInternalQueueLength(uint32_t defaultVal) { + if (_maxEventRaisedChain != UNDECIDABLE) + return _maxEventRaisedChain + _doneEventRaiseTolerance; + return defaultVal; +} + +uint32_t ChartToFSM::getMinExternalQueueLength(uint32_t defaultVal) { + if (_maxEventSentChain != UNDECIDABLE) + return _maxEventSentChain; + return defaultVal; +} + +void ChartToFSM::reassembleFromFlat() { + LOG(ERROR) << "Cannot flatten flat SCXML document"; + abort(); +} + +Arabica::XPath::NodeSet ChartToFSM::refsToStates(const std::set& stateRefs) { + NodeSet states; + for (std::set::const_iterator stateIter = stateRefs.begin(); stateIter != stateRefs.end(); stateIter++) { + states.push_back(indexedStates[*stateIter]); + } + return states; +} + +Arabica::XPath::NodeSet ChartToFSM::refsToTransitions(const std::set& transRefs) { + NodeSet transitions; + for (std::set::const_iterator transIter = transRefs.begin(); transIter != transRefs.end(); transIter++) { + transitions.push_back(indexedTransitions[*transIter]); + } + return transitions; +} + +void ChartToFSM::beforeMicroStep(Interpreter interpreter) { +} +void ChartToFSM::onStableConfiguration(Interpreter interpreter) { +} +void ChartToFSM::beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { + GlobalTransition::Action action; + action.exited = state; + _currGlobalTransition->actions.push_back(action); +} +void ChartToFSM::beforeEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { + GlobalTransition::Action action; + action.entered = state; + _currGlobalTransition->actions.push_back(action); +} +void ChartToFSM::beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) { +} + +GlobalState::GlobalState(const Arabica::XPath::NodeSet& activeStates_, + const Arabica::XPath::NodeSet& alreadyEnteredStates_, // we need to remember for binding=late + const std::map >& historyStates_, + const std::string& xmlNSPrefix, + ChartToFSM* flattener) { + interpreter = flattener; + activeIndex = -1; + + // take references + for (int i = 0; i < activeStates_.size(); i++) { + activeStatesRefs.insert(strTo(ATTR_CAST(activeStates_[i], "index"))); + } + + for (int i = 0; i < alreadyEnteredStates_.size(); i++) { + alreadyEnteredStatesRefs.insert(strTo(ATTR_CAST(alreadyEnteredStates_[i], "index"))); + } + + for (std::map >::const_iterator histIter = historyStates_.begin(); histIter != historyStates_.end(); histIter++) { + for (int i = 0; i < histIter->second.size(); i++) { + historyStatesRefs[histIter->first].insert(strTo(ATTR_CAST(histIter->second[i], "index"))); + } + } + + isFinal = false; + + // is state this final? + for(int i = 0; i < activeStates_.size(); i++) { + Arabica::DOM::Element state = Arabica::DOM::Element(activeStates_[i]); + Arabica::DOM::Element parentElem = (Arabica::DOM::Element)state.getParentNode(); + if(InterpreterImpl::isFinal(state) && iequals(parentElem.getTagName(), xmlNSPrefix + "scxml")) { + isFinal = true; + break; + } + } + + FlatStateIdentifier flatStateId(getActiveStates(), getAlreadyEnteredStates(), getHistoryStates()); + stateId = flatStateId.getStateId(); + activeId = flatStateId.getFlatActive(); +} + +GlobalTransition* GlobalTransition::copyWithoutExecContent(GlobalTransition* other) { + GlobalTransition* newTrans = new GlobalTransition(*other); + newTrans->actions.clear(); + newTrans->historyBase = other; + other->historyTrans.push_back(newTrans); + return newTrans; +} + +GlobalTransition::GlobalTransition(const Arabica::XPath::NodeSet& transitionSet, DataModel dataModel, ChartToFSM* flattener) { + interpreter = flattener; + + eventsRaised = 0; + eventsSent = 0; + eventsChainRaised = 0; + eventsChainSent = 0; + historyBase = NULL; + + for (int i = 0; i < transitionSet.size(); i++) { + transitionRefs.insert(strTo(ATTR_CAST(transitionSet[i], "index"))); + } + + std::ostringstream setId; // also build id for subset + std::string seperator = ""; + for (std::set::iterator transIter = transitionRefs.begin(); transIter != transitionRefs.end(); transIter++) { + setId << seperator << *transIter; + seperator = "-"; + } + transitionId = setId.str(); + + hasExecutableContent = false; + isValid = true; + isEventless = true; + +#if 0 + std::cerr << "################" << std::endl; + for (int i = 0; i < transitions.size(); i++) { + std::cerr << transitions[i] << std::endl; + } + std::cerr << "################" << std::endl; +#endif + + // first establish whether this is a valid set + + /** + * Can these events event occur together? They can't if: + * 1. event / eventless is mixed + * 2. target / targetless is mixed (?) + * 3. there is no common prefix for their event attribute + */ + + bool foundWithEvent = false; + bool foundEventLess = false; + bool foundWithTarget = false; + bool foundTargetLess = false; + + Arabica::DOM::Element withEvent; + Arabica::DOM::Element noneEvent; + Arabica::DOM::Element withTarget; + Arabica::DOM::Element noneTarget; + + for (int i = 0; i < transitionSet.size(); i++) { + Arabica::DOM::Element transElem = Arabica::DOM::Element(transitionSet[i]); + if (HAS_ATTR(transElem, "eventexpr")) { + ERROR_EXECUTION_THROW("Cannot flatten document with eventexpr attributes"); + } + if (HAS_ATTR(transElem, "event")) { + foundWithEvent = true; + withEvent = transElem; + if (foundEventLess) + break; + } else { + foundEventLess = true; + noneEvent = transElem; + if (foundWithEvent) + break; + } + if (HAS_ATTR(transElem, "target")) { + foundWithTarget = true; + withTarget = transElem; + if (foundTargetLess) + break; + } else { + foundTargetLess = true; + noneTarget = transElem; + if (foundWithTarget) + break; + } + + } + + // do not mix eventless and event transitions + if (foundEventLess && foundWithEvent) { + if (flattener->_keepInvalidTransitions) { + invalidReason = MIXES_EVENT_SPONTANEOUS; + invalidMsg = "Mixes (non-)spontaneous"; + } + isValid = false; + return; + } + + // 403c vs 229 / 403b - solved via filterChildEnabled + if (foundTargetLess && foundWithTarget) { +// isValid = false; +// return; + } + + isEventless = foundEventLess; + isTargetless = !foundWithTarget; + + // is there a set of event names that would enable this conflict-free transition set? + if (foundWithEvent) { + // get the set of longest event descriptors that will enable this transition set + eventNames = getCommonEvents(transitionSet); + if (eventNames.size() == 0) { +// LOG(INFO) << "No event will activate this conflict-free subset" << std::endl; + if (flattener->_keepInvalidTransitions) { + invalidReason = NO_COMMON_EVENT; + invalidMsg = "No common event"; + } + isValid = false; + return; + } else { + std::string seperator = ""; + for (std::list::iterator eventIter = eventNames.begin(); + eventIter != eventNames.end(); + eventIter++) { + eventDesc += seperator + *eventIter; + seperator = " "; + } + } + if (eventDesc.size() == 0) + eventDesc = "*"; + } + + // extract conditions and history targets + std::list conditions; + for (int i = 0; i < transitionSet.size(); i++) { + Arabica::DOM::Element transElem = Arabica::DOM::Element(transitionSet[i]); + // gather conditions while we are iterating anyway + if (HAS_ATTR(transElem, "cond")) { + conditions.push_back(boost::trim_copy(ATTR(transElem, "cond"))); + } + + std::list targets = InterpreterImpl::tokenizeIdRefs(ATTR(transElem, "target")); + std::list::iterator targetIter = targets.begin(); + while(targetIter != targets.end()) { +// std::cout << "// " << *targetIter << std::endl; + if (flattener->_historyTargets.find(*targetIter) != flattener->_historyTargets.end()) { + histTargets.insert(*targetIter); + } + targetIter++; + } +// std::cout << std::endl << std::endl; + } + + seperator = ""; + for (std::vector >::iterator transIter = interpreter->indexedTransitions.begin(); transIter != interpreter->indexedTransitions.end(); transIter++) { + const Element& refTrans = *transIter; + if (!HAS_ATTR(refTrans, "priority")) + continue; + if (InterpreterImpl::isMember(refTrans, transitionSet)) { + members += seperator + ATTR(refTrans, "priority"); + } else { + members += seperator; + for (int i = 0; i < ATTR(refTrans, "priority").size(); i++) { + members += " "; + } + } + seperator = " "; + } + + // if (members == " 4 6 7 ") + // std::cout << "asdfadf"; + + if (conditions.size() > 1) { + condition = dataModel.andExpressions(conditions); + if (condition.size() == 0) { + LOG(ERROR) << "Datamodel does not support to conjungate expressions!" << std::endl; + } + } else if (conditions.size() == 1) { + condition = conditions.front(); + } +} + +Arabica::XPath::NodeSet GlobalState::getActiveStates() { + return interpreter->refsToStates(activeStatesRefs); +} + +Arabica::XPath::NodeSet GlobalState::getAlreadyEnteredStates() { + return interpreter->refsToStates(alreadyEnteredStatesRefs); +} + +std::map > GlobalState::getHistoryStates() { + std::map > historyValue; + for (std::map >::iterator histIter = historyStatesRefs.begin(); histIter != historyStatesRefs.end(); histIter++) { + historyValue[histIter->first] = interpreter->refsToStates(histIter->second); + } + return historyValue; +} + + +Arabica::XPath::NodeSet GlobalTransition::getTransitions() const { + return interpreter->refsToTransitions(transitionRefs); +} + +std::list GlobalTransition::getCommonEvents(const NodeSet& transitions) { + std::list prefixes; + std::list longestPrefixes; + + for (int i = 0; i < transitions.size(); i++) { + // for every transition + std::list eventNames = InterpreterImpl::tokenizeIdRefs(ATTR_CAST(transitions[i], "event")); + + for (std::list::iterator eventNameIter = eventNames.begin(); + eventNameIter != eventNames.end(); + eventNameIter++) { + // for every event descriptor + std::string eventName = *eventNameIter; + + // remove trailing .* + if (eventName.find("*", eventName.size() - 1) != std::string::npos) + eventName = eventName.substr(0, eventName.size() - 1); + if (eventName.find(".", eventName.size() - 1) != std::string::npos) + eventName = eventName.substr(0, eventName.size() - 1); + + bool isMatching = true; + for (int j = 0; j < transitions.size(); j++) { + // check if token would activate all other transitions + if (i == j) + continue; + if (!InterpreterImpl::nameMatch(ATTR_CAST(transitions[j], "event"), eventName)) { + isMatching = false; + break; + } + } + if (isMatching) { + prefixes.push_back(eventName); + } + } + } + + // from the set of event names, remove those that are prefixes + for (std::list::iterator outerEventNameIter = prefixes.begin(); + outerEventNameIter != prefixes.end(); + outerEventNameIter++) { + for (std::list::iterator innerEventNameIter = prefixes.begin(); + innerEventNameIter != prefixes.end(); + innerEventNameIter++) { + if (!iequals(*outerEventNameIter, *innerEventNameIter) && InterpreterImpl::nameMatch(*outerEventNameIter, *innerEventNameIter)) { + goto IS_PREFIX; + } + } + longestPrefixes.push_back(*outerEventNameIter); +IS_PREFIX: + ; + } + return longestPrefixes; +} + +} diff --git a/src/uscxml/transform/ChartToFSM.h b/src/uscxml/transform/ChartToFSM.h index c4d2da3..1dc8813 100644 --- a/src/uscxml/transform/ChartToFSM.h +++ b/src/uscxml/transform/ChartToFSM.h @@ -61,6 +61,8 @@ public: } static uint64_t stateMachineComplexity(const Arabica::DOM::Element& root, Complexity::Variant variant = IGNORE_NOTHING); + static std::list > > getAllConfigurations(const Arabica::DOM::Element& root); + static std::map getTransitionHistogramm(const Arabica::DOM::Element& root); protected: static Complexity calculateStateMachineComplexity(const Arabica::DOM::Element& root); @@ -89,8 +91,8 @@ public: std::string stateId; std::string activeId; - long activeIndex; - long index; + unsigned long activeIndex; + unsigned long index; bool isFinal; ChartToFSM* interpreter; @@ -105,11 +107,40 @@ public: class USCXML_API GlobalTransition { public: + enum InvalidReason { + MIXES_EVENT_SPONTANEOUS, + NO_COMMON_EVENT, + CHILD_ENABLED, + SAME_SOURCE_STATE, + UNCONDITIONAL_SUPERSET, + UNCONDITIONAL_MATCH, + PREEMPTING_MEMBERS + }; + class Action { public: bool operator<(const Action& other) const { + if ((onEntry && !other.onEntry) || (!onEntry && other.onEntry)) + return true; + if ((raiseDone && !other.raiseDone) || (!raiseDone && other.raiseDone)) + return true; + if ((onExit && !other.onExit) || (!onExit && other.onExit)) + return true; + if ((transition && !other.transition) || (!transition && other.transition)) + return true; + if ((entered && !other.entered) || (!entered && other.entered)) + return true; + if ((exited && !other.exited) || (!exited && other.exited)) + return true; + if ((invoke && !other.invoke) || (!invoke && other.invoke)) + return true; + if ((uninvoke && !other.uninvoke) || (!uninvoke && other.uninvoke)) + return true; + if (onEntry < other.onEntry) return onEntry < other.onEntry; + if (raiseDone < other.raiseDone) + return raiseDone < other.raiseDone; if (onExit < other.onExit) return onExit < other.onExit; if (transition < other.transition) @@ -132,6 +163,8 @@ public: return !operator==(other); } + friend USCXML_API std::ostream& operator<< (std::ostream& os, const Action& action); + typedef std::list::iterator iter_t; Arabica::DOM::Element onEntry; @@ -141,6 +174,7 @@ public: Arabica::DOM::Element exited; Arabica::DOM::Element invoke; Arabica::DOM::Element uninvoke; + Arabica::DOM::Element raiseDone; }; @@ -148,6 +182,9 @@ public: static GlobalTransition* copyWithoutExecContent(GlobalTransition* other); bool isValid; // constructor will determine, calling code will delete if not + std::string invalidMsg; + InvalidReason invalidReason; + bool isEventless; // whether or not all our transitions are eventless bool isTargetless; // whether or not all our transitions are eventless bool isSubset; // there is a superset to this set @@ -189,17 +226,66 @@ protected: std::list getCommonEvents(const Arabica::XPath::NodeSet& transitions); }; +USCXML_API std::ostream& operator<< (std::ostream& os, const GlobalTransition::Action& action); + +class TransitionTreeNode { +public: + enum TransitionTreeNodeType { + TYPE_UNDEFINED, + TYPE_PARALLEL, + TYPE_NESTED, + TYPE_TRANSITION + }; + + TransitionTreeNode() + : prevTransition(NULL), + nextTransition(NULL), + firstTransition(NULL), + firstState(NULL), + parent(NULL), + type(TYPE_UNDEFINED) {} + + virtual ~TransitionTreeNode() { + for (std::list::iterator childIter = children.begin(); childIter != children.end(); childIter++) { + delete(*childIter); + } + } + + void dump(int indent = 0); + + TransitionTreeNode* prevTransition; + TransitionTreeNode* nextTransition; + Arabica::DOM::Element transition; + + Arabica::DOM::Element state; + TransitionTreeNode* firstTransition; + TransitionTreeNode* lastTransition; + TransitionTreeNode* firstState; + + TransitionTreeNode* parent; + std::list children; + std::string nodeId; + + TransitionTreeNodeType type; + + bool operator<(const TransitionTreeNode& other) const { + return nodeId < other.nodeId; + } + +}; class USCXML_API ChartToFSM : public InterpreterRC, public InterpreterMonitor { public: + ChartToFSM(const Interpreter& other); virtual ~ChartToFSM(); + void indexTransitions(); + Arabica::DOM::Document getDocument() const; // overwrite to return flat FSM + protected: - ChartToFSM(const Interpreter& other); - Arabica::DOM::Document getDocument() const; // overwrite to return flat FSM InterpreterState interpret(); - + GlobalState* _start; Arabica::DOM::Document _flatDoc; std::map _globalConf; @@ -209,13 +295,16 @@ protected: uint32_t getMinInternalQueueLength(uint32_t defaultVal); uint32_t getMinExternalQueueLength(uint32_t defaultVal); + bool _keepInvalidTransitions; + bool _transitionsFromTree; + + std::vector > indexedTransitions; + std::vector > indexedStates; + private: Arabica::XPath::NodeSet refsToStates(const std::set&); Arabica::XPath::NodeSet refsToTransitions(const std::set&); - std::vector > indexedTransitions; - std::vector > indexedStates; - // gather executable content per microstep void executeContent(const Arabica::DOM::Element& content, bool rethrow = false); @@ -235,8 +324,10 @@ private: virtual void beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing); void explode(); - void getPotentialTransitionsForConf(const Arabica::XPath::NodeSet& conf, std::map& outMap); + void getPotentialTransitionsForConfFromPowerSet(const Arabica::XPath::NodeSet& conf, std::map& outMap); + void getPotentialTransitionsForConfFromTree(const Arabica::XPath::NodeSet& conf, std::map& outMap); // void labelTransitions(); + TransitionTreeNode* buildTransTree(const Arabica::DOM::Element& root, const std::string& nodeId); void indexTransitions(const Arabica::DOM::Element& root); void annotateRaiseAndSend(const Arabica::DOM::Element& root); @@ -256,12 +347,14 @@ private: uint64_t _perfTransTotal; uint64_t _perfTransUsed; uint64_t _perfStatesProcessed; + uint64_t _perfStatesTotal; uint64_t _perfStatesSkippedProcessed; uint64_t _perfStatesSkippedTotal; uint64_t _perfStatesCachedProcessed; uint64_t _perfStatesCachedTotal; uint64_t _perfMicroStepProcessed; uint64_t _perfMicroStepTotal; + uint64_t _perfStackSize; uint64_t _lastTimeStamp; size_t _lastTransientStateId; @@ -277,6 +370,10 @@ private: size_t _doneEventRaiseTolerance; GlobalTransition* _currGlobalTransition; + std::map > _confToTransitions; + + TransitionTreeNode* _transTree; + std::map, TransitionTreeNode*> _stateToTransTreeNode; friend class GlobalTransition; friend class GlobalState; diff --git a/src/uscxml/transform/ChartToFSM.h.new b/src/uscxml/transform/ChartToFSM.h.new new file mode 100644 index 0000000..2ec2f44 --- /dev/null +++ b/src/uscxml/transform/ChartToFSM.h.new @@ -0,0 +1,303 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#ifndef CHARTTOFSM_H_IOKPYEBY +#define CHARTTOFSM_H_IOKPYEBY + +#include "uscxml/DOMUtils.h" +#include "uscxml/interpreter/InterpreterRC.h" +#include +#include +#include +#include +#include + +namespace uscxml { +class GlobalState; +class GlobalTransition; +class ChartToFSM; + +class USCXML_API Complexity { +public: + + enum Variant { + IGNORE_NOTHING, + IGNORE_HISTORY, + IGNORE_NESTED_DATA, + IGNORE_HISTORY_AND_NESTED_DATA, + }; + + Complexity() : value(0), nestedData(0) {} + Complexity(uint64_t value) : value(value), nestedData(0) {} + + Complexity& operator+=(const Complexity& rhs) { + value += rhs.value; + nestedData += rhs.nestedData; + history.insert(history.end(), rhs.history.begin(), rhs.history.end()); + return *this; + } + + Complexity& operator*=(const Complexity& rhs) { + value *= rhs.value; + nestedData += rhs.nestedData; + history.insert(history.end(), rhs.history.begin(), rhs.history.end()); + return *this; + } + + static uint64_t stateMachineComplexity(const Arabica::DOM::Element& root, Complexity::Variant variant = IGNORE_NOTHING); + +protected: + static Complexity calculateStateMachineComplexity(const Arabica::DOM::Element& root); + + uint64_t value; + uint64_t nestedData; + std::list history; +}; + +class USCXML_API GlobalState { +public: + + GlobalState() {} + GlobalState(const Arabica::DOM::Node& globalState); + GlobalState(const Arabica::XPath::NodeSet& activeStates, + const Arabica::XPath::NodeSet& alreadyEnteredStates, // we need to remember for binding=late + const std::map >& historyStates, + const std::string& xmlNSPrefix, + ChartToFSM* flattener); + + std::set activeStatesRefs; + std::set alreadyEnteredStatesRefs; + std::map > historyStatesRefs; + + std::list sortedOutgoing; + std::string stateId; + std::string activeId; + + unsigned long activeIndex; + unsigned long index; + bool isFinal; + + ChartToFSM* interpreter; + + Arabica::XPath::NodeSet getActiveStates(); + Arabica::XPath::NodeSet getAlreadyEnteredStates(); + std::map > getHistoryStates(); + +}; + + +class USCXML_API GlobalTransition { +public: + enum InvalidReason { + MIXES_EVENT_SPONTANEOUS, + NO_COMMON_EVENT, + CHILD_ENABLED, + SAME_SOURCE_STATE, + UNCONDITIONAL_SUPERSET, + UNCONDITIONAL_MATCH, + PREEMPTING_MEMBERS + }; + + class Action { + public: + bool operator<(const Action& other) const { + if (onEntry < other.onEntry) + return onEntry < other.onEntry; + if (onExit < other.onExit) + return onExit < other.onExit; + if (transition < other.transition) + return transition < other.transition; + if (entered < other.entered) + return entered < other.entered; + if (exited < other.exited) + return exited < other.exited; + if (invoke < other.invoke) + return invoke < other.invoke; + if (uninvoke < other.uninvoke) + return uninvoke < other.uninvoke; + return false; + } + + bool operator==(const Action& other) const { + return !(other < *this) && !(*this < other); + } + bool operator!=(const Action& other) const { + return !operator==(other); + } + + typedef std::list::iterator iter_t; + + Arabica::DOM::Element onEntry; + Arabica::DOM::Element onExit; + Arabica::DOM::Element transition; + Arabica::DOM::Element entered; + Arabica::DOM::Element exited; + Arabica::DOM::Element invoke; + Arabica::DOM::Element uninvoke; + + }; + + GlobalTransition(const Arabica::XPath::NodeSet& transitions, DataModel dataModel, ChartToFSM* flattener); + static GlobalTransition* copyWithoutExecContent(GlobalTransition* other); + + bool isValid; // constructor will determine, calling code will delete if not + std::string invalidMsg; + InvalidReason invalidReason; + + bool isEventless; // whether or not all our transitions are eventless + bool isTargetless; // whether or not all our transitions are eventless + bool isSubset; // there is a superset to this set + bool hasExecutableContent; + + uint32_t eventsRaised; // internal events this transition will raise + uint32_t eventsSent; // external events this transition will send + uint32_t eventsChainRaised; // maximum number of internal events raised when taking this transition in a chain + uint32_t eventsChainSent; // maximum number of external events raised when taking this transition in a chain + + std::set startTransitionRefs; // indices of eventful transitions that might trigger this transition + + std::set transitionRefs; // indizes of constituting transitions + Arabica::XPath::NodeSet getTransitions() const; + + std::list eventNames; // the list of longest event names that will enable this set + std::string eventDesc; // space-seperated eventnames for convenience + std::string condition; // conjunction of all the set's conditions + std::string members; // a convenience string listing all constituting transitions + + // executable content we gathered when we took the transition + std::list actions; + + std::string transitionId; + std::string source; + std::string destination; + std::string activeDestination; + + GlobalTransition* historyBase; // we have a base transition that left our source with no history (-> we are a history transition) + std::list historyTrans; // transitions from the same source but different histories + std::set histTargets; // constituting targets to history states + + long index; + ChartToFSM* interpreter; + + bool operator< (const GlobalTransition& other) const; + +protected: + std::list getCommonEvents(const Arabica::XPath::NodeSet& transitions); +}; + + +class USCXML_API ChartToFSM : public InterpreterRC, public InterpreterMonitor { +public: + ChartToFSM(const Interpreter& other); + virtual ~ChartToFSM(); + + void indexTransitions(); + Arabica::DOM::Document getDocument() const; // overwrite to return flat FSM + +protected: + + InterpreterState interpret(); + + GlobalState* _start; + Arabica::DOM::Document _flatDoc; + std::map _globalConf; + std::map _activeConf; // potentially enabled transition sets per active configuration + std::map > _historyTargets; // ids of all history states + + uint32_t getMinInternalQueueLength(uint32_t defaultVal); + uint32_t getMinExternalQueueLength(uint32_t defaultVal); + + bool _keepInvalidTransitions; + +private: + Arabica::XPath::NodeSet refsToStates(const std::set&); + Arabica::XPath::NodeSet refsToTransitions(const std::set&); + + std::vector > indexedTransitions; + std::vector > indexedStates; + + // gather executable content per microstep + void executeContent(const Arabica::DOM::Element& content, bool rethrow = false); + + // invoke and uninvoke + virtual void invoke(const Arabica::DOM::Element& element); + virtual void cancelInvoke(const Arabica::DOM::Element& element); + + // override to do nothing + void send(const Arabica::DOM::Element& element) {} + void internalDoneSend(const Arabica::DOM::Element& state); + + // InterpreterMonitor + virtual void beforeMicroStep(Interpreter interpreter); + virtual void onStableConfiguration(Interpreter interpreter); + virtual void beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing); + virtual void beforeEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing); + virtual void beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing); + + void explode(); + void getPotentialTransitionsForConf(const Arabica::XPath::NodeSet& conf, std::map& outMap); +// void labelTransitions(); + + void indexTransitions(const Arabica::DOM::Element& root); + void annotateRaiseAndSend(const Arabica::DOM::Element& root); + bool hasForeachInBetween(const Arabica::DOM::Node& ancestor, const Arabica::DOM::Node& child); + void updateRaisedAndSendChains(GlobalState* state, GlobalTransition* source, std::set visited); + + void reassembleFromFlat(); + + std::list sortTransitions(std::list list); + + // we need this static as we use it in a sort function + static std::map, Arabica::DOM::Node > _transParents; + + static bool filterSameState(const Arabica::XPath::NodeSet& transitions); + + uint64_t _perfTransProcessed; + uint64_t _perfTransTotal; + uint64_t _perfTransUsed; + uint64_t _perfStatesProcessed; + uint64_t _perfStatesSkippedProcessed; + uint64_t _perfStatesSkippedTotal; + uint64_t _perfStatesCachedProcessed; + uint64_t _perfStatesCachedTotal; + uint64_t _perfMicroStepProcessed; + uint64_t _perfMicroStepTotal; + uint64_t _lastTimeStamp; + + size_t _lastTransientStateId; + size_t _lastStateIndex; + size_t _lastActiveIndex; + size_t _lastTransIndex; + + bool _alreadyFlat; + + bool _skipEventChainCalculations; + size_t _maxEventSentChain; + size_t _maxEventRaisedChain; + size_t _doneEventRaiseTolerance; + + GlobalTransition* _currGlobalTransition; + + friend class GlobalTransition; + friend class GlobalState; +}; + +} + +#endif /* end of include guard: CHARTTOFSM_H_IOKPYEBY */ diff --git a/src/uscxml/transform/ChartToFlatSCXML.cpp b/src/uscxml/transform/ChartToFlatSCXML.cpp index d9e2586..c554218 100644 --- a/src/uscxml/transform/ChartToFlatSCXML.cpp +++ b/src/uscxml/transform/ChartToFlatSCXML.cpp @@ -18,6 +18,7 @@ */ #include "ChartToFlatSCXML.h" +#include "uscxml/Convenience.h" #include "uscxml/transform/FlatStateIdentifier.h" #define CREATE_TRANSIENT_STATE_WITH_CHILDS(stateId) \ @@ -25,6 +26,10 @@ if (childs.size() > 0) { \ Element transientState = _flatDoc.createElementNS(_nsInfo.nsURL, "state"); \ _nsInfo.setPrefix(transientState);\ transientState.setAttribute("transient", "true"); \ + for (std::list >::iterator commentIter = pendingComments.begin(); commentIter != pendingComments.end(); commentIter++) {\ + transientState.appendChild(*commentIter); \ + }\ + pendingComments.clear(); \ if (stateId.length() > 0) \ transientState.setAttribute("id", stateId); \ for (int i = 0; i < childs.size(); i++) { \ @@ -41,7 +46,7 @@ using namespace Arabica::DOM; using namespace Arabica::XPath; ChartToFlatSCXML::operator Interpreter() { - if (!HAS_ATTR(_scxml, "flat") || !DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) { + if (!HAS_ATTR(_scxml, "flat") || !stringIsTrue(ATTR(_scxml, "flat"))) { createDocument(); } @@ -53,36 +58,37 @@ Transformer ChartToFlatSCXML::transform(const Interpreter& other) { } void ChartToFlatSCXML::writeTo(std::ostream& stream) { - if (!HAS_ATTR(_scxml, "flat") || !DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) { + if (!HAS_ATTR(_scxml, "flat") || !stringIsTrue(ATTR(_scxml, "flat"))) { createDocument(); } - - char* withDebugAttrs = getenv("USCXML_FLAT_WITH_DEBUG_ATTRS"); - if (withDebugAttrs == NULL || !DOMUtils::attributeIsTrue(withDebugAttrs)) { - // remove all debug attributes - NodeSet elementNodes = filterChildType(Node_base::ELEMENT_NODE, _scxml, true); - for (int i = 0; i < elementNodes.size(); i++) { - Element element(elementNodes[i]); - if (HAS_ATTR(element, "send")) - element.removeAttribute("send"); - if (HAS_ATTR(element, "raise")) - element.removeAttribute("raise"); - if (HAS_ATTR(element, "members")) - element.removeAttribute("members"); - if (HAS_ATTR(element, "ref")) - element.removeAttribute("ref"); - if (HAS_ATTR(element, "final-target")) - element.removeAttribute("final-target"); - } + + // remove all debug attributes + NodeSet elementNodes = filterChildType(Node_base::ELEMENT_NODE, _scxml, true); + for (int i = 0; i < elementNodes.size(); i++) { + Element element(elementNodes[i]); + if (!envVarIsTrue("USCXML_ANNOTATE_GLOBAL_TRANS_SENDS") && HAS_ATTR(element, "send")) + element.removeAttribute("send"); + if (!envVarIsTrue("USCXML_ANNOTATE_GLOBAL_TRANS_RAISES") && HAS_ATTR(element, "raise")) + element.removeAttribute("raise"); + if (!envVarIsTrue("USCXML_ANNOTATE_GLOBAL_TRANS_MEMBERS") && HAS_ATTR(element, "members")) + element.removeAttribute("members"); + if (!envVarIsTrue("USCXML_ANNOTATE_GLOBAL_TRANS_PRIO") && HAS_ATTR(element, "priority")) + element.removeAttribute("priority"); + if (!envVarIsTrue("USCXML_ANNOTATE_GLOBAL_STATE_STEP") && HAS_ATTR(element, "step")) + element.removeAttribute("step"); + if (HAS_ATTR(element, "final-target")) + element.removeAttribute("final-target"); } + if (envVarIsTrue("USCXML_FLAT_FSM_METRICS_ONLY")) + return; stream << _scxml; } void ChartToFlatSCXML::createDocument() { - if (HAS_ATTR(_scxml, "flat") && DOMUtils::attributeIsTrue(ATTR(_scxml, "flat"))) + if (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat"))) return; { @@ -99,6 +105,9 @@ void ChartToFlatSCXML::createDocument() { if (_start == NULL) interpret(); // only if not already flat! + if (envVarIsTrue("USCXML_FLAT_FSM_METRICS_ONLY")) + return; + Element _origSCXML = _scxml; _scxml = _flatDoc.createElementNS(_nsInfo.nsURL, "scxml"); @@ -190,7 +199,7 @@ void ChartToFlatSCXML::appendGlobalStateNode(GlobalState* globalState) { Element state = _flatDoc.createElementNS(_nsInfo.nsURL, "state"); _nsInfo.setPrefix(state); - state.setAttribute("ref", toStr(globalState->index)); + state.setAttribute("step", toStr(globalState->index)); state.setAttribute("id", globalState->stateId); if (globalState->isFinal) @@ -247,13 +256,17 @@ Node ChartToFlatSCXML::globalTransitionToNode(GlobalTransition* glo // gather content for new transient state NodeSet childs; + + // aggregated entering / exiting to avoid states without childs while still labeling + std::list > pendingComments; + // iterate all actions taken during the transition for (std::list::iterator actionIter = globalTransition->actions.begin(); actionIter != globalTransition->actions.end(); actionIter++) { if (actionIter->transition) { - // DETAIL_EXEC_CONTENT(transition, actionIter); + // DETAIL_EXEC_CONTENT(transition, actionIter); Element onexit = _flatDoc.createElementNS(_nsInfo.nsURL, "onexit"); _nsInfo.setPrefix(onexit); @@ -264,29 +277,48 @@ Node ChartToFlatSCXML::globalTransitionToNode(GlobalTransition* glo child = child.getNextSibling(); } // only append if there is something done - if (onexit.hasChildNodes()) + std::stringstream commentSS; + commentSS << " Transition in '" << ATTR_CAST(getSourceState(actionIter->transition), "id") << "'"; + if (HAS_ATTR(actionIter->transition, "event")) + commentSS << " for event '" << ATTR(actionIter->transition, "event") << "'"; + if (HAS_ATTR(actionIter->transition, "cond")) + commentSS << " with condition '" << ATTR(actionIter->transition, "cond") << "'"; + if (HAS_ATTR(actionIter->transition, "target")) + commentSS << " to target '" << ATTR(actionIter->transition, "target") << "'"; + commentSS << " "; + + if (onexit.hasChildNodes()) { + if (envVarIsTrue("USCXML_ANNOTATE_VERBOSE_COMMENTS")) + childs.push_back(_flatDoc.createComment(commentSS.str())); childs.push_back(onexit); + } else { + if (envVarIsTrue("USCXML_ANNOTATE_VERBOSE_COMMENTS")) + pendingComments.push_back(_flatDoc.createComment(commentSS.str())); + } continue; } if (actionIter->onExit) { - // DETAIL_EXEC_CONTENT(onExit, actionIter); + // DETAIL_EXEC_CONTENT(onExit, actionIter); childs.push_back(actionIter->onExit); continue; } if (actionIter->onEntry) { - // DETAIL_EXEC_CONTENT(onEntry, actionIter); + // DETAIL_EXEC_CONTENT(onEntry, actionIter); childs.push_back(actionIter->onEntry); continue; } - + + if (actionIter->raiseDone) { + // DETAIL_EXEC_CONTENT(raiseDone, actionIter); + childs.push_back(actionIter->raiseDone); + continue; + } + if (actionIter->invoke) { - // DETAIL_EXEC_CONTENT(invoke, actionIter); - if (!globalTransition->isTargetless) { - // CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)); - } + // DETAIL_EXEC_CONTENT(invoke, actionIter); Element invokeElem = Element(actionIter->invoke); invokeElem.setAttribute("persist", "true"); childs.push_back(invokeElem); @@ -294,7 +326,7 @@ Node ChartToFlatSCXML::globalTransitionToNode(GlobalTransition* glo } if (actionIter->uninvoke) { - // DETAIL_EXEC_CONTENT(uninvoke, actionIter); + // DETAIL_EXEC_CONTENT(uninvoke, actionIter); Element uninvokeElem = _flatDoc.createElementNS(_nsInfo.nsURL, "uninvoke"); _nsInfo.setPrefix(uninvokeElem); @@ -315,17 +347,27 @@ Node ChartToFlatSCXML::globalTransitionToNode(GlobalTransition* glo } if (actionIter->exited) { - // std::cerr << " exited(" << ATTR_CAST(actionIter->exited, "id") << ")"; currActiveStates.remove(ATTR_CAST(actionIter->exited, "id")); if (childs.size() > 0) { + if (envVarIsTrue("USCXML_ANNOTATE_VERBOSE_COMMENTS")) + childs.push_back(_flatDoc.createComment(" Exiting " + ATTR_CAST(actionIter->exited, "id") + " ")); CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)); // create a new transient state to update its id + } else { + // enqueue for next actual state + if (envVarIsTrue("USCXML_ANNOTATE_VERBOSE_COMMENTS")) + pendingComments.push_back(_flatDoc.createComment(" Exiting " + ATTR_CAST(actionIter->exited, "id") + " ")); } } if (actionIter->entered) { - // std::cerr << " entered(" << ATTR_CAST(actionIter->entered, "id") << ")"; - if (childs.size() > 0) + if (childs.size() > 0) { + if (envVarIsTrue("USCXML_ANNOTATE_VERBOSE_COMMENTS")) + childs.push_back(_flatDoc.createComment(" Entering " + ATTR_CAST(actionIter->entered, "id") + " ")); CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)); // create a new transient state to update its id + } else { + if (envVarIsTrue("USCXML_ANNOTATE_VERBOSE_COMMENTS")) + pendingComments.push_back(_flatDoc.createComment(" Entering " + ATTR_CAST(actionIter->entered, "id") + " ")); + } currActiveStates.push_back(ATTR_CAST(actionIter->entered, "id")); // we entered a new child - check if it has a datamodel and we entered for the first time @@ -336,22 +378,8 @@ Node ChartToFlatSCXML::globalTransitionToNode(GlobalTransition* glo } } } - if (!globalTransition->isTargetless) { - // CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)) - } } - // std::cerr << std::endl; - - // if (globalTransition->isTargetless) { - // for (int i = 0; i < childs.size(); i++) { - // Node imported = _flatDoc.importNode(childs[i], true); - // transition.appendChild(imported); - // // CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)) - // } - // return transition; - // } - CREATE_TRANSIENT_STATE_WITH_CHILDS(FlatStateIdentifier::toStateId(currActiveStates)) if (transientStateChain.size() > 0) { @@ -383,7 +411,20 @@ Node ChartToFlatSCXML::globalTransitionToNode(GlobalTransition* glo // last one points to actual target assert(prevExitTransitionElem); prevExitTransitionElem.setAttribute("target", globalTransition->destination); +#if 0 + } else if (transientStateChain.size() == 1) { + Element transientStateElem = Element(transientStateChain[0]); + transientStateElem.setAttribute("onlyOne", "yes!"); + Element exitTransition = _flatDoc.createElementNS(_nsInfo.nsURL, "transition"); + _nsInfo.setPrefix(exitTransition); + exitTransition.setAttribute("target", globalTransition->destination); + + transientStateElem.appendChild(exitTransition); + + _scxml.appendChild(transientStateElem); + transition.setAttribute("target", transientStateElem.getAttribute("id")); +#endif } else { transition.setAttribute("target", globalTransition->destination); } diff --git a/src/uscxml/transform/ChartToMinimalSCXML.cpp b/src/uscxml/transform/ChartToMinimalSCXML.cpp index 270794d..ecfa12b 100644 --- a/src/uscxml/transform/ChartToMinimalSCXML.cpp +++ b/src/uscxml/transform/ChartToMinimalSCXML.cpp @@ -36,7 +36,7 @@ Transformer ChartToMinimalSCXML::transform(const Interpreter& other) { return boost::shared_ptr(new ChartToMinimalSCXML(other)); } -ChartToMinimalSCXML::ChartToMinimalSCXML(const Interpreter& other) : TransformerImpl(), _retainAsComments(false) { +ChartToMinimalSCXML::ChartToMinimalSCXML(const Interpreter& other) : TransformerImpl(), _retainAsComments(false), _step(1) { cloneFrom(other.getImpl()); // a bit messy but needed for SCXML IO Processor with session id target @@ -85,10 +85,7 @@ void ChartToMinimalSCXML::writeTo(std::ostream& stream) { } char* waitForEnv = getenv("USCXML_MINIMIZE_WAIT_MS"); - char* waitForCmpl = getenv("USCXML_MINIMIZE_WAIT_FOR_COMPLETION"); - char* retainAsComments = getenv("USCXML_MINIMIZE_RETAIN_AS_COMMENTS"); - if (retainAsComments != NULL && DOMUtils::attributeIsTrue(retainAsComments)) - _retainAsComments = true; + _retainAsComments = envVarIsTrue("USCXML_MINIMIZE_RETAIN_AS_COMMENTS"); long waitFor = -1; @@ -100,7 +97,7 @@ void ChartToMinimalSCXML::writeTo(std::ostream& stream) { } } - if (waitForCmpl != NULL && DOMUtils::attributeIsTrue(waitForCmpl)) { + if (envVarIsTrue("USCXML_MINIMIZE_WAIT_FOR_COMPLETION")) { interpret(); } else { start(); @@ -181,14 +178,15 @@ void ChartToMinimalSCXML::removeUnvisited(Arabica::DOM::Node& node) // detach unvisited nodes from DOM if (_visited.find(node) == _visited.end()) { - std::cout << DOMUtils::xPathForNode(node) << std::endl; + std::cerr << DOMUtils::xPathForNode(node) << std::endl; if (_retainAsComments) { std::stringstream oldContent; oldContent << node; node.getParentNode().replaceChild(_document.createComment(boost::replace_all_copy(oldContent.str(),"--", "-")), node); } else { // removeChildren is not working as expected - node.getParentNode().replaceChild(_document.createTextNode(""), node); +// node.getParentNode().replaceChild(_document.createTextNode(""), node); + node.getParentNode().removeChild(node); } return; } @@ -231,11 +229,29 @@ void ChartToMinimalSCXML::beforeTakingTransition(Interpreter interpreter, const markAsVisited(Arabica::DOM::Element(targets[i])); } markAsVisited(transition); + + std::stringstream commentSS; + if (HAS_ATTR(transition, "event")) { + commentSS << " Step #" << _step++ << " - transition taken for event '" << _currEvent.name << "' "; + } else { + commentSS << " Step #" << _step++ << " - spontaneous transition taken "; + } + if (envVarIsTrue("USCXML_ANNOTATE_PROGRESS")) + transition.getParentNode().insertBefore(_document.createComment(commentSS.str()), transition); + StateTransitionMonitor::beforeTakingTransition(interpreter, transition, moreComing); } void ChartToMinimalSCXML::beforeEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) { markAsVisited(state); + + std::stringstream commentSS; + commentSS << " Step #" << _step++ << " - state entered "; + + Arabica::DOM::Element ncState = const_cast&>(state); + if (envVarIsTrue("USCXML_ANNOTATE_PROGRESS")) + ncState.insertBefore(_document.createComment(commentSS.str()), ncState.getFirstChild()); + StateTransitionMonitor::beforeEnteringState(interpreter, state, moreComing); } @@ -262,4 +278,7 @@ void ChartToMinimalSCXML::cancelInvoke(const Arabica::DOM::Element& InterpreterRC::cancelInvoke(element); } +void ChartToMinimalSCXML::onStableConfiguration(uscxml::Interpreter interpreter) { +} + } \ No newline at end of file diff --git a/src/uscxml/transform/ChartToMinimalSCXML.h b/src/uscxml/transform/ChartToMinimalSCXML.h index 31dfd64..5975cbc 100644 --- a/src/uscxml/transform/ChartToMinimalSCXML.h +++ b/src/uscxml/transform/ChartToMinimalSCXML.h @@ -45,6 +45,7 @@ public: virtual void beforeEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing); virtual void beforeInvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid); virtual void beforeCompletion(Interpreter interpreter); + virtual void onStableConfiguration(Interpreter interpreter); // gather executable content per microstep void executeContent(const Arabica::DOM::Element& content, bool rethrow = false); @@ -66,6 +67,8 @@ protected: bool _retainAsComments; private: + size_t _step; + // we need this to register as an instance at Interpreter::_instances boost::shared_ptr _selfPtr; diff --git a/src/uscxml/transform/ChartToPromela.cpp b/src/uscxml/transform/ChartToPromela.cpp index 3e920be..b750409 100644 --- a/src/uscxml/transform/ChartToPromela.cpp +++ b/src/uscxml/transform/ChartToPromela.cpp @@ -99,6 +99,23 @@ for (int indentIndex = start; indentIndex < cols; indentIndex++) \ } \ } +#define DUMP_STATS(disregardTime) \ +uint64_t now = tthread::chrono::system_clock::now(); \ +if (now - _lastTimeStamp > 1000 || disregardTime) { \ + std::cerr << "## State : " << _perfStatesTotal << " [" << _perfStatesProcessed << "/sec]" << std::endl; \ + std::cerr << "## Transition: " << _perfTransTotal << " [" << _perfHistoryProcessed << "/sec]" << std::endl; \ + std::cerr << "## History : " << _perfHistoryTotal << " [" << _perfHistoryProcessed << "/sec]" << std::endl; \ + std::cerr << "toPML: "; \ + std::cerr << _perfStatesTotal << ", " << _perfStatesProcessed << ", "; \ + std::cerr << _perfTransTotal << ", " << _perfTransProcessed << ", "; \ + std::cerr << _perfHistoryTotal << ", " << _perfHistoryProcessed; \ + std::cerr << std::endl << std::endl; \ + _perfTransProcessed = 0; \ + _perfHistoryProcessed = 0; \ + _perfStatesProcessed = 0; \ + if (!disregardTime)\ + _lastTimeStamp = now; \ +} namespace uscxml { @@ -140,7 +157,6 @@ void PromelaEventSource::writeDeclarations(std::ostream& stream, int indent) { } void PromelaEventSource::writeBody(std::ostream& stream) { - stream << "proctype " << name << "EventSource() {" << std::endl; stream << " " << name << "EventSourceDone = 0;" << std::endl; @@ -321,9 +337,12 @@ void PromelaCodeAnalyzer::addCode(const std::string& code, ChartToPromela* inter PromelaParserNode* node = astNodes.front(); astNodes.pop_front(); +// node->dump(); + bool hasValue = false; int assignedValue = 0; + switch (node->type) { case PML_STRING: { std::string unquoted = node->value; @@ -344,10 +363,13 @@ void PromelaCodeAnalyzer::addCode(const std::string& code, ChartToPromela* inter // remember strings for later astNodes.push_back(node->operands.back()); } - if (node->operands.front()->type == PML_CMPND) + if (node->operands.front()->type == PML_CMPND) { node = node->operands.front(); - if (node->operands.front()->type != PML_NAME) - break; // this will skip array assignments + } else { + break; + } +// if (node->operands.front()->type != PML_NAME) +// break; // this will skip array assignments case PML_CMPND: { std::string nameOfType; std::list::iterator opIter = node->operands.begin(); @@ -356,7 +378,6 @@ void PromelaCodeAnalyzer::addCode(const std::string& code, ChartToPromela* inter assert(false); } - _typeDefs.occurrences.insert(interpreter); PromelaTypedef* td = &_typeDefs; std::string seperator; @@ -364,6 +385,8 @@ void PromelaCodeAnalyzer::addCode(const std::string& code, ChartToPromela* inter switch ((*opIter)->type) { case PML_NAME: td = &td->types[(*opIter)->value]; + td->occurrences.insert(interpreter); + nameOfType += seperator + (*opIter)->value; if (nameOfType.compare("_x") == 0) _usesPlatformVars = true; @@ -374,6 +397,8 @@ void PromelaCodeAnalyzer::addCode(const std::string& code, ChartToPromela* inter PromelaParserNode* name = (*opIter)->operands.front(); PromelaParserNode* subscript = *(++(*opIter)->operands.begin()); td = &td->types[name->value]; + td->occurrences.insert(interpreter); + nameOfType += seperator + name->value; td->name = nameOfType + "_t"; @@ -433,7 +458,8 @@ void PromelaCodeAnalyzer::addCode(const std::string& code, ChartToPromela* inter // assert(false); } - astNodes.merge(node->operands); + astNodes.insert(astNodes.end(), node->operands.begin(), node->operands.end()); + } } @@ -659,7 +685,7 @@ std::list > PromelaCodeAnalyzer::getTokenPositions(con } for (std::list::iterator opIter = ast->operands.begin(); opIter != ast->operands.end(); opIter++) { std::list > tmp = getTokenPositions(expr, type, *opIter); - posList.merge(tmp); + posList.insert(posList.end(), tmp.begin(), tmp.end()); } return posList; } @@ -940,9 +966,12 @@ std::string ChartToPromela::conditionalizeForHist(const std::set 0) + if (condition.str().size() > 0) { return "(" + condition.str() + ")"; - return ""; + } else { + assert(false); + } + return "true"; } //std::list ChartToPromela::getTransientContent(GlobalTransition* transition) { @@ -966,44 +995,76 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra for (int i = 0; i < indent; i++) { padding += " "; } - + std::list::const_iterator histIter; + stream << std::endl << _prefix << "t" << transition->index << ": /* ######################## " << std::endl; FlatStateIdentifier flatActiveSource(transition->source); stream << " from state: "; PRETTY_PRINT_LIST(stream, flatActiveSource.getActive()); stream << std::endl; - stream << " with history: " << flatActiveSource.getFlatHistory() << std::endl; +// stream << " with history: " << flatActiveSource.getFlatHistory() << std::endl; stream << " ----- on event: " << (transition->eventDesc.size() > 0 ? transition->eventDesc : "SPONTANEOUS") << " --" << std::endl; stream << " to state: "; - FlatStateIdentifier flatActiveDest(transition->destination); - PRETTY_PRINT_LIST(stream, flatActiveDest.getActive()); + std::set destinations; + destinations.insert(FlatStateIdentifier(transition->destination)); + histIter = transition->historyTrans.begin(); + while(histIter != transition->historyTrans.end()) { + destinations.insert(FlatStateIdentifier((*histIter)->destination)); + histIter++; + } + + std::string seperator = ""; + for (std::set::iterator destIter = destinations.begin(); destIter != destinations.end(); destIter++) { + stream << seperator; + PRETTY_PRINT_LIST(stream, destIter->getActive()); + stream << " with " << (destIter->getFlatHistory().size() > 0 ? destIter->getFlatHistory() : "no history"); + seperator = "\n "; + } stream << std::endl; - stream << " with history: " << flatActiveDest.getFlatHistory() << std::endl; stream << "############################### */" << std::endl; stream << std::endl; stream << padding << "skip;" << std::endl; stream << padding << "d_step {" << std::endl; - stream << padding << " printf(\"Taking Transition " << _prefix << "t" << transition->index << "\\n\");" << std::endl; + if (_writeTransitionPrintfs) + stream << padding << " printf(\"Taking Transition " << _prefix << "t" << transition->index << "\\n\");" << std::endl; + padding += " "; indent++; // iterators of history transitions executable content std::map > actionIters; std::map > actionsInTransition; - + typedef std::map > actionIters_t; - std::list::const_iterator histIter = transition->historyTrans.begin(); + histIter = transition->historyTrans.begin(); while(histIter != transition->historyTrans.end()) { actionIters.insert(std::make_pair((*histIter), std::make_pair((*histIter)->actions.begin(), (*histIter)->actions.end()))); // add history transitions actions to the set - std::copy((*histIter)->actions.begin(), (*histIter)->actions.end(), std::inserter(actionsInTransition[*histIter], actionsInTransition[*histIter].begin())); + for (std::list::iterator actionIter = (*histIter)->actions.begin(); actionIter != (*histIter)->actions.end(); actionIter++) { + actionsInTransition[*histIter].insert(*actionIter); + } +// std::copy((*histIter)->actions.begin(), (*histIter)->actions.end(), std::inserter(actionsInTransition[*histIter], actionsInTransition[*histIter].begin())); histIter++; } - std::copy(transition->actions.begin(), transition->actions.end(), std::inserter(actionsInTransition[transition], actionsInTransition[transition].begin())); - +// std::cout << "###" << std::endl; + for (std::list::iterator actionIter = transition->actions.begin(); actionIter != transition->actions.end(); actionIter++) { +#if 0 + if (actionIter->onEntry) { + std::cout << "onEntry: " << actionIter->onEntry << std::endl; + } else if (actionIter->raiseDone) { + std::cout << "raiseDone: " << actionIter->raiseDone << std::endl; + } else { + std::cout << "#" << std::endl; + } + foo.insert(*actionIter); +#endif + actionsInTransition[transition].insert(*actionIter); + } +// std::copy(transition->actions.begin(), transition->actions.end(), std::inserter(actionsInTransition[transition], actionsInTransition[transition].begin())); + // GlobalTransition::Action action; std::set allBut; @@ -1017,10 +1078,17 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra for (actionIters_t::iterator histActionIter = actionIters.begin(); histActionIter != actionIters.end(); histActionIter++) { // iterate every history transition GlobalTransition* histTrans = histActionIter->first; + if (histActionIter->second.first == histActionIter->second.second) // TODO: is this correct? + continue; GlobalTransition::Action& histAction = *(histActionIter->second.first); - // is the current action identical? - if (baseAction != histAction) { + // is the current action identical or a generated raise for done.state.ID? +// std::cerr << baseAction << std::endl; +// std::cerr << histAction << std::endl; + if (baseAction != histAction && !baseAction.raiseDone) { +// std::cout << baseAction << std::endl; +// std::cout << histAction << std::endl; + // executable content differs - will given executable content appear later in history? if (actionsInTransition[histTrans].find(baseAction) != actionsInTransition[histTrans].end()) { // yes -> write all exec content exclusive to this history transition until base executable content @@ -1062,7 +1130,6 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra bool isConditionalized = false; bool wroteHistoryAssignments = false; - std::set condSet; for (std::list::const_iterator ecIter = ecSeq.begin(); ecIter != ecSeq.end(); ecIter++) { const GlobalTransition::Action& action = ecIter->action; @@ -1079,32 +1146,38 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra continue; } - if (ecIter->type == ExecContentSeqItem::EXEC_CONTENT_ONLY_FOR) { + if (!isConditionalized && ecIter->type == ExecContentSeqItem::EXEC_CONTENT_ONLY_FOR) { // assert(!wroteHistoryAssignments); // we need to move assignments after dispatching? - if (condSet != ecIter->transitions) { - stream << padding << "if" << std::endl; - stream << padding << ":: " << conditionalizeForHist(ecIter->transitions) << " -> {" << std::endl; - padding += " "; - indent++; - isConditionalized = true; - condSet = ecIter->transitions; - } - } else if (ecIter->type == ExecContentSeqItem::EXEC_CONTENT_ALL_BUT) { + stream << padding << "if" << std::endl; + stream << padding << ":: " << conditionalizeForHist(ecIter->transitions) << " -> {" << std::endl; + padding += " "; + indent++; + isConditionalized = true; + } else if (!isConditionalized && ecIter->type == ExecContentSeqItem::EXEC_CONTENT_ALL_BUT) { // assert(!wroteHistoryAssignments); // we need to move assignments after dispatching? - if (condSet != ecIter->transitions) { - stream << padding << "if" << std::endl; - stream << padding << ":: " << conditionalizeForHist(ecIter->transitions) << " -> skip;" << std::endl; - stream << padding << ":: else -> {" << std::endl; - padding += " "; - indent++; - isConditionalized = true; - condSet = ecIter->transitions; - } - } else { - isConditionalized = false; - condSet.clear(); + stream << padding << "if" << std::endl; + stream << padding << ":: " << conditionalizeForHist(ecIter->transitions) << " -> skip;" << std::endl; + stream << padding << ":: else -> {" << std::endl; + padding += " "; + indent++; + isConditionalized = true; } + switch (ecIter->type) { + case ExecContentSeqItem::EXEC_CONTENT_ALL_BUT: + std::cout << "ALL_BUT" << std::endl; + break; + case ExecContentSeqItem::EXEC_CONTENT_EVERY: + std::cout << "EVERY" << std::endl; + break; + case ExecContentSeqItem::EXEC_CONTENT_ONLY_FOR: + std::cout << "ONLY_FOR" << std::endl; + break; + + default: + break; + } + if (action.exited) { // we left a state stream << padding << _prefix << "_x.states[" << _analyzer->macroForLiteral(ATTR(action.exited, "id")) << "] = false; " << std::endl; @@ -1119,7 +1192,7 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra if (action.transition) { // this is executable content from a transition - stream << std::endl << "/* executable content for transition */" << std::endl; + stream << "/* executable content for transition */" << std::endl; writeExecutableContent(stream, action.transition, indent); // continue; } @@ -1128,7 +1201,7 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra // std::cout<< action.onExit << std::endl; // executable content from an onexit element if (action.onExit.getParentNode()) // this should not be necessary? - stream << std::endl << "/* executable content for exiting state " << ATTR_CAST(action.onExit.getParentNode(), "id") << " */" << std::endl; + stream << "/* executable content for exiting state " << ATTR_CAST(action.onExit.getParentNode(), "id") << " */" << std::endl; writeExecutableContent(stream, action.onExit, indent); // continue; } @@ -1136,11 +1209,19 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra if (action.onEntry) { // executable content from an onentry element if (action.onEntry.getParentNode()) // this should not be necessary? - stream << std::endl << "/* executable content for entering state " << ATTR_CAST(action.onEntry.getParentNode(), "id") << " */" << std::endl; + stream << "/* executable content for entering state " << ATTR_CAST(action.onEntry.getParentNode(), "id") << " */" << std::endl; writeExecutableContent(stream, action.onEntry, indent); // continue; } - + + if (action.raiseDone) { + // executable content from an onentry element + if (action.raiseDone.getParentNode()) // this should not be necessary? + stream << "/* raising done event for " << ATTR_CAST(action.raiseDone.getParentNode(), "id") << " */" << std::endl; + writeExecutableContent(stream, action.raiseDone, indent); + // continue; + } + if (action.invoke) { // an invoke element @@ -1228,17 +1309,28 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra } if (isConditionalized) { - padding = padding.substr(2); - indent--; - if (ecIter->type == ExecContentSeqItem::EXEC_CONTENT_ALL_BUT) { - stream << padding << "}" << std::endl; - stream << padding << "fi" << std::endl; - } else if(ecIter->type == ExecContentSeqItem::EXEC_CONTENT_ONLY_FOR) { - stream << padding << "}" << std::endl; - stream << padding << ":: else -> skip;" << std::endl; - stream << padding << "fi;" << std::endl; + // peek into next content and see if same conditions apply -> keep conditionalization + bool sameCondition = false; + std::list::const_iterator nextIter = ecIter; + nextIter++; + if (nextIter != ecSeq.end() && ecIter->type == nextIter->type && ecIter->transitions == nextIter->transitions) { + sameCondition = true; + } + + if (!sameCondition) { + padding = padding.substr(2); + indent--; + + if (ecIter->type == ExecContentSeqItem::EXEC_CONTENT_ALL_BUT) { + stream << padding << "}" << std::endl; + stream << padding << "fi" << std::endl << std::endl; + } else if(ecIter->type == ExecContentSeqItem::EXEC_CONTENT_ONLY_FOR) { + stream << padding << "}" << std::endl; + stream << padding << ":: else -> skip;" << std::endl; + stream << padding << "fi;" << std::endl << std::endl; + } + isConditionalized = false; } - isConditionalized = false; } } @@ -1268,39 +1360,40 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra if (histNewState == origNewState) continue; stream << padding << "if" << std::endl; - stream << padding << "::" << conditionalizeForHist(histTargetIter->second) << " -> {" << std::endl; - stream << std::endl << "/* via hist "; + + stream << "/* to state "; FlatStateIdentifier flatActiveDest(histNewState->activeId); PRETTY_PRINT_LIST(stream, flatActiveDest.getActive()); - stream << "*/" << std::endl; + stream << " via history */" << std::endl; - stream << padding << " " << _prefix << "s = s" << histNewState->activeIndex << ";" << std::endl; - + stream << padding << ":: " << conditionalizeForHist(histTargetIter->second) << " -> " << _prefix << "s = s" << histNewState->activeIndex << ";" << std::endl; // writeTransitionClosure(stream, *histTargetIter->second.begin(), histNewState, indent + 1); // is this correct for everyone in set? - stream << padding << "}" << std::endl; + hasHistoryTarget = true; } - if (hasHistoryTarget) { - stream << padding << ":: else {" << std::endl; - padding += " "; - indent++; - } - origNewState = _activeConf[transition->activeDestination]; + FlatStateIdentifier flatActiveDest(transition->activeDestination); assert(origNewState != NULL); - - stream << std::endl << "/* to state "; + + stream << "/* to state "; PRETTY_PRINT_LIST(stream, flatActiveDest.getActive()); stream << " */" << std::endl; + + if (hasHistoryTarget) { + stream << padding << ":: else -> "; + padding += " "; + indent++; + } stream << padding << _prefix << "s = s" << origNewState->activeIndex << ";" << std::endl; + if (hasHistoryTarget) { padding = padding.substr(2); indent--; - stream << padding << "}" << std::endl; +// stream << padding << "}" << std::endl; stream << padding << "fi;" << std::endl; } @@ -1310,6 +1403,11 @@ void ChartToPromela::writeTransition(std::ostream& stream, GlobalTransition* tra writeTransitionClosure(stream, transition, origNewState, indent-1); + _perfTransProcessed++; + _perfTransTotal++; + + DUMP_STATS(false); + } void ChartToPromela::writeHistoryAssignments(std::ostream& stream, GlobalTransition* transition, int indent) { @@ -1318,6 +1416,9 @@ void ChartToPromela::writeHistoryAssignments(std::ostream& stream, GlobalTransit padding += " "; } + if (transition->historyTrans.size() == 0) + return; + // GlobalState to *changed* history configuration std::list histClasses; @@ -1354,11 +1455,15 @@ void ChartToPromela::writeHistoryAssignments(std::ostream& stream, GlobalTransit if (outerClass.matches(innerClass)) { outerClass.merge(innerClass); - histClasses.erase(innerHistClassIter); + histClasses.erase(innerHistClassIter++); + } else { + innerHistClassIter++; } - - innerHistClassIter++; } + + _perfHistoryProcessed++; + _perfHistoryTotal++; + outerHistClassIter++; } // std::cout << histClasses.size() << std::endl; @@ -1441,6 +1546,9 @@ HistoryTransitionClass::HistoryTransitionClass(const std::string& from, const st } void HistoryTransitionClass::init(const std::string& from, const std::string& to) { + if (from == to) + return; + FlatStateIdentifier flatSource(from); FlatStateIdentifier flatTarget(to); @@ -1550,6 +1658,12 @@ void ChartToPromela::writeTransitionClosure(std::ostream& stream, GlobalTransiti padding += " "; } + if (_traceTransitions) { + for (std::set::iterator transRefIter = transition->transitionRefs.begin(); transRefIter != transition->transitionRefs.end(); transRefIter++) { + stream << padding << _prefix << "transitions[" << *transRefIter << "] = false; " << std::endl; + } + } + if (state->isFinal) { stream << padding << "goto " << _prefix << "terminate;" << std::endl; } else { @@ -1770,9 +1884,9 @@ void ChartToPromela::writeExecutableContent(std::ostream& stream, const Arabica: } } else if(TAGNAME(nodeElem) == "cancel") { if (HAS_ATTR(nodeElem, "sendid")) { - stream << padding << "cancelSendId(" << _analyzer->macroForLiteral(ATTR(nodeElem, "sendid")) << "," << (_invokerid.size() > 0 ? _analyzer->macroForLiteral(_invokerid) : "0") << ");" << std::endl; + stream << padding << "cancelSendId(" << _analyzer->macroForLiteral(ATTR(nodeElem, "sendid")) << ", " << (_invokerid.size() > 0 ? _analyzer->macroForLiteral(_invokerid) : "0") << ");" << std::endl; } else if (HAS_ATTR(nodeElem, "sendidexpr")) { - stream << padding << "cancelSendId(" << ADAPT_SRC(ATTR(nodeElem, "sendidexpr")) << "," << (_invokerid.size() > 0 ? _analyzer->macroForLiteral(_invokerid) : "0") << ");" << std::endl; + stream << padding << "cancelSendId(" << ADAPT_SRC(ATTR(nodeElem, "sendidexpr")) << ", " << (_invokerid.size() > 0 ? _analyzer->macroForLiteral(_invokerid) : "0") << ");" << std::endl; } } else { std::cerr << "'" << TAGNAME(nodeElem) << "'" << std::endl << nodeElem << std::endl; @@ -2009,12 +2123,17 @@ void ChartToPromela::writeDeclarations(std::ostream& stream) { stream << "chan " << _prefix << "start = [" << _machines.size() << "] of {int} /* nested machines to start at next macrostep */" << std::endl; } - stream << "hidden int " << _prefix << "_index; /* helper for indexless foreach loops */" << std::endl; + if (_hasIndexLessLoops) + stream << "hidden int " << _prefix << "_index; /* helper for indexless foreach loops */" << std::endl; + stream << "hidden int " << _prefix << "procid; /* the process id running this machine */" << std::endl; stream << "bool " << _prefix << "spontaneous; /* whether to take spontaneous transitions */" << std::endl; stream << "bool " << _prefix << "done; /* is the state machine stopped? */" << std::endl; stream << "bool " << _prefix << "canceled; /* is the state machine canceled? */" << std::endl; + if (_traceTransitions) + stream << "bool " << _prefix << "transitions[" << indexedTransitions.size() << "]; /* transitions in the optimal transition set */" << std::endl; + if (_analyzer->getTypes().types.find("_ioprocessors") != _analyzer->getTypes().types.end()) { stream << "hidden _ioprocessors_t " << _prefix << "_ioprocessors;" << std::endl; _varInitializers.push_front("_ioprocessors.scxml.location = " + (_invokerid.size() > 0 ? _analyzer->macroForLiteral(_invokerid) : "1") + ";"); @@ -2137,7 +2256,12 @@ void ChartToPromela::writeDeclarations(std::ostream& stream) { typeIter++; continue; } - if (typeIter->first == "_event" || typeIter->first == "_ioprocessors" || typeIter->first == "_SESSIONID" || typeIter->first == "_NAME") { + + if (typeIter->first == "_event" || + typeIter->first == "_x" || + typeIter->first == "_ioprocessors" || + typeIter->first == "_SESSIONID" || + typeIter->first == "_NAME") { typeIter++; continue; } @@ -2245,6 +2369,12 @@ void ChartToPromela::writeFSM(std::ostream& stream) { // every other transition for (std::map::iterator stateIter = _activeConf.begin(); stateIter != _activeConf.end(); stateIter++) { for (std::list::iterator transIter = stateIter->second->sortedOutgoing.begin(); transIter != stateIter->second->sortedOutgoing.end(); transIter++) { + // don't write invalid transition + if (!(*transIter)->isValid) { + LOG(ERROR) << "Sorted outgoing transitions contains invalid transitions - did you instruct ChartToFSM to keep those?"; + abort(); + } + // don't write initial transition if (_start->sortedOutgoing.front() == *transIter) continue; @@ -2253,12 +2383,17 @@ void ChartToPromela::writeFSM(std::ostream& stream) { // if ((*transIter)->hasExecutableContent && (*transIter)->historyBase == NULL) writeTransition(stream, *transIter, 1); } + _perfStatesProcessed++; + _perfStatesTotal++; + + DUMP_STATS(false); } + DUMP_STATS(true); stream << std::endl; stream << _prefix << "macroStep: skip;" << std::endl; if (_allowEventInterleaving) { - stream << " /* push send events to external queue */" << std::endl; + stream << " /* push send events to external queue - this needs to be interleavable! */" << std::endl; stream << " do" << std::endl; if (_analyzer->usesEventField("delay")) { stream << " :: len(" << _prefix << "tmpQ) != 0 -> { " << _prefix << "tmpQ?" << _prefix << "_event; " << _prefix << "eQ!!" << _prefix << "_event }" << std::endl; @@ -2292,11 +2427,12 @@ void ChartToPromela::writeFSM(std::ostream& stream) { stream << " scheduleMachines();" << std::endl << std::endl; } - stream << " /* pop an event */" << std::endl; - stream << " if" << std::endl; - stream << " :: len(" << _prefix << "iQ) != 0 -> " << _prefix << "iQ ? " << _prefix << "_event /* from internal queue */" << std::endl; - stream << " :: else -> " << _prefix << "eQ ? " << _prefix << "_event /* from external queue */" << std::endl; - stream << " fi;" << std::endl << std::endl; + stream << " atomic {" << std::endl; + stream << " /* pop an event */" << std::endl; + stream << " if" << std::endl; + stream << " :: len(" << _prefix << "iQ) != 0 -> " << _prefix << "iQ ? " << _prefix << "_event /* from internal queue */" << std::endl; + stream << " :: else -> " << _prefix << "eQ ? " << _prefix << "_event /* from external queue */" << std::endl; + stream << " fi;" << std::endl << std::endl; #if 0 if (!_analyzer->usesComplexEventStruct()) { @@ -2312,16 +2448,16 @@ void ChartToPromela::writeFSM(std::ostream& stream) { } stream << std::endl; #endif - stream << " /* terminate if we are stopped */" << std::endl; - stream << " if" << std::endl; - stream << " :: " << _prefix << "done -> goto " << _prefix << "terminate;" << std::endl; + + stream << " /* terminate if we are stopped */" << std::endl; + stream << " if" << std::endl; + stream << " :: " << _prefix << "done -> goto " << _prefix << "terminate;" << std::endl; if (_parent != NULL) { - stream << " :: " << _prefix << "canceled -> goto " << _prefix << "cancel;" << std::endl; + stream << " :: " << _prefix << "canceled -> goto " << _prefix << "cancel;" << std::endl; } - stream << " :: else -> skip;" << std::endl; - stream << " fi;" << std::endl << std::endl; + stream << " :: else -> skip;" << std::endl; + stream << " fi;" << std::endl << std::endl; -#if 1 { bool finalizeFound = false; for (std::map, ChartToPromela*>::iterator invIter = _machines.begin(); invIter != _machines.end(); invIter++) { @@ -2332,82 +2468,81 @@ void ChartToPromela::writeFSM(std::ostream& stream) { } } if (finalizeFound) { - stream << " /* event */" << std::endl; - stream << " if" << std::endl; + stream << "/* event */" << std::endl; + stream << " if" << std::endl; for (std::map, ChartToPromela*>::iterator invIter = _machines.begin(); invIter != _machines.end(); invIter++) { NodeSet finalizes = filterChildElements(_nsInfo.xmlNSPrefix + "finalize", invIter->first, false); if (finalizes.size() > 0) { - stream << " :: " << _prefix << "_event.invokeid == " << _analyzer->macroForLiteral(invIter->second->_invokerid) << " -> {" << std::endl; - writeExecutableContent(stream, finalizes[0], 2); - stream << " } " << std::endl; + stream << " :: " << _prefix << "_event.invokeid == " << _analyzer->macroForLiteral(invIter->second->_invokerid) << " -> {" << std::endl; + writeExecutableContent(stream, finalizes[0], 3); + stream << " } " << std::endl; } } - stream << " :: else -> skip;" << std::endl; - stream << " fi;" << std::endl << std::endl; + stream << " :: else -> skip;" << std::endl; + stream << " fi;" << std::endl << std::endl; } } -#endif for (std::map, ChartToPromela*>::iterator invIter = _machines.begin(); invIter != _machines.end(); invIter++) { if (invIter->second == this) { continue; } //std::cout << invIter->first << std::endl; - if (DOMUtils::attributeIsTrue(ATTR_CAST(invIter->first, "autoforward"))) { - stream << " /* autoforward event to " << invIter->second->_invokerid << " invokers */" << std::endl; - stream << " if" << std::endl; - stream << " :: " << invIter->second->_prefix << "done -> skip;" << std::endl; - stream << " :: " << invIter->second->_prefix << "canceled -> skip;" << std::endl; - stream << " :: else -> { " << invIter->second->_prefix << "eQ!!" << _prefix << "_event" << " }" << std::endl; - stream << " fi;" << std::endl << std::endl; + if (stringIsTrue(ATTR_CAST(invIter->first, "autoforward"))) { + stream << "/* autoforward event to " << invIter->second->_invokerid << " invokers */" << std::endl; + stream << " if" << std::endl; + stream << " :: " << invIter->second->_prefix << "done -> skip;" << std::endl; + stream << " :: " << invIter->second->_prefix << "canceled -> skip;" << std::endl; + stream << " :: else -> { " << invIter->second->_prefix << "eQ!!" << _prefix << "_event" << " }" << std::endl; + stream << " fi;" << std::endl << std::endl; } } stream << std::endl; - stream << _prefix << "microStep:" << std::endl; - stream << " /* event dispatching per state */" << std::endl; - stream << " if" << std::endl; + stream << "/* event dispatching per state */" << std::endl; + stream << " if" << std::endl; writeEventDispatching(stream); stream << "/* this is an error as we dispatched all valid states */" << std::endl; - stream << " :: else -> assert(false);" << std::endl; - stream << " fi;" << std::endl; + stream << " :: else -> assert(false);" << std::endl; + stream << " fi;" << std::endl; stream << std::endl; stream << _prefix << "terminate: skip;" << std::endl; if (_parent != NULL) { - stream << " {" << std::endl; - stream << " _event_t tmpE;" << std::endl; - stream << " tmpE.name = " << _analyzer->macroForLiteral("done.invoke." + _invokerid) << ";" << std::endl; + stream << " {" << std::endl; + stream << " _event_t tmpE;" << std::endl; + stream << " tmpE.name = " << _analyzer->macroForLiteral("done.invoke." + _invokerid) << ";" << std::endl; if (_invokerid.length() > 0) { - stream << " tmpE.invokeid = " << _analyzer->macroForLiteral(_invokerid) << ";" << std::endl; + stream << " tmpE.invokeid = " << _analyzer->macroForLiteral(_invokerid) << ";" << std::endl; } if (_analyzer->usesEventField("delay")) { - stream << " _lastSeqId = _lastSeqId + 1;" << std::endl; - stream << " tmpE.seqNr = _lastSeqId;" << std::endl; - stream << " " << _parent->_prefix << "eQ!!tmpE;" << std::endl; + stream << " _lastSeqId = _lastSeqId + 1;" << std::endl; + stream << " tmpE.seqNr = _lastSeqId;" << std::endl; + stream << " " << _parent->_prefix << "eQ!!tmpE;" << std::endl; } else { - stream << " " << _parent->_prefix << "eQ!tmpE;" << std::endl; + stream << " " << _parent->_prefix << "eQ!tmpE;" << std::endl; } - stream << " }" << std::endl; + stream << " }" << std::endl; stream << _prefix << "cancel: skip;" << std::endl; if (_analyzer->usesEventField("delay")) - stream << " removePendingEventsForInvoker(" << _analyzer->macroForLiteral(this->_invokerid) << ")" << std::endl; + stream << " removePendingEventsForInvoker(" << _analyzer->macroForLiteral(this->_invokerid) << ")" << std::endl; } // stop all event sources if (_globalEventSource) - _globalEventSource.writeStop(stream, 1); + _globalEventSource.writeStop(stream, 2); std::map::iterator invIter = _invokers.begin(); while(invIter != _invokers.end()) { - invIter->second.writeStop(stream, 1); + invIter->second.writeStop(stream, 2); invIter++; } + stream << " }" << std::endl; stream << "}" << std::endl; } @@ -2422,7 +2557,7 @@ void ChartToPromela::writeRescheduleProcess(std::ostream& stream, int indent) { } else { stream << padding << "inline rescheduleProcess(smallestDelay, procId, internalQ, externalQ) {" << std::endl; } - stream << padding << " _event_t tmpEvent;" << std::endl; + stream << padding << " _event_t tmpE;" << std::endl; stream << padding << " set_priority(procId, 1);" << std::endl; stream << padding << " if" << std::endl; @@ -2431,18 +2566,18 @@ void ChartToPromela::writeRescheduleProcess(std::ostream& stream, int indent) { stream << padding << " if" << std::endl; stream << padding << " :: len(externalQ) > 0 -> {" << std::endl; - stream << padding << " externalQ?;" << std::endl; + stream << padding << " externalQ?;" << std::endl; stream << padding << " if" << std::endl; - stream << padding << " :: smallestDelay == tmpEvent.delay -> set_priority(procId, 10);" << std::endl; + stream << padding << " :: smallestDelay == tmpE.delay -> set_priority(procId, 10);" << std::endl; stream << padding << " :: else -> skip;" << std::endl; stream << padding << " fi;" << std::endl; stream << padding << " }" << std::endl; if (_allowEventInterleaving) { stream << padding << " :: len(tempQ) > 0 -> {" << std::endl; - stream << padding << " tempQ?;" << std::endl; + stream << padding << " tempQ?;" << std::endl; stream << padding << " if" << std::endl; - stream << padding << " :: smallestDelay == tmpEvent.delay -> set_priority(procId, 10);" << std::endl; + stream << padding << " :: smallestDelay == tmpE.delay -> set_priority(procId, 10);" << std::endl; stream << padding << " :: else -> skip;" << std::endl; stream << padding << " fi;" << std::endl; stream << padding << " }" << std::endl; @@ -2462,12 +2597,12 @@ void ChartToPromela::writeDetermineShortestDelay(std::ostream& stream, int inden } stream << padding << "inline determineSmallestDelay(smallestDelay, queue) {" << std::endl; - stream << padding << " _event_t tmpEvent;" << std::endl; + stream << padding << " _event_t tmpE;" << std::endl; stream << padding << " if" << std::endl; stream << padding << " :: len(queue) > 0 -> {" << std::endl; - stream << padding << " queue?;" << std::endl; + stream << padding << " queue?;" << std::endl; stream << padding << " if" << std::endl; - stream << padding << " :: (tmpEvent.delay < smallestDelay) -> { smallestDelay = tmpEvent.delay; }" << std::endl; + stream << padding << " :: (tmpE.delay < smallestDelay) -> { smallestDelay = tmpE.delay; }" << std::endl; stream << padding << " :: else -> skip;" << std::endl; stream << padding << " fi;" << std::endl; stream << padding << " }" << std::endl; @@ -2484,15 +2619,15 @@ void ChartToPromela::writeAdvanceTime(std::ostream& stream, int indent) { stream << padding << "inline advanceTime(increment, queue) {" << std::endl; stream << padding << " int tmpIndex = 0;" << std::endl; - stream << padding << " _event_t tmpEvent;" << std::endl; + stream << padding << " _event_t tmpE;" << std::endl; stream << padding << " do" << std::endl; stream << padding << " :: tmpIndex < len(queue) -> {" << std::endl; - stream << padding << " queue?tmpEvent;" << std::endl; + stream << padding << " queue?tmpE;" << std::endl; stream << padding << " if" << std::endl; - stream << padding << " :: tmpEvent.delay >= increment -> tmpEvent.delay = tmpEvent.delay - increment;" << std::endl; + stream << padding << " :: tmpE.delay >= increment -> tmpE.delay = tmpE.delay - increment;" << std::endl; stream << padding << " :: else -> skip;" << std::endl; stream << padding << " fi" << std::endl; - stream << padding << " queue!tmpEvent;" << std::endl; + stream << padding << " queue!tmpE;" << std::endl; stream << padding << " tmpIndex++;" << std::endl; stream << padding << " }" << std::endl; stream << padding << " :: else -> break;" << std::endl; @@ -2517,12 +2652,12 @@ void ChartToPromela::writeRemovePendingEventsFromInvoker(std::ostream& stream, i stream << "inline removePendingEventsForInvokerOnQueue(invokeIdentifier, queue) {" << std::endl; stream << " int tmpIndex = 0;" << std::endl; - stream << " _event_t tmpEvent;" << std::endl; + stream << " _event_t tmpE;" << std::endl; stream << " do" << std::endl; stream << " :: tmpIndex < len(queue) -> {" << std::endl; - stream << " queue?tmpEvent;" << std::endl; + stream << " queue?tmpE;" << std::endl; stream << " if" << std::endl; - stream << " :: tmpEvent.delay == 0 || tmpEvent.invokeid != invokeIdentifier -> queue!tmpEvent;" << std::endl; + stream << " :: tmpE.delay == 0 || tmpE.invokeid != invokeIdentifier -> queue!tmpE;" << std::endl; stream << " :: else -> skip;" << std::endl; stream << " fi" << std::endl; stream << " tmpIndex++;" << std::endl; @@ -2550,12 +2685,12 @@ void ChartToPromela::writeCancelEvents(std::ostream& stream, int indent) { stream << "inline cancelSendIdOnQueue(sendIdentifier, queue, invokerIdentifier) {" << std::endl; stream << " int tmpIndex = 0;" << std::endl; - stream << " _event_t tmpEvent;" << std::endl; + stream << " _event_t tmpE;" << std::endl; stream << " do" << std::endl; stream << " :: tmpIndex < len(queue) -> {" << std::endl; - stream << " queue?tmpEvent;" << std::endl; + stream << " queue?tmpE;" << std::endl; stream << " if" << std::endl; - stream << " :: tmpEvent.invokeid != invokerIdentifier || tmpEvent.sendid != sendIdentifier || tmpEvent.delay == 0 -> queue!tmpEvent;" << std::endl; + stream << " :: tmpE.invokeid != invokerIdentifier || tmpE.sendid != sendIdentifier || tmpE.delay == 0 -> queue!tmpE;" << std::endl; stream << " :: else -> skip;" << std::endl; stream << " fi" << std::endl; stream << " tmpIndex++;" << std::endl; @@ -2624,12 +2759,6 @@ void ChartToPromela::writeScheduleMachines(std::ostream& stream, int indent) { void ChartToPromela::writeEventDispatching(std::ostream& stream) { for (std::map::iterator stateIter = _activeConf.begin(); stateIter != _activeConf.end(); stateIter++) { -// if (_globalStates[i] == _startState) -// continue; - - // do not write state with history - we only iterate pure active -// if (stateIter->second->historyStatesRefs.size() > 0) -// continue; const std::string& stateId = stateIter->first; const GlobalState* state = stateIter->second; @@ -2639,11 +2768,10 @@ void ChartToPromela::writeEventDispatching(std::ostream& stream) { PRETTY_PRINT_LIST(stream, flatActiveSource.getActive()); stream << " ######################## */" << std::endl; - stream << " :: (" << _prefix << "s == s" << state->activeIndex << ") -> {" << std::endl; + stream << " :: (" << _prefix << "s == s" << state->activeIndex << ") -> {" << std::endl; - writeDispatchingBlock(stream, state->sortedOutgoing, 2); -// stream << " goto macroStep;"; - stream << " }" << std::endl; + writeDispatchingBlock(stream, state->sortedOutgoing, 3); + stream << " }" << std::endl; } } @@ -2728,11 +2856,16 @@ void ChartToPromela::writeDispatchingBlock(std::ostream& stream, std::listhasExecutableContent || currTrans->historyTrans.size() > 0) { stream << " -> { " << std::endl; - stream << "/* transition to "; FlatStateIdentifier flatActiveSource(currTrans->activeDestination); PRETTY_PRINT_LIST(stream, flatActiveSource.getActive()); stream << " */" << std::endl; + + if (_traceTransitions) { + for (std::set::iterator transRefIter = currTrans->transitionRefs.begin(); transRefIter != currTrans->transitionRefs.end(); transRefIter++) { + stream << padding << " " << _prefix << "transitions[" << *transRefIter << "] = true; " << std::endl; + } + } stream << padding << " goto " << _prefix << "t" << currTrans->index << ";" << std::endl; stream << padding << "}" << std::endl; @@ -2931,6 +3064,9 @@ void ChartToPromela::initNodes() { } } +// _analyzer->addCode("bumpDownArrow = 1; _event.foo = 3; forgetSelectedServer = 1;", this); +// exit(0); + // transform data / assign json into PROMELA statements { NodeSet asgn; @@ -3166,6 +3302,8 @@ void ChartToPromela::initNodes() { for (int i = 0; i < foreachs.size(); i++) { if (HAS_ATTR_CAST(foreachs[i], "index")) { allCode.insert(ATTR_CAST(foreachs[i], "index")); + } else { + _hasIndexLessLoops = true; } if (HAS_ATTR_CAST(foreachs[i], "item")) { allCode.insert(ATTR_CAST(foreachs[i], "item")); @@ -3235,6 +3373,9 @@ void PromelaInline::dump() { void ChartToPromela::writeProgram(std::ostream& stream) { + _traceTransitions = envVarIsTrue("USCXML_PROMELA_TRANSITION_TRACE"); + _writeTransitionPrintfs = envVarIsTrue("USCXML_PROMELA_TRANSITION_DEBUG"); + if (!HAS_ATTR(_scxml, "datamodel") || ATTR(_scxml, "datamodel") != "promela") { LOG(ERROR) << "Can only convert SCXML documents with \"promela\" datamodel"; return; @@ -3267,6 +3408,8 @@ void ChartToPromela::writeProgram(std::ostream& stream) { stream << std::endl; writeStates(stream); stream << std::endl; + writeStrings(stream); + stream << std::endl; if (_analyzer->usesInPredicate()) { writeStateMap(stream); stream << std::endl; @@ -3277,8 +3420,6 @@ void ChartToPromela::writeProgram(std::ostream& stream) { } writeTypeDefs(stream); stream << std::endl; - writeStrings(stream); - stream << std::endl; writeDeclarations(stream); stream << std::endl; @@ -3287,7 +3428,7 @@ void ChartToPromela::writeProgram(std::ostream& stream) { stream << std::endl; } - stream << std::endl << "/* Global inline functions */" << std::endl; + stream << std::endl << "/* global inline functions */" << std::endl; if (_analyzer->usesEventField("delay") && _machines.size() > 0) { diff --git a/src/uscxml/transform/ChartToPromela.h b/src/uscxml/transform/ChartToPromela.h index cfe5a78..9a8b0a3 100644 --- a/src/uscxml/transform/ChartToPromela.h +++ b/src/uscxml/transform/ChartToPromela.h @@ -280,7 +280,25 @@ public: void writeTo(std::ostream& stream); protected: - ChartToPromela(const Interpreter& other) : TransformerImpl(), ChartToFSM(other), _analyzer(NULL), _machinesAll(NULL), _parent(NULL), _parentTopMost(NULL), _machinesAllPerId(NULL) {} + ChartToPromela(const Interpreter& other) + : TransformerImpl(), + ChartToFSM(other), + _analyzer(NULL), + _allowEventInterleaving(false), + _hasIndexLessLoops(false), + _writeTransitionPrintfs(false), + _traceTransitions(false), + _machinesAll(NULL), + _parent(NULL), + _parentTopMost(NULL), + _machinesAllPerId(NULL), + _perfTransProcessed(0), + _perfTransTotal(0), + _perfHistoryProcessed(0), + _perfHistoryTotal(0), + _perfStatesProcessed(0), + _perfStatesTotal(0), + _lastTimeStamp(0) {} void initNodes(); @@ -341,6 +359,9 @@ protected: PromelaCodeAnalyzer* _analyzer; bool _allowEventInterleaving; + bool _hasIndexLessLoops; + bool _writeTransitionPrintfs; + bool _traceTransitions; uint32_t _externalQueueLength; uint32_t _internalQueueLength; @@ -362,6 +383,14 @@ protected: std::string _prefix; // our prefix in case of nested SCXML documents std::string _invokerid; + uint64_t _perfTransProcessed; + uint64_t _perfTransTotal; + uint64_t _perfHistoryProcessed; + uint64_t _perfHistoryTotal; + uint64_t _perfStatesProcessed; + uint64_t _perfStatesTotal; + uint64_t _lastTimeStamp; + friend class PromelaEventSource; }; diff --git a/src/uscxml/transform/ChartToTex.cpp b/src/uscxml/transform/ChartToTex.cpp new file mode 100644 index 0000000..35731a1 --- /dev/null +++ b/src/uscxml/transform/ChartToTex.cpp @@ -0,0 +1,284 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#include "uscxml/transform/ChartToFSM.h" +#include "uscxml/transform/ChartToTex.h" +#include "uscxml/transform/FlatStateIdentifier.h" + +#include +#include +#include "uscxml/UUID.h" +#include +#include +#include + + +namespace uscxml { + +using namespace Arabica::DOM; +using namespace Arabica::XPath; + +ChartToTex::~ChartToTex() { +} + +Transformer ChartToTex::transform(const Interpreter& other) { + return boost::shared_ptr(new ChartToTex(other)); +} + +void ChartToTex::writeTo(std::ostream& stream) { + writeTex(stream); +} + +void ChartToTex::writeTex(std::ostream& stream) { + _keepInvalidTransitions = true; + if (_start == NULL) { + interpret(); + } + + bool wroteRowStart = false; + std::string seperator; + + for (std::map::iterator stateIter = _globalConf.begin(); stateIter != _globalConf.end(); stateIter++) { + assert(_indexToState.find(stateIter->second->index) == _indexToState.end()); + _indexToState[stateIter->second->index] = stateIter->second; + } + + stream << "% " << _sourceURL.asString() << std::endl; + + stream << "%<*provideCommand>" << std::endl; + stream << "\\providecommand{\\globalStateListCell}[2][c]{%" << std::endl; + stream << " \\begin{tabular}[#1]{@{}l@{}}#2\\end{tabular}}" << std::endl; + stream << "%" << std::endl; + + + stream << std::endl; + +// stream << "\\begin{table}[H]" << std::endl; +// stream << "\\centering" << std::endl; +// stream << "\\begin{tabular}{r | l | L{12em} | l}" << std::endl; + + stream << "\\begin{longtable}{| r | l | l | l |}" << std::endl; + + for (std::map::iterator stateIter = _indexToState.begin(); stateIter != _indexToState.end(); stateIter++) { + GlobalState* currState = stateIter->second; + + stream << "\\hline" << std::endl; + + if (!wroteRowStart) { + stream << "%<*tableRows>" << std::endl; + wroteRowStart = true; + } + + stream << "%<*globalState" << currState->index << ">" << std::endl; + + // state index + stream << "\\tikzmark{statename_" << currState->index << "}" << "$\\widetilde{s}(" << currState->index << ")$ & "; + + // members in active configuration + FlatStateIdentifier flatId(currState->stateId); + stream << "\\globalStateListCell[t]{"; + stream << "\\tikzmark{active_" << currState->index << "}"; + stream << "$\\widetilde{s}_a(" << currState->index << ")$: " << stateListToTex(flatId.getFlatActive(), flatId.getActive().size() == 0) << "\\\\"; + + // already visited states + stream << "\\tikzmark{visited_" << currState->index << "}"; + stream << "$\\widetilde{s}_d(" << currState->index << ")$: " << stateListToTex(flatId.getFlatVisited(), flatId.getVisited().size() == 0) << "\\\\"; + + // history assignments + stream << "\\tikzmark{history_" << currState->index << "}"; + stream << "$\\widetilde{s}_h(" << currState->index << ")$: " << stateListToTex(flatId.getFlatHistory(), flatId.getHistory().size() == 0) << "} & "; + + // all transitions + std::set origTransitions; + for (std::list::iterator transIter = stateIter->second->sortedOutgoing.begin(); transIter != stateIter->second->sortedOutgoing.end(); transIter++) { + GlobalTransition* currTrans = *transIter; + Arabica::XPath::NodeSet members = currTrans->getTransitions(); + for (int i = 0; i < members.size(); i++) { + Element transElem(members[i]); + if (HAS_ATTR(transElem, "priority")) { + origTransitions.insert(ATTR(transElem, "priority")); + } else { + origTransitions.insert("initial"); + } + } + } + + if (origTransitions.size() > 0) { + stream << "$\\{ "; + seperator = ""; + for (std::set::reverse_iterator transIter = origTransitions.rbegin(); transIter != origTransitions.rend(); transIter++) { + stream << seperator << "t_{" << *transIter << "}"; + seperator = ", "; + } + stream << " \\}$"; + } else { + stream << "$\\emptyset$"; + } + stream << "\\tikzmark{transitions_" << currState->index << "}"; + stream << " & \\\\ \\hline" << std::endl; + + if (stateIter->second->sortedOutgoing.size() > 0) { + stream << "$\\widetilde{\\mathcal{T}}(" << currState->index << ")$" << std::endl; + + size_t ecIndex = 0; + for (std::list::iterator transIter = stateIter->second->sortedOutgoing.begin(); transIter != stateIter->second->sortedOutgoing.end(); transIter++, ecIndex++) { + GlobalTransition* currTrans = *transIter; + stream << "& "; + stream << "\\tikzmark{trans_set" << currState->index << "_" << ecIndex << "}"; + + if (!currTrans->isValid) + stream << "\\sout{"; + + Arabica::XPath::NodeSet members = currTrans->getTransitions(); + if (members.size() > 0) { + stream << "$\\{ "; + seperator = ""; + for (int i = 0; i < members.size(); i++) { + Element transElem(members[i]); + if (HAS_ATTR(transElem, "priority")) { + stream << seperator << "t_{" << ATTR(transElem, "priority") << "}"; + } else { + stream << seperator << "t_{initial}"; + } + seperator = ", "; + } + stream << " \\}$"; + } else { + stream << "$\\emptyset$"; + } + // stream << "& \\sout{$\\{ t_2, t_0 \\}$}, & \\emph{$Inv_4$: nested source states} \\\\" << std::endl; + // stream << "& $\\{ t_2 \\}$ & & $\\widetilde{s}(2)$ \\\\" << std::endl; + // stream << "& $\\{ t_0 \\}$ & & $\\widetilde{s}(4)$ \\\\" << std::endl; + + if (!currTrans->isValid) { +#if 1 + stream << " } & \\emph{"; + switch(currTrans->invalidReason) { + case GlobalTransition::NO_COMMON_EVENT: + stream << "$Inv_1$: "; break; + case GlobalTransition::MIXES_EVENT_SPONTANEOUS: + stream << "$Inv_2$: "; break; + case GlobalTransition::SAME_SOURCE_STATE: + stream << "$Inv_3$: "; break; + case GlobalTransition::CHILD_ENABLED: + stream << "$Inv_4$: "; break; + case GlobalTransition::PREEMPTING_MEMBERS: + stream << "$Inv_5$: "; break; + case GlobalTransition::UNCONDITIONAL_MATCH: + stream << "$Opt_1$: "; break; + case GlobalTransition::UNCONDITIONAL_SUPERSET: + stream << "$Opt_2$: "; break; + } + stream << currTrans->invalidMsg << "} "; +#endif + stream << "\\tikzmark{exec_content" << currState->index << "_" << ecIndex << "}"; + stream << " & "; + } else { + stream << " & "; + std::stringstream execContentSS; + + seperator = ""; + for (std::list::iterator actionIter = currTrans->actions.begin(); actionIter != currTrans->actions.end(); actionIter++) { + Element execContent; + + if (actionIter->onEntry) + execContent = actionIter->onEntry; + + if (actionIter->raiseDone) + execContent = actionIter->raiseDone; + + if (actionIter->onExit) + execContent = actionIter->onExit; + + if (actionIter->transition) + execContent = actionIter->transition; + + if (execContent) { + if (HAS_ATTR(execContent, "line_start") && HAS_ATTR(execContent, "line_end")) { + size_t lineStart = strTo(ATTR(execContent, "line_start")); + size_t lineEnd = strTo(ATTR(execContent, "line_end")); + lineStart++; + lineEnd--; + if (lineStart == lineEnd) { + execContentSS << seperator << "l_{" << lineStart << "}"; + } else { + execContentSS << seperator << "l_{" << lineStart << "-" << lineEnd << "}"; + } + } + seperator = ", "; + } + } + + if (execContentSS.str().size() > 0) { + stream << "$\\mathcal{X} := (" << execContentSS.str() << ")$"; + } else { + stream << "$\\emptyset$"; + } + stream << "\\tikzmark{exec_content" << currState->index << "_" << ecIndex << "}"; + + stream << " & $\\widetilde{s}(" << _globalConf[currTrans->destination]->index << ")$ "; + stream << "\\tikzmark{target" << currState->index << "_" << ecIndex << "}"; + } + + stream << "\\\\" << std::endl; + } + if (stateIter->second->sortedOutgoing.size() == 0) { + stream << " & & & \\\\" << std::endl; + } + + stream << "\\hline" << std::endl; + } + stream << "%index << ">" << std::endl; + + } + if (wroteRowStart) { + stream << "%" << std::endl; + } + +// stream << "\\end{tabular}" << std::endl; +// stream << "\\end{table}" << std::endl << std::endl; + stream << "\\end{longtable}" << std::endl << std::endl; + +} + +std::string ChartToTex::stateListToTex(const std::string& input, bool isEmpty) { + std::string statesTex; + if (!isEmpty) { + statesTex = input; + boost::replace_all(statesTex, "active:", ""); + boost::replace_all(statesTex, "history:", ""); + boost::replace_all(statesTex, "visited:", ""); + statesTex = "\\texttt{" + texEscape(statesTex) + "}"; + } else { + statesTex = "$\\emptyset$"; + } + return statesTex; +} + +std::string ChartToTex::texEscape(const std::string& input) { + std::string texString(input); + boost::replace_all(texString, "\\", "\\\\"); + boost::replace_all(texString, "{", "\\{"); + boost::replace_all(texString, "}", "\\}"); + boost::replace_all(texString, ",", ", "); + return texString; +} + + +} \ No newline at end of file diff --git a/src/uscxml/transform/ChartToTex.h b/src/uscxml/transform/ChartToTex.h new file mode 100644 index 0000000..b7542f4 --- /dev/null +++ b/src/uscxml/transform/ChartToTex.h @@ -0,0 +1,61 @@ +/** + * @file + * @author 2012-2014 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) + * @copyright Simplified BSD + * + * @cond + * This program is free software: you can redistribute it and/or modify + * it under the terms of the FreeBSD license as published by the FreeBSD + * project. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the FreeBSD license along with this + * program. If not, see . + * @endcond + */ + +#ifndef CHARTTOTEX_H_2B7D5889 +#define CHARTTOTEX_H_2B7D5889 + + +#include "Transformer.h" +#include "ChartToFSM.h" +#include "uscxml/Interpreter.h" +#include "uscxml/DOMUtils.h" +#include "uscxml/util/Trie.h" + +#include +#include +#include +#include + +namespace uscxml { + +class USCXML_API ChartToTex : public TransformerImpl, public ChartToFSM { +public: + + virtual ~ChartToTex(); + static Transformer transform(const Interpreter& other); + + void writeTo(std::ostream& stream); + +protected: + ChartToTex(const Interpreter& other) + : TransformerImpl(), + ChartToFSM(other) {} + + void writeTex(std::ostream& stream); + + std::map _indexToState; + +private: + static std::string stateListToTex(const std::string& input, bool isEmpty); + static std::string texEscape(const std::string& input); +}; + +} + +#endif /* end of include guard: CHARTTOTEX_H_2B7D5889 */ diff --git a/src/uscxml/transform/FlatStateIdentifier.h b/src/uscxml/transform/FlatStateIdentifier.h index 011888a..f082b18 100644 --- a/src/uscxml/transform/FlatStateIdentifier.h +++ b/src/uscxml/transform/FlatStateIdentifier.h @@ -42,6 +42,10 @@ public: return stateId.length() > 0; } + bool operator<( const FlatStateIdentifier& other) const { + return stateId < other.stateId; + } + FlatStateIdentifier(const Arabica::XPath::NodeSet& activeStates, const Arabica::XPath::NodeSet& alreadyEnteredStates, const std::map >& historyStates) { @@ -166,29 +170,31 @@ public: initStateId(); } - const std::string& getStateId() { + const std::string& getStateId() const { return stateId; } - const std::list& getActive() { + const std::list& getActive() const { return active; } - const std::string& getFlatActive() { + const std::string& getFlatActive() const { return flatActive; } - const std::string& getFlatHistory() { + + const std::string& getFlatHistory() const { return flatHistories; } - const std::list& getVisited() { + const std::list& getVisited() const { return visited; } - const std::string& getFlatVisited() { + const std::string& getFlatVisited() const { return flatVisited; } - const std::map > & getHistory() { + + const std::map > & getHistory() const { return histories; } diff --git a/test/uscxml/promela/test-complete.scxml b/test/uscxml/promela/test-complete.scxml new file mode 100644 index 0000000..a96152b --- /dev/null +++ b/test/uscxml/promela/test-complete.scxml @@ -0,0 +1,154 @@ + + + + [1,2,3] + + { foo: 1, bar: 'baz' } + { itemSum: 0, indexSum: 0 } + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/uscxml/promela/test-event-source-auto.scxml b/test/uscxml/promela/test-event-source-auto.scxml new file mode 100644 index 0000000..ef0e26c --- /dev/null +++ b/test/uscxml/promela/test-event-source-auto.scxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/w3c/analyze_tests.pl b/test/w3c/analyze_tests.pl index 111db5a..a14c129 100755 --- a/test/w3c/analyze_tests.pl +++ b/test/w3c/analyze_tests.pl @@ -119,17 +119,22 @@ while ($block = ) { / Approximate\sComplexity:\s(\d+)\n Approximate\sActive\sComplexity:\s(\d+)\n - Actual\sComplexity:\s(\d+)\n - Actual\sActive\sComplexity:\s(\d+)\n - Internal\sQueue:\s(\d+)\n - External\sQueue:\s(\d+)\n /x ) { $test->{$currTest}->{'flat'}->{'cmplx'}->{'appr'} = $1; $test->{$currTest}->{'flat'}->{'cmplx'}->{'apprActv'} = $2; - $test->{$currTest}->{'flat'}->{'cmplx'}->{'actual'} = $3; - $test->{$currTest}->{'flat'}->{'cmplx'}->{'actualActv'} = $4; - $test->{$currTest}->{'flat'}->{'queue'}->{'internal'} = $5; - $test->{$currTest}->{'flat'}->{'queue'}->{'external'} = $6; + + if ($block =~ + / + Actual\sComplexity:\s(\d+)\n + Actual\sActive\sComplexity:\s(\d+)\n + Internal\sQueue:\s(\d+)\n + External\sQueue:\s(\d+)\n + /x ) { + $test->{$currTest}->{'flat'}->{'cmplx'}->{'actual'} = $1; + $test->{$currTest}->{'flat'}->{'cmplx'}->{'actualActv'} = $2; + $test->{$currTest}->{'flat'}->{'queue'}->{'internal'} = $3; + $test->{$currTest}->{'flat'}->{'queue'}->{'external'} = $4; + } if ($block =~ /State-vector (\d+) byte, depth reached (\d+), errors: (\d+)/) { $test->{$currTest}->{'pml'}->{'states'}->{'stateSize'} = $1; diff --git a/test/w3c/run_promela_test.cmake b/test/w3c/run_promela_test.cmake index e19148f..e6d2418 100644 --- a/test/w3c/run_promela_test.cmake +++ b/test/w3c/run_promela_test.cmake @@ -3,7 +3,10 @@ get_filename_component(TEST_FILE_NAME ${TESTFILE} NAME) execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${OUTDIR}) -execute_process(COMMAND ${USCXML_TRANSFORM_BIN} -i ${TESTFILE} -o ${OUTDIR}/${TEST_FILE_NAME}.pml RESULT_VARIABLE CMD_RESULT) +set(ENV{USCXML_PROMELA_TRANSITION_TRACE} "TRUE") +set(ENV{USCXML_PROMELA_TRANSITION_DEBUG} "TRUE") + +execute_process(COMMAND ${USCXML_TRANSFORM_BIN} -tpml -i ${TESTFILE} -o ${OUTDIR}/${TEST_FILE_NAME}.pml RESULT_VARIABLE CMD_RESULT) if(CMD_RESULT) message(FATAL_ERROR "Error running ${USCXML_TRANSFORM_BIN}: ${CMD_RESULT}") endif() -- cgit v0.12