MinitScript  0.9.31 PRE-BETA
MinitScript.cpp
Go to the documentation of this file.
2 
3 #include <algorithm>
4 #include <filesystem>
5 #include <fstream>
6 #include <initializer_list>
7 #include <iostream>
8 #include <map>
9 #include <memory>
10 #include <regex>
11 #include <span>
12 #include <stack>
13 #include <string>
14 #include <string_view>
15 #include <unordered_map>
16 #include <unordered_set>
17 #include <utility>
18 #include <vector>
19 
21 
41 
42 #include <minitscript/math/Math.h>
52 
53 using std::find;
54 using std::ifstream;
55 using std::initializer_list;
56 using std::make_pair;
57 using std::make_unique;
58 using std::map;
59 using std::move;
60 using std::remove;
61 using std::reverse;
62 using std::smatch;
63 using std::sort;
64 using std::span;
65 using std::stack;
66 using std::string;
67 using std::string_view;
68 using std::stringstream;
69 using std::to_string;
70 using std::unique_ptr;
71 using std::unordered_map;
72 using std::unordered_set;
73 using std::vector;
74 
76 
95 
96 // TODO: we can remove the _ here again, as MinitScript.cpp is not a transpilation unit anymore
107 
108 const string MinitScript::OPERATOR_CHARS = "+-!~/%<>=&^|";
109 
110 vector<MinitScript::DataType*> MinitScript::dataTypes;
111 MinitScript::ShutdownRAII MinitScript::shutdownRAII(MinitScript::dataTypes);
112 
113 const string MinitScript::METHOD_SCRIPTCALL = "script.call";
114 const string MinitScript::METHOD_SCRIPTCALLSTACKLET = "script.callStacklet";
115 const string MinitScript::METHOD_SCRIPTCALLBYINDEX = "script.callByIndex";
116 const string MinitScript::METHOD_SCRIPTCALLSTACKLETBYINDEX = "script.callStackletByIndex";
117 
118 const string MinitScript::METHOD_ENABLENAMEDCONDITION = "script.enableNamedCondition";
119 const string MinitScript::METHOD_DISABLENAMEDCONDITION = "script.disableNamedCondition";
120 
121 const string MinitScript::Variable::TYPENAME_NONE = "";
122 const string MinitScript::Variable::TYPENAME_NULL = "Null";
123 const string MinitScript::Variable::TYPENAME_BOOLEAN = "Boolean";
124 const string MinitScript::Variable::TYPENAME_INTEGER = "Integer";
125 const string MinitScript::Variable::TYPENAME_FLOAT = "Float";
126 const string MinitScript::Variable::TYPENAME_FUNCTION = "Function";
127 const string MinitScript::Variable::TYPENAME_STACKLET = "Stacklet";
128 const string MinitScript::Variable::TYPENAME_NUMBER = "Number";
129 const string MinitScript::Variable::TYPENAME_MIXED = "Mixed";
130 const string MinitScript::Variable::TYPENAME_STRING = "String";
131 const string MinitScript::Variable::TYPENAME_BYTEARRAY = "ByteArray";
132 const string MinitScript::Variable::TYPENAME_ARRAY = "Array";
133 const string MinitScript::Variable::TYPENAME_MAP = "Map";
134 const string MinitScript::Variable::TYPENAME_SET = "Set";
135 
136 const vector<string> MinitScript::Method::CONTEXTFUNCTIONS_ALL = {};
137 
138 void MinitScript::initialize() {
139  //
141  //
142  registerDataType(new HTTPDownloadClientClass());
143  //
144  HTTPDownloadClientClass::initialize();
145 }
146 
147 const string MinitScript::getBaseClassHeader() {
148  return "minitscript/minitscript/MinitScript.h";
149 }
150 
151 const string MinitScript::getBaseClass() {
152  return "minitscript::minitscript::MinitScript";
153 }
154 
155 const vector<string> MinitScript::getTranspilationUnits() {
156  return {
157  MINITSCRIPT_DATA + "/src/minitscript/minitscript/ApplicationMethods.cpp",
158  MINITSCRIPT_DATA + "/src/minitscript/minitscript/ArrayMethods.cpp",
159  MINITSCRIPT_DATA + "/src/minitscript/minitscript/BaseMethods.cpp",
160  MINITSCRIPT_DATA + "/src/minitscript/minitscript/ByteArrayMethods.cpp",
161  MINITSCRIPT_DATA + "/src/minitscript/minitscript/ConsoleMethods.cpp",
162  MINITSCRIPT_DATA + "/src/minitscript/minitscript/ContextMethods.cpp",
163  MINITSCRIPT_DATA + "/src/minitscript/minitscript/CryptographyMethods.cpp",
164  MINITSCRIPT_DATA + "/src/minitscript/minitscript/FileSystemMethods.cpp",
165  MINITSCRIPT_DATA + "/src/minitscript/minitscript/HTTPDownloadClientClass.cpp",
166  MINITSCRIPT_DATA + "/src/minitscript/minitscript/JSONMethods.cpp",
167  MINITSCRIPT_DATA + "/src/minitscript/minitscript/MapMethods.cpp",
168  MINITSCRIPT_DATA + "/src/minitscript/minitscript/MathMethods.cpp",
169  MINITSCRIPT_DATA + "/src/minitscript/minitscript/NetworkMethods.cpp",
170  MINITSCRIPT_DATA + "/src/minitscript/minitscript/ScriptMethods.cpp",
171  MINITSCRIPT_DATA + "/src/minitscript/minitscript/SetMethods.cpp",
172  MINITSCRIPT_DATA + "/src/minitscript/minitscript/StringMethods.cpp",
173  MINITSCRIPT_DATA + "/src/minitscript/minitscript/TimeMethods.cpp",
174  MINITSCRIPT_DATA + "/src/minitscript/minitscript/XMLMethods.cpp"
175  };
176 }
177 
178 MinitScript::MinitScript() {
179  //
180  for (auto dataType: dataTypes) {
181  if (dataType->isRequiringGarbageCollection() == false) continue;
182  // create script context
183  auto scriptContext = dataType->createScriptContext();
184  scriptContext->setMinitScript(this);
185  scriptContext->setIndex(garbageCollectionDataTypes.size());
186  //
187  garbageCollectionDataTypes.emplace_back(dataType,scriptContext);
188  garbageCollectionScriptContextsByDataType[dataType->getType()] = scriptContext;
189  }
190  setNative(false);
191  pushScriptState();
192 }
193 
194 MinitScript::~MinitScript() {
195  for (const auto& [methodName, method]: this->methods) delete method;
196  for (const auto& [stateMachineStateId, stateMachineState]: this->stateMachineStates) delete stateMachineState;
197  while (scriptStateStack.empty() == false) popScriptState();
198  garbageCollection();
199  for (auto& garbageCollectionDataType: garbageCollectionDataTypes) garbageCollectionDataType.dataType->deleteScriptContext(garbageCollectionDataType.context);
200 }
201 
202 void MinitScript::registerStateMachineState(StateMachineState* state) {
203  auto stateMachineStateIt = stateMachineStates.find(state->getId());
204  if (stateMachineStateIt != stateMachineStates.end()) {
205  _Console::printLine("MinitScript::registerStateMachineState(): " + scriptFileName + ": State with id + " + to_string(state->getId()) + ", name " + state->getName() + " already registered.");
206  return;
207  }
208  stateMachineStates[state->getId()] = state;
209 }
210 
211 void MinitScript::initializeNative() {
212 }
213 
214 void MinitScript::complain(const string& methodName, const SubStatement& subStatement) {
215  auto argumentsInformation = getArgumentsInformation(methodName);
216  if (argumentsInformation.empty() == true) argumentsInformation = "None";
217  auto errorMessageDetails = getSubStatementInformation(subStatement) + ": " + methodName + "(...): Argument mismatch: expected arguments: " + argumentsInformation;
218  //
219  _Console::printLine(errorMessageDetails);
220  //
221  errorMessage =
222  "An method usage complain has occurred: " +
223  errorMessageDetails;
224  //
225  errorSubStatement = subStatement;
226 }
227 
228 void MinitScript::complain(const string& methodName, const SubStatement& subStatement, const string& message) {
229  auto errorMessageDetails = getSubStatementInformation(subStatement) + ": " + methodName + "(...): " + message;
230  //
231  _Console::printLine(errorMessageDetails);
232  //
233  errorMessage =
234  "An method usage complain has occurred: " +
235  errorMessageDetails;
236  //
237  errorSubStatement = subStatement;
238 }
239 
240 void MinitScript::complainOperator(const string& methodName, const string& operatorString, const SubStatement& subStatement) {
241  auto argumentsInformation = getArgumentsInformation(methodName);
242  if (argumentsInformation.empty() == true) argumentsInformation = "None";
243  auto errorMessageDetails = getSubStatementInformation(subStatement) + ": '" + operatorString + "': Argument mismatch: expected arguments: " + argumentsInformation;
244  //
245  _Console::printLine(errorMessageDetails);
246  //
247  errorMessage =
248  (isOperator(operatorString) == true?
249  "An operator usage complain has occurred: ":
250  "An method usage complain has occurred: "
251  ) +
252  errorMessageDetails;
253  //
254  errorSubStatement = subStatement;
255 }
256 
257 void MinitScript::complainOperator(const string& methodName, const string& operatorString, const SubStatement& subStatement, const string& message) {
258  auto errorMessageDetails = getSubStatementInformation(subStatement) + ": '" + operatorString + "': " + message;
259  //
260  _Console::printLine(errorMessageDetails);
261  //
262  errorMessage =
263  (isOperator(operatorString) == true?
264  "An operator usage complain has occurred: ":
265  "An method usage complain has occurred: "
266  ) +
267  errorMessageDetails;
268  //
269  errorSubStatement = subStatement;
270 }
271 
272 void MinitScript::registerMethod(Method* method) {
273  auto methodsIt = methods.find(method->getMethodName());
274  if (methodsIt != methods.end()) {
275  _Console::printLine("MinitScript::registerMethod(): " + scriptFileName + ": Method with name " + method->getMethodName() + " already registered.");
276  return;
277  }
278  methods[method->getMethodName()] = method;
279 }
280 
281 void MinitScript::registerDataType(DataType* dataType) {
282  dataType->setType(static_cast<VariableType>(TYPE_PSEUDO_DATATYPES + dataTypes.size()));
283  dataTypes.push_back(dataType);
284 }
285 
286 void MinitScript::executeNextStatement() {
287  auto& scriptState = getScriptState();
288  if (scriptState.scriptIdx == SCRIPTIDX_NONE || scriptState.statementIdx == STATEMENTIDX_NONE || scriptState.running == false) return;
289  //
290  const auto& script = scripts[scriptState.scriptIdx];
291  if (script.statements.empty() == true) return;
292  // take goto statement index into account
293  if (scriptState.gotoStatementIdx != STATEMENTIDX_NONE) {
294  scriptState.statementIdx = scriptState.gotoStatementIdx;
295  scriptState.gotoStatementIdx = STATEMENTIDX_NONE;
296  }
297  //
298  const auto& statement = script.statements[scriptState.statementIdx];
299  const auto& syntaxTree = script.syntaxTree[scriptState.statementIdx];
300  //
301  if (scriptState.statementIdx == STATEMENTIDX_FIRST) emitted = false;
302  //
303  executeStatement(syntaxTree, statement);
304  //
305  if (emitted == true) return;
306  //
307  scriptState.statementIdx++;
308  if (scriptState.statementIdx >= script.statements.size()) {
309  scriptState.scriptIdx = SCRIPTIDX_NONE;
310  scriptState.statementIdx = STATEMENTIDX_NONE;
311  setScriptStateState(STATEMACHINESTATE_WAIT_FOR_CONDITION);
312  }
313 }
314 
315 bool MinitScript::parseStatement(const string_view& executableStatement, string_view& methodName, vector<ParserArgument>& arguments, const Statement& statement, string& accessObjectMemberStatement) {
316  if (VERBOSE == true) _Console::printLine("MinitScript::parseStatement(): " + getStatementInformation(statement) + ": '" + string(executableStatement) + "'");
317  //
318  string_view objectMemberAccessObject;
319  string_view objectMemberAccessMethod;
320  int executableStatementStartIdx = 0;
321  auto objectMemberAccess = getObjectMemberAccess(executableStatement, objectMemberAccessObject, objectMemberAccessMethod, executableStatementStartIdx, statement);
322  auto bracketCount = 0;
323  auto squareBracketCount = 0;
324  auto curlyBracketCount = 0;
325  auto quote = '\0';
326  auto methodStart = string::npos;
327  auto methodEnd = string::npos;
328  auto argumentStart = string::npos;
329  auto argumentEnd = string::npos;
330  auto quotedArgumentStart = string::npos;
331  auto quotedArgumentEnd = string::npos;
332  auto argumentSubLineIdx = -1;
333  auto lc = '\0';
334  //
335  auto subLineIdx = 0;
336  //
337  auto i = executableStatementStartIdx;
338  //
339  auto hasNextArgument = [&]() -> bool {
340  //
341  for (i++; i < executableStatement.size(); i++) {
342  auto c = executableStatement[i];
343  // do we have a non space character?
344  if (_Character::isSpace(c) == false) return c != ')';
345  }
346  //
347  return false;
348  };
349  //
350  auto createArgument = [&]() {
351  //
352  if (quotedArgumentStart != string::npos) {
353  auto argumentLength = quotedArgumentEnd - quotedArgumentStart + 1;
354  if (argumentLength > 0) arguments.emplace_back(_StringTools::viewTrim(string_view(&executableStatement[quotedArgumentStart], argumentLength)), argumentSubLineIdx);
355  quotedArgumentStart = string::npos;
356  quotedArgumentEnd = string::npos;
357  argumentSubLineIdx = -1;
358  } else
359  if (argumentStart != string::npos) {
360  if (argumentEnd == string::npos) argumentEnd = i - 1;
361  auto argumentLength = argumentEnd - argumentStart + 1;
362  if (argumentLength > 0) arguments.emplace_back(_StringTools::viewTrim(string_view(&executableStatement[argumentStart], argumentLength)), argumentSubLineIdx);
363  argumentStart = string::npos;
364  argumentEnd = string::npos;
365  argumentSubLineIdx = -1;
366  }
367  };
368  //
369  auto isObjectMemberAccess = [&]() -> bool {
370  //
371  auto j = i + 1;
372  // spaces
373  for (; j < executableStatement.size() && _Character::isSpace(executableStatement[j]) == true; j++);
374  //
375  if (j >= executableStatement.size()) return false;
376  // -
377  if (executableStatement[j++] != '-') return false;
378  //
379  if (j >= executableStatement.size()) return false;
380  // >
381  if (executableStatement[j++] != '>') return false;
382  //
383  return true;
384  };
385  //
386  methodStart = executableStatementStartIdx;
387  //
388  for (; i < executableStatement.size(); i++) {
389  //
390  auto c = executableStatement[i];
391  // quotes
392  if (squareBracketCount == 0 && curlyBracketCount == 0 && ((c == '"' || c == '\'') && lc != '\\')) {
393  // new quote
394  if (quote == '\0') {
395  //
396  if (bracketCount == 1) {
397  argumentStart = string::npos;
398  quotedArgumentStart = i;
399  argumentSubLineIdx = subLineIdx;
400  }
401  //
402  quote = c;
403  } else
404  // end of quote
405  if (quote == c) {
406  //
407  if (bracketCount == 1) {
408  quotedArgumentEnd = i;
409  //
410  if (isObjectMemberAccess() == true) {
411  //
412  if (quotedArgumentStart != string::npos) {
413  argumentStart = quotedArgumentStart;
414  argumentEnd = string::npos;
415  quotedArgumentStart = string::npos;
416  quotedArgumentEnd = string::npos;
417  }
418  }
419  }
420  //
421  quote = '\0';
422  }
423  } else
424  // we still have a quote, so do not process any other chars at other places
425  if (quote != '\0') {
426  // no op
427  } else {
428  // no quotes, handle \n
429  if (c == '\n') {
430  subLineIdx++;
431  } else
432  // (
433  if (c == '(') {
434  bracketCount++;
435  // mark the method end
436  if (bracketCount == 1) {
437  //
438  methodEnd = i - 1;
439  // check if we have a next argument and mark it
440  // if its a quoted argument, the non quoted argument will be unset later
441  if (hasNextArgument() == true) {
442  //
443  argumentStart = i;
444  argumentSubLineIdx = subLineIdx;
445  //
446  i--;
447  continue;
448  } else {
449  i--;
450  }
451  }
452  } else
453  // )
454  if (c == ')') {
455  //
456  bracketCount--;
457  // end of statement, create argument
458  if (bracketCount == 0) createArgument();
459  } else
460  // [
461  if (c == '[' && curlyBracketCount == 0) {
462  squareBracketCount++;
463  } else
464  // ]
465  if (c == ']' && curlyBracketCount == 0) {
466  squareBracketCount--;
467  } else
468  // {
469  if (c == '{') {
470  curlyBracketCount++;
471  } else
472  // }
473  if (c == '}') {
474  curlyBracketCount--;
475  } else
476  // ,
477  if (squareBracketCount == 0 && curlyBracketCount == 0 && bracketCount == 1 && c == ',') {
478  if (bracketCount == 1) {
479  // create argument
480  createArgument();
481  // check if we have a next argument and mark it
482  // if its a quoted argument, the non quoted argument will be unset later
483  if (hasNextArgument() == true) {
484  //
485  argumentStart = i;
486  argumentSubLineIdx = subLineIdx;
487  //
488  i--;
489  continue;
490  } else {
491  i--;
492  }
493  }
494  }
495  }
496  //
497  lc = lc == '\\' && c == '\\'?'\0':c;
498  }
499 
500  // extract method name
501  if (methodStart != string::npos) {
502  // we might have a statement without ()
503  if (methodEnd == string::npos) methodEnd = i - 1;
504  //
505  methodName = _StringTools::viewTrim(string_view(&executableStatement[methodStart], methodEnd - methodStart + 1));
506  }
507 
508  // handle object member access and generate internal.script.evaluateMemberAccess call
509  if (objectMemberAccess == true) {
510  // construct executable statement and arguments
511  string_view evaluateMemberAccessMethodName;
512  vector<ParserArgument> evaluateMemberAccessArguments;
513 
514  //
515  auto objectMemberAccessObjectVariable = viewIsVariableAccess(objectMemberAccessObject);
516 
517  // construct new method name and argument string views
518  accessObjectMemberStatement.reserve(16384); // TODO: check me later
519  auto idx = accessObjectMemberStatement.size();
520  accessObjectMemberStatement+= "internal.script.evaluateMemberAccess";
521  evaluateMemberAccessMethodName = string_view(&accessObjectMemberStatement.data()[idx], accessObjectMemberStatement.size() - idx);
522  accessObjectMemberStatement+= "(";
523  idx = accessObjectMemberStatement.size();
524  accessObjectMemberStatement+= objectMemberAccessObjectVariable == true?"\"" + string(objectMemberAccessObject) + "\"":"null";
525  evaluateMemberAccessArguments.emplace_back(string_view(&accessObjectMemberStatement.data()[idx], accessObjectMemberStatement.size() - idx), 0);
526  idx = accessObjectMemberStatement.size();
527  accessObjectMemberStatement+= ", ";
528  idx = accessObjectMemberStatement.size();
529  accessObjectMemberStatement+= objectMemberAccessObjectVariable == true?"null":string(objectMemberAccessObject);
530  evaluateMemberAccessArguments.emplace_back(string_view(&accessObjectMemberStatement.data()[idx], accessObjectMemberStatement.size() - idx), 0);
531  accessObjectMemberStatement+= ", ";
532  idx = accessObjectMemberStatement.size();
533  accessObjectMemberStatement+= "\"" + string(methodName) + "\"";
534  evaluateMemberAccessArguments.emplace_back(string_view(&accessObjectMemberStatement.data()[idx], accessObjectMemberStatement.size() - idx), 0);
535  for (const auto& argument: arguments) {
536  auto argumentVariable = viewIsVariableAccess(argument.argument);
537  accessObjectMemberStatement+= ", ";
538  idx = accessObjectMemberStatement.size();
539  accessObjectMemberStatement+= argumentVariable == true?"\"" + string(argument.argument) + "\"":"null";
540  evaluateMemberAccessArguments.emplace_back(string_view(&accessObjectMemberStatement.data()[idx], accessObjectMemberStatement.size() - idx), 0);
541  accessObjectMemberStatement+= ", ";
542  idx = accessObjectMemberStatement.size();
543  accessObjectMemberStatement+= argumentVariable == true?"null":string(argument.argument);
544  evaluateMemberAccessArguments.emplace_back(string_view(&accessObjectMemberStatement.data()[idx], accessObjectMemberStatement.size() - idx), 0);
545  }
546  accessObjectMemberStatement+= ")";
547  // set up new results
548  methodName = evaluateMemberAccessMethodName;
549  arguments = evaluateMemberAccessArguments;
550  }
551 
552  //
553  if (VERBOSE == true) {
554  _Console::printLine("MinitScript::parseStatement(): " + _StringTools::replace(getStatementInformation(statement), "\n", "\\n"));
555  _Console::printLine(string("\t") + ": Method: '" + string(methodName) + "'");
556  _Console::printLine(string("\t") + ": Arguments");
557  int variableIdx = 0;
558  for (const auto& argument: arguments) {
559  _Console::printLine(string("\t\t") + "@" + to_string(argument.subLineIdx) + "'" + _StringTools::replace(string(argument.argument), "\n", "\\n") + "'");
560  variableIdx++;
561  }
563  }
564 
565  // complain about bracket count
566  if (bracketCount != 0) {
567  // TODO: sub line index
568  _Console::printLine(getStatementInformation(statement) + ": " + string(executableStatement) + "': Unbalanced bracket count: " + to_string(_Math::abs(bracketCount)) + " " + (bracketCount < 0?"too much closed":"still open"));
569  //
570  parseErrors.push_back(string(executableStatement) + ": Unbalanced bracket count: " + to_string(_Math::abs(bracketCount)) + " " + (bracketCount < 0?"too much closed":"still open"));
571  //
572  return false;
573  }
574  // complain about square bracket count
575  if (squareBracketCount != 0) {
576  // TODO: sub line index
577  _Console::printLine(getStatementInformation(statement) + ": " + string(executableStatement) + "': Unbalanced square bracket count: " + to_string(_Math::abs(squareBracketCount)) + " " + (squareBracketCount < 0?"too much closed":"still open"));
578  //
579  parseErrors.push_back(string(executableStatement) + ": Unbalanced square bracket count: " + to_string(_Math::abs(squareBracketCount)) + " " + (squareBracketCount < 0?"too much closed":"still open"));
580  //
581  return false;
582  }
583  // complain about curly bracket count
584  if (curlyBracketCount != 0) {
585  // TODO: sub line index
586  _Console::printLine(getStatementInformation(statement) + ": " + string(executableStatement) + "': Unbalanced curly bracket count: " + to_string(_Math::abs(curlyBracketCount)) + " " + (curlyBracketCount < 0?"too much closed":"still open"));
587  //
588  parseErrors.push_back(string(executableStatement) + ": Unbalanced curly bracket count: " + to_string(_Math::abs(curlyBracketCount)) + " " + (curlyBracketCount < 0?"too much closed":"still open"));
589  //
590  return false;
591  }
592 
593  //
594  return true;
595 }
596 
597 MinitScript::Variable MinitScript::executeStatement(const SyntaxTreeNode& syntaxTree, const Statement& statement) {
598  if (VERBOSE == true) _Console::printLine("MinitScript::executeStatement(): " + getStatementInformation(statement) + "': " + syntaxTree.value.getValueAsString() + "(" + getArgumentsAsString(syntaxTree.arguments) + ")");
599  // return on literal or empty syntaxTree
600  if (syntaxTree.type == SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL) {
601  return initializeVariable(syntaxTree.value);
602  }
603  //
604  vector<Variable> arguments;
605  Variable returnValue;
606  // construct argument values
607  for (const auto& argument: syntaxTree.arguments) {
608  switch (argument.type) {
609  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL:
610  {
611  arguments.push_back(initializeVariable(argument.value));
612  break;
613  }
614  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_FUNCTION:
615  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD:
616  {
617  arguments.push_back(executeStatement(argument, statement));
618  break;
619  }
620  default:
621  break;
622  }
623  }
624  //
625  if (VERBOSE == true) {
626  _Console::printLine("MinitScript::executeStatement(): '" + getStatementInformation(statement) + ": " + syntaxTree.value.getValueAsString() + "(" + getArgumentsAsString(syntaxTree.arguments) + ")");
627  }
628  // try first function
629  if (syntaxTree.type == SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_FUNCTION) {
630  // call
631  span argumentsSpan(arguments);
632  call(syntaxTree.getScriptIdx(), argumentsSpan, returnValue);
633  //
634  return returnValue;
635  } else
636  if (syntaxTree.type == SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_STACKLET) {
637  // call
638  span argumentsSpan(arguments);
639  callStacklet(syntaxTree.getScriptIdx(), argumentsSpan, returnValue);
640  //
641  return returnValue;
642  } else
643  if (syntaxTree.type == SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD) {
644  // try methods next
645  auto method = syntaxTree.getMethod();
646  // validate arguments
647  if (VALIDATION == true) {
648  auto argumentIdx = 0;
649  for (const auto& argumentType: method->getArgumentTypes()) {
650  auto argumentOk = true;
651  // nullable and NULL argument
652  if (argumentType.nullable == true &&
653  argumentIdx >= 0 && argumentIdx < arguments.size() &&
654  arguments[argumentIdx].getType() == TYPE_NULL) {
655  argumentOk = true;
656  } else {
657  // otherwise check the argument
658  switch(argumentType.type) {
659  case TYPE_NULL:
660  break;
661  case TYPE_BOOLEAN:
662  {
663  bool booleanValue;
664  argumentOk = getBooleanValue(arguments, argumentIdx, booleanValue, argumentType.optional);
665  }
666  break;
667  case TYPE_INTEGER:
668  {
669  int64_t integerValue;
670  argumentOk = getIntegerValue(arguments, argumentIdx, integerValue, argumentType.optional);
671  }
672  break;
673  case TYPE_FLOAT:
674  {
675  float floatValue;
676  argumentOk = getFloatValue(arguments, argumentIdx, floatValue, argumentType.optional);
677  }
678  break;
679  case TYPE_PSEUDO_NUMBER:
680  {
681  float floatValue;
682  argumentOk = getFloatValue(arguments, argumentIdx, floatValue, argumentType.optional);
683  break;
684  }
685  case TYPE_PSEUDO_MIXED:
686  {
687  argumentOk = true;
688  break;
689  }
690  case TYPE_STRING:
691  {
692  string stringValue;
693  argumentOk = getStringValue(arguments, argumentIdx, stringValue, argumentType.optional);
694  }
695  break;
696  case TYPE_BYTEARRAY:
697  {
698  argumentOk =
699  argumentIdx < 0 || argumentIdx >= arguments.size()?
700  argumentType.optional:
701  arguments[argumentIdx].getType() == TYPE_BYTEARRAY;
702  break;
703  }
704  case TYPE_ARRAY:
705  {
706  argumentOk =
707  argumentIdx < 0 || argumentIdx >= arguments.size()?
708  argumentType.optional:
709  arguments[argumentIdx].getType() == TYPE_ARRAY;
710  break;
711  }
712  case TYPE_MAP:
713  {
714  argumentOk =
715  argumentIdx < 0 || argumentIdx >= arguments.size()?
716  argumentType.optional:
717  arguments[argumentIdx].getType() == TYPE_MAP;
718  break;
719  }
720  case TYPE_SET:
721  {
722  argumentOk =
723  argumentIdx < 0 || argumentIdx >= arguments.size()?
724  argumentType.optional:
725  arguments[argumentIdx].getType() == TYPE_SET;
726  break;
727  }
728  default:
729  {
730  // custom data types
731  argumentOk =
732  argumentIdx < 0 || argumentIdx >= arguments.size()?
733  argumentType.optional:
734  arguments[argumentIdx].getType() == argumentType.type;
735  break;
736  }
737 
738  }
739  }
740  if (argumentOk == false) {
742  getStatementInformation(statement, syntaxTree.subLineIdx) +
743  ": Method '" + string(syntaxTree.value.getValueAsString()) + "'" +
744  ": argument value @ " + to_string(argumentIdx) + ": expected " + Variable::getTypeAsString(argumentType.type) + ", but got: " + (argumentIdx < arguments.size()?arguments[argumentIdx].getAsString():"nothing"));
745  }
746  argumentIdx++;
747  }
748  if (method->isVariadic() == false && arguments.size() > method->getArgumentTypes().size()) {
750  getStatementInformation(statement, syntaxTree.subLineIdx) +
751  ": Method '" + string(syntaxTree.value.getValueAsString()) + "'" +
752  ": too many arguments: expected: " + to_string(method->getArgumentTypes().size()) + ", got " + to_string(arguments.size()));
753  }
754  }
755  // execute method
756  span argumentsSpan(arguments);
757  method->executeMethod(argumentsSpan, returnValue, SubStatement(statement, syntaxTree.subLineIdx));
758  // check return type
759  if (VALIDATION == true) {
760  if (method->isReturnValueNullable() == true && returnValue.getType() == TYPE_NULL) {
761  // no op, this is a valid return value
762  } else
763  if (MinitScript::Variable::isExpectedType(returnValue.getType(), method->getReturnValueType()) == false) {
765  getStatementInformation(statement, syntaxTree.subLineIdx) +
766  ": Method '" + string(syntaxTree.value.getValueAsString()) + "'" +
767  ": return value: expected " + Variable::getReturnTypeAsString(method->getReturnValueType(), method->isReturnValueNullable()) + ", but got: " + Variable::getReturnTypeAsString(returnValue.getType(), false));
768  }
769  }
770  //
771  return returnValue;
772  }
773  //
774  return returnValue;
775 }
776 
777 bool MinitScript::createStatementSyntaxTree(int scriptIdx, const string_view& methodName, const vector<ParserArgument>& arguments, const Statement& statement, SyntaxTreeNode& syntaxTree, int subLineIdx) {
778  if (VERBOSE == true) {
779  //
780  auto getArgumentsAsString = [](const vector<ParserArgument>& arguments) -> const string {
781  string argumentsString;
782  for (const auto& argument: arguments) argumentsString+= (argumentsString.empty() == false?", ":"") + string("@") + to_string(argument.subLineIdx) + string("'") + string(argument.argument) + string("'");
783  return argumentsString;
784  };
785  //
786  _Console::printLine("MinitScript::createScriptStatementSyntaxTree(): " + getStatementInformation(statement) + ": " + string(methodName) + "(" + getArgumentsAsString(arguments) + ")");
787  }
788  // method/function
789  auto functionScriptIdx = SCRIPTIDX_NONE;
790  Method* method = nullptr;
791  // try first user functions
792  {
793  auto functionsIt = functions.find(string(methodName));
794  if (functionsIt != functions.end()) {
795  functionScriptIdx = functionsIt->second;
796  }
797  }
798  // try methods next
799  {
800  auto methodsIt = methods.find(string(methodName));
801  if (methodsIt != methods.end()) {
802  method = methodsIt->second;
803  }
804  }
805 
806  // arguments
807  vector<bool> argumentReferences(0);
808  if (functionScriptIdx != SCRIPTIDX_NONE) {
809  const auto& script = scripts[functionScriptIdx];
810  if (script.type == Script::TYPE_STACKLET) {
811  if (arguments.empty() == false) {
812  _Console::printLine(getStatementInformation(statement) + ": A stacklet must not be called with any arguments: " + string(methodName));
813  //
814  parseErrors.push_back(getStatementInformation(statement) + ": A stacklet must not be called with any arguments: " + string(methodName));
815  //
816  return false;
817  }
818  } else {
819  argumentReferences.resize(script.arguments.size());
820  auto argumentIdx = 0;
821  for (const auto& argument: scripts[functionScriptIdx].arguments) {
822  argumentReferences[argumentIdx++] = argument.reference;
823  }
824  }
825  } else
826  if (method != nullptr) {
827  argumentReferences.resize(method->getArgumentTypes().size());
828  auto argumentIdx = 0;
829  for (const auto& argument: method->getArgumentTypes()) {
830  argumentReferences[argumentIdx++] = argument.reference;
831  }
832  }
833  auto argumentIdx = 0;
834  for (const auto& argument: arguments) {
835  // object member access
836  string_view accessObjectMemberObject;
837  string_view accessObjectMemberMethod;
838  int accessObjectMemberStartIdx;
839  vector<string_view> lamdaFunctionStackletArguments;
840  string_view lamdaFunctionStackletScriptCode;
841  int lamdaFunctionStackletLineIdx = statement.line + subLineIdx + argument.subLineIdx;
842  if (viewIsLamdaFunction(argument.argument, lamdaFunctionStackletArguments, lamdaFunctionStackletScriptCode, lamdaFunctionStackletLineIdx) == true) {
843  Variable variable;
844  createLamdaFunction(variable, lamdaFunctionStackletArguments, lamdaFunctionStackletScriptCode, lamdaFunctionStackletLineIdx, false, statement);
845  SyntaxTreeNode subSyntaxTree(SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL, variable, nullptr, {}, subLineIdx + argument.subLineIdx);
846  syntaxTree.arguments.push_back(subSyntaxTree);
847  } else
848  if (viewIsStacklet(argument.argument, lamdaFunctionStackletArguments, lamdaFunctionStackletScriptCode, lamdaFunctionStackletLineIdx) == true) {
849  string scopeName;
850  // empty scope means root scope
851  if (scriptIdx != SCRIPTIDX_NONE) {
852  // function are a valid scope for stacklets
853  if (scripts[scriptIdx].type == Script::TYPE_FUNCTION) {
854  scopeName = scripts[scriptIdx].condition;
855  }
856  // TODO: as well as stacklets
857  }
858  Variable variable;
859  createStacklet(variable, scopeName, lamdaFunctionStackletArguments, lamdaFunctionStackletScriptCode, lamdaFunctionStackletLineIdx, statement);
860  SyntaxTreeNode subSyntaxTree(SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL, variable, nullptr, {}, subLineIdx + argument.subLineIdx);
861  syntaxTree.arguments.push_back(subSyntaxTree);
862  } else
863  if (getObjectMemberAccess(argument.argument, accessObjectMemberObject, accessObjectMemberMethod, accessObjectMemberStartIdx, statement) == true) {
864  // object member access
865  string_view subMethodName;
866  vector<ParserArgument> subArguments;
867  string accessObjectMemberStatement;
868  //
869  if (parseStatement(argument.argument, subMethodName, subArguments, statement, accessObjectMemberStatement) == true) {
870  SyntaxTreeNode subSyntaxTree;
871  if (createStatementSyntaxTree(scriptIdx, subMethodName, subArguments, statement, subSyntaxTree, subLineIdx + argument.subLineIdx) == false) {
872  return false;
873  }
874  syntaxTree.arguments.push_back(subSyntaxTree);
875  } else {
876  return false;
877  }
878  } else
879  // plain variable
880  if (viewIsVariableAccess(argument.argument) == true) {
881  //
882  Variable value;
883  value.setValue(deescape(argument.argument, statement));
884  // look up getVariable method
885  string methodName =
886  argumentIdx >= argumentReferences.size() || argumentReferences[argumentIdx] == false?
887  (method != nullptr?"getMethodArgumentVariable":"getVariable"):
888  "getVariableReference";
889  //
890  Method* method = nullptr;
891  {
892  auto methodsIt = methods.find(methodName);
893  if (methodsIt != methods.end()) {
894  method = methodsIt->second;
895  } else {
896  _Console::printLine(getStatementInformation(statement, subLineIdx + argument.subLineIdx) + ": Unknown method: " + methodName);
897  //
898  parseErrors.push_back(getStatementInformation(statement, subLineIdx + argument.subLineIdx) + ": Unknown method: " + methodName);
899  //
900  return false;
901  }
902  }
903  //
904  syntaxTree.arguments.emplace_back(
905  SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD,
906  MinitScript::Variable(deescape(methodName, statement)),
907  method,
908  initializer_list<SyntaxTreeNode>
909  {
910  {
911  SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL,
912  value,
913  nullptr,
914  {},
915  subLineIdx + argument.subLineIdx
916  }
917  },
918  subLineIdx + argument.subLineIdx
919  );
920  } else
921  // method call
922  if (argument.argument.empty() == false &&
923  viewIsStringLiteral(argument.argument) == false &&
924  viewIsInitializer(argument.argument) == false &&
925  viewIsCall(argument.argument) == true) {
926  // method call
927  string_view subMethodName;
928  vector<ParserArgument> subArguments;
929  string accessObjectMemberStatement;
930  //
931  if (parseStatement(argument.argument, subMethodName, subArguments, statement, accessObjectMemberStatement) == true) {
932  SyntaxTreeNode subSyntaxTree;
933  if (createStatementSyntaxTree(scriptIdx, subMethodName, subArguments, statement, subSyntaxTree, subLineIdx + argument.subLineIdx) == false) {
934  return false;
935  }
936  syntaxTree.arguments.push_back(subSyntaxTree);
937  } else {
938  //
939  return false;
940  }
941  } else {
942  // implicitely literal
943  Variable value;
944  value.setImplicitTypedValueFromStringView(argument.argument, this, scriptIdx, statement);
945  //
946  syntaxTree.arguments.emplace_back(
947  SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL,
948  value,
949  nullptr,
950  initializer_list<SyntaxTreeNode>{},
951  subLineIdx + argument.subLineIdx
952  );
953  }
954  //
955  argumentIdx++;
956  }
957  // try first user functions
958  if (functionScriptIdx != SCRIPTIDX_NONE) {
959  syntaxTree.type = scripts[functionScriptIdx].type == Script::TYPE_FUNCTION?SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_FUNCTION:SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_STACKLET;
960  syntaxTree.value.setValue(deescape(methodName, statement));
961  syntaxTree.subLineIdx = subLineIdx;
962  syntaxTree.setScriptIdx(functionScriptIdx);
963  //
964  return true;
965  } else
966  // try methods next
967  if (method != nullptr) {
968  syntaxTree.type = SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD;
969  syntaxTree.value.setValue(deescape(methodName, statement));
970  syntaxTree.subLineIdx = subLineIdx;
971  syntaxTree.setMethod(method);
972  //
973  return true;
974  } else {
975  _Console::printLine(getStatementInformation(statement, subLineIdx) + ": Unknown function/method: " + string(methodName) + "()");
976  //
977  parseErrors.push_back(getStatementInformation(statement, subLineIdx) + ": Unknown function/method: " + string(methodName) + "()");
978  //
979  return false;
980  }
981  //
982  return false;
983 }
984 
985 bool MinitScript::setupFunctionAndStackletScriptIndices(int scriptIdx) {
986  //
987  auto& script = scripts[scriptIdx];
988  auto statementIdx = STATEMENTIDX_FIRST;
989  //
990  for (auto& syntaxTreeNode: script.syntaxTree) {
991  auto& statement = script.statements[statementIdx++];
992  //
993  if (setupFunctionAndStackletScriptIndices(syntaxTreeNode, statement) == false) {
994  //
995  return false;
996  }
997  }
998  //
999  return true;
1000 
1001 }
1002 
1003 bool MinitScript::setupFunctionAndStackletScriptIndices(SyntaxTreeNode& syntaxTreeNode, const Statement& statement) {
1004  switch (syntaxTreeNode.type) {
1005  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL:
1006  {
1007  switch(syntaxTreeNode.value.getType()) {
1008  case(MinitScript::TYPE_ARRAY):
1009  case(MinitScript::TYPE_MAP):
1010  {
1011  if (setupFunctionAndStackletScriptIndices(syntaxTreeNode.value, statement, syntaxTreeNode.subLineIdx) == false) return false;
1012  //
1013  break;
1014  }
1015  case(MinitScript::TYPE_FUNCTION_ASSIGNMENT):
1016  {
1017  string function;
1018  auto functionScriptIdx = SCRIPTIDX_NONE;
1019  if (syntaxTreeNode.value.getFunctionValue(function, functionScriptIdx) == false ||
1020  (functionScriptIdx = getFunctionScriptIdx(function)) == SCRIPTIDX_NONE) {
1021  //
1023  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1024  ": Function not found: " +
1025  syntaxTreeNode.value.getValueAsString()
1026  );
1027  //
1028  parseErrors.push_back(
1029  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1030  ": Function not found: " +
1031  syntaxTreeNode.value.getValueAsString()
1032  );
1033  //
1034  return false;
1035  }
1036  //
1037  syntaxTreeNode.value.setFunctionAssignment(function, functionScriptIdx);
1038  //
1039  break;
1040  }
1041  case(MinitScript::TYPE_STACKLET_ASSIGNMENT):
1042  {
1043  string stacklet;
1044  auto stackletScriptIdx = SCRIPTIDX_NONE;
1045  if (syntaxTreeNode.value.getStackletValue(stacklet, stackletScriptIdx) == false ||
1046  (stackletScriptIdx = getFunctionScriptIdx(stacklet)) == SCRIPTIDX_NONE) {
1047  //
1049  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1050  ": Stacklet not found" +
1051  syntaxTreeNode.value.getValueAsString()
1052  );
1053  //
1054  parseErrors.push_back(
1055  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1056  ": Stacklet not found: " +
1057  syntaxTreeNode.value.getValueAsString()
1058  );
1059  //
1060  return false;
1061  }
1062  //
1063  syntaxTreeNode.value.setStackletAssignment(stacklet, stackletScriptIdx);
1064  //
1065  break;
1066  }
1067  default:
1068  break;
1069  }
1070  //
1071  break;
1072  }
1073  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD:
1074  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_FUNCTION:
1075  {
1076  for (auto& argument: syntaxTreeNode.arguments) {
1077  if (setupFunctionAndStackletScriptIndices(argument, statement) == false) return false;
1078  }
1079  //
1080  break;
1081  }
1082  default:
1083  break;
1084  }
1085  //
1086  return true;
1087 }
1088 
1089 bool MinitScript::setupFunctionAndStackletScriptIndices(Variable& variable, const Statement& statement, int subLineIdx) {
1090  switch (variable.getType()) {
1091  case TYPE_ARRAY:
1092  {
1093  auto arrayPointer = variable.getArrayPointer();
1094  if (arrayPointer == nullptr) break;
1095  for (auto arrayEntry: *arrayPointer) {
1096  if (setupFunctionAndStackletScriptIndices(*arrayEntry, statement, subLineIdx) == false) return false;
1097  }
1098  //
1099  break;
1100  }
1101  case TYPE_MAP:
1102  {
1103  //
1104  auto mapPointer = variable.getMapPointer();
1105  if (mapPointer == nullptr) break;
1106  for (auto& [mapKey, mapValue]: *mapPointer) {
1107  if (setupFunctionAndStackletScriptIndices(*mapValue, statement, subLineIdx) == false) return false;
1108  }
1109  //
1110  break;
1111  }
1112  case TYPE_FUNCTION_ASSIGNMENT:
1113  {
1114  string function;
1115  auto functionScriptIdx = SCRIPTIDX_NONE;
1116  if (variable.getFunctionValue(function, functionScriptIdx) == false ||
1117  (functionScriptIdx = getFunctionScriptIdx(function)) == SCRIPTIDX_NONE) {
1118  //
1120  getStatementInformation(statement, subLineIdx) +
1121  ": Function not found: " +
1122  variable.getValueAsString()
1123  );
1124  //
1125  parseErrors.push_back(
1126  getStatementInformation(statement, subLineIdx) +
1127  ": Function not found: " +
1128  variable.getValueAsString()
1129  );
1130  //
1131  return false;
1132  }
1133  //
1134  variable.setFunctionAssignment(function, functionScriptIdx);
1135  //
1136  break;
1137  }
1138  case TYPE_STACKLET_ASSIGNMENT:
1139  {
1140  string stacklet;
1141  auto stackletScriptIdx = SCRIPTIDX_NONE;
1142  if (variable.getStackletValue(stacklet, stackletScriptIdx) == false ||
1143  (stackletScriptIdx = getFunctionScriptIdx(stacklet)) == SCRIPTIDX_NONE) {
1144  //
1146  getStatementInformation(statement, subLineIdx) +
1147  ": Stacklet not found" +
1148  variable.getValueAsString()
1149  );
1150  //
1151  parseErrors.push_back(
1152  getStatementInformation(statement, subLineIdx) +
1153  ": Stacklet not found: " +
1154  variable.getValueAsString()
1155  );
1156  //
1157  return false;
1158  }
1159  //
1160  variable.setStackletAssignment(stacklet, stackletScriptIdx);
1161  //
1162  break;
1163  }
1164  default: break;
1165  }
1166  //
1167  return true;
1168 }
1169 
1170 int MinitScript::getStackletScopeScriptIdx(int scriptIdx) {
1171  if (scriptIdx < 0 || scriptIdx >= scripts.size() ||
1172  scripts[scriptIdx].type != MinitScript::Script::TYPE_STACKLET) {
1173  return MinitScript::SCRIPTIDX_NONE;
1174  }
1175  //
1176  const auto& stackletScript = scripts[scriptIdx];
1177  const auto& stackletScopeName = stackletScript.arguments.size() == 1?stackletScript.arguments[0].name:string();
1178  if (stackletScopeName.empty() == true) {
1179  return MinitScript::SCRIPTIDX_NONE;
1180  }
1181  //
1182  for (auto i = 0; i < scripts.size(); i++) {
1183  if (i == scriptIdx) continue;
1184  const auto& scriptCandidate = scripts[i];
1185  if (scriptCandidate.type != MinitScript::Script::TYPE_FUNCTION && scriptCandidate.type != MinitScript::Script::TYPE_STACKLET) continue;
1186  if (scriptCandidate.condition == stackletScopeName) {
1187  if (scriptCandidate.type == MinitScript::Script::TYPE_STACKLET) return getStackletScopeScriptIdx(i); else return i;
1188  }
1189  }
1190  //
1191  return MinitScript::SCRIPTIDX_NONE;
1192 }
1193 
1194 bool MinitScript::validateStacklets(int scriptIdx) {
1195  //
1196  const auto& script = scripts[scriptIdx];
1197  auto statementIdx = STATEMENTIDX_FIRST;
1198  //
1199  for (const auto& syntaxTreeNode: script.syntaxTree) {
1200  const auto& statement = script.statements[statementIdx++];
1201  //
1202  if (validateStacklets(script.type == Script::TYPE_FUNCTION?scriptIdx:SCRIPTIDX_NONE, syntaxTreeNode, statement) == false) {
1203  //
1204  return false;
1205  }
1206  }
1207  //
1208  return true;
1209 }
1210 
1211 bool MinitScript::validateStacklets(const string& function, int scopeScriptIdx) {
1212  auto functionScriptIdx = getFunctionScriptIdx(function);
1213  if (functionScriptIdx == SCRIPTIDX_NONE) {
1214  _Console::printLine("MinitScript::validateStacklet(): function not found: " + function);
1215  return false;
1216  }
1217  //
1218  const auto& script = scripts[functionScriptIdx];
1219  auto statementIdx = STATEMENTIDX_FIRST;
1220  //
1221  for (const auto& syntaxTreeNode: script.syntaxTree) {
1222  const auto& statement = script.statements[statementIdx++];
1223  //
1224  if (validateStacklets(scopeScriptIdx == MinitScript::SCRIPTIDX_NONE?functionScriptIdx:scopeScriptIdx, syntaxTreeNode, statement) == false) {
1225  //
1226  return false;
1227  }
1228  }
1229  //
1230  return true;
1231 }
1232 
1233 bool MinitScript::validateStacklets(int scopeScriptIdx, const SyntaxTreeNode& syntaxTreeNode, const Statement& statement) {
1234  switch (syntaxTreeNode.type) {
1235  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL:
1236  {
1237  // TODO: improve me! This is actually litaral only, which can be also set as variable and be reused later
1238  // basically we forbid here to create a stacklet assignment variable with wrong scope in a given scope
1239  if (syntaxTreeNode.value.getType() == MinitScript::TYPE_STACKLET_ASSIGNMENT) {
1240  // we only allow assignments of stacklets with a correct scope, means
1241  string stackletName;
1242  auto stackletScriptIdx = SCRIPTIDX_NONE;
1243  if (syntaxTreeNode.value.getStackletValue(stackletName, stackletScriptIdx) == false ||
1244  (stackletScriptIdx = getFunctionScriptIdx(stackletName)) == SCRIPTIDX_NONE) {
1245  //
1247  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1248  ": " +
1249  syntaxTreeNode.value.getValueAsString() +
1250  ": Stacklet not found"
1251  );
1252  //
1253  parseErrors.push_back(
1254  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1255  ": " +
1256  syntaxTreeNode.value.getValueAsString() +
1257  ": Stacklet not found"
1258  );
1259  //
1260  return false;
1261  }
1262  //
1263  int stackletScopeScriptIdx = getStackletScopeScriptIdx(stackletScriptIdx);
1264  if (stackletScopeScriptIdx != scopeScriptIdx) {
1265  // construct scope error
1266  string scopeErrorMessage;
1267  if (stackletScopeScriptIdx == SCRIPTIDX_NONE) {
1268  scopeErrorMessage = "Stacklet requires root scope";
1269  } else {
1270  scopeErrorMessage = "Stacklet requires scope of " + scripts[stackletScopeScriptIdx].condition + "()";
1271  }
1272  scopeErrorMessage+= ", but has scope of ";
1273  if (scopeScriptIdx == SCRIPTIDX_NONE) {
1274  scopeErrorMessage+= "root scope";
1275  } else {
1276  scopeErrorMessage+= scripts[scopeScriptIdx].condition + "()";
1277  }
1278  //
1280  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1281  ": " +
1282  syntaxTreeNode.value.getValueAsString() +
1283  ": Stacklet scope invalid: " +
1284  scopeErrorMessage
1285  );
1286  //
1287  parseErrors.push_back(
1288  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1289  ": " +
1290  syntaxTreeNode.value.getValueAsString() +
1291  ": Stacklet scope invalid" +
1292  scopeErrorMessage
1293  );
1294  //
1295  return false;
1296  }
1297  // check stacklet itself for stacklet litarals
1298  if (validateStacklets(stackletName, scopeScriptIdx) == false) return false;
1299  }
1300  //
1301  break;
1302  }
1303  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD:
1304  {
1305  for (const auto& argument: syntaxTreeNode.arguments) {
1306  if (validateStacklets(scopeScriptIdx, argument, statement) == false) return false;
1307  }
1308  //
1309  break;
1310  }
1311  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_FUNCTION:
1312  {
1313  for (const auto& argument: syntaxTreeNode.arguments) {
1314  if (validateStacklets(scopeScriptIdx, argument, statement) == false) return false;
1315  }
1316  //
1317  if (getFunctionScriptIdx(syntaxTreeNode.value.getValueAsString()) == scopeScriptIdx) {
1318  // recursion
1319  } else {
1320  validateStacklets(syntaxTreeNode.value.getValueAsString());
1321  }
1322  //
1323  break;
1324  }
1325  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_STACKLET:
1326  {
1327  //
1328  string stackletName = syntaxTreeNode.value.getValueAsString();
1329  auto stackletScriptIdx = syntaxTreeNode.getScriptIdx();
1330  if (stackletName.empty() == true || stackletScriptIdx == SCRIPTIDX_NONE) {
1331  //
1333  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1334  ": " +
1335  syntaxTreeNode.value.getValueAsString() +
1336  ": Stacklet not found"
1337  );
1338  //
1339  parseErrors.push_back(
1340  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1341  ": " +
1342  syntaxTreeNode.value.getValueAsString() +
1343  ": Stacklet not found"
1344  );
1345  //
1346  return false;
1347  }
1348  //
1349  int stackletScopeScriptIdx = getStackletScopeScriptIdx(stackletScriptIdx);
1350  if (stackletScopeScriptIdx != scopeScriptIdx) {
1351  // construct scope error
1352  string scopeErrorMessage;
1353  if (stackletScopeScriptIdx == SCRIPTIDX_NONE) {
1354  scopeErrorMessage = "Stacklet requires root scope";
1355  } else {
1356  scopeErrorMessage = "Stacklet requires scope of " + scripts[stackletScopeScriptIdx].condition + "()";
1357  }
1358  scopeErrorMessage+= ", but has scope of ";
1359  if (scopeScriptIdx == SCRIPTIDX_NONE) {
1360  scopeErrorMessage+= "root scope";
1361  } else {
1362  scopeErrorMessage+= scripts[scopeScriptIdx].condition + "()";
1363  }
1364  //
1366  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1367  ": " +
1368  syntaxTreeNode.value.getValueAsString() +
1369  ": Stacklet scope invalid: " +
1370  scopeErrorMessage
1371  );
1372  //
1373  parseErrors.push_back(
1374  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1375  ": " +
1376  syntaxTreeNode.value.getValueAsString() +
1377  ": Stacklet scope invalid" +
1378  scopeErrorMessage
1379  );
1380  //
1381  return false;
1382  }
1383  //
1384  validateStacklets(syntaxTreeNode.value.getValueAsString(), scopeScriptIdx);
1385  //
1386  break;
1387  }
1388  default:
1389  break;
1390  }
1391  //
1392  return true;
1393 }
1394 
1395 bool MinitScript::validateCallable(const string& function) {
1396  auto functionScriptIdx = getFunctionScriptIdx(function);
1397  if (functionScriptIdx == SCRIPTIDX_NONE) {
1398  _Console::printLine("MinitScript::validateCallable(): function not found: " + function);
1399  return false;
1400  }
1401  //
1402  const auto& script = scripts[functionScriptIdx];
1403  auto statementIdx = STATEMENTIDX_FIRST;
1404  //
1405  for (const auto& syntaxTreeNode: script.syntaxTree) {
1406  const auto& statement = script.statements[statementIdx++];
1407  //
1408  if (validateCallable(syntaxTreeNode, statement) == false) {
1409  //
1410  return false;
1411  }
1412  }
1413  //
1414  return true;
1415 }
1416 
1417 bool MinitScript::validateCallable(const SyntaxTreeNode& syntaxTreeNode, const Statement& statement) {
1418  //
1419  switch (syntaxTreeNode.type) {
1420  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL:
1421  {
1422  break;
1423  }
1424  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD:
1425  {
1426  const auto& contextFunctions = syntaxTreeNode.getMethod()->getContextFunctions();
1427  if (contextFunctions.empty() == false) {
1428  //
1430  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1431  ": Method " +
1432  syntaxTreeNode.getMethod()->getMethodName() + "() can not be called within a callable function"
1433  );
1434  //
1435  parseErrors.push_back(
1436  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1437  ": Method " +
1438  syntaxTreeNode.getMethod()->getMethodName() + "() can not be called within a callable function"
1439  );
1440  //
1441  return false;
1442  }
1443  }
1444  break;
1445  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_FUNCTION:
1446  {
1447  for (const auto& argument: syntaxTreeNode.arguments) {
1448  if (validateCallable(argument, statement) == false) return false;
1449  }
1450  //
1451  validateCallable(syntaxTreeNode.value.getValueAsString());
1452  //
1453  break;
1454  }
1455  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_STACKLET:
1456  {
1457  validateCallable(syntaxTreeNode.value.getValueAsString());
1458  //
1459  break;
1460  }
1461  default:
1462  break;
1463  }
1464  //
1465  return true;
1466 }
1467 
1468 bool MinitScript::validateContextFunctions(const string& function, vector<string>& functionStack) {
1469  auto functionScriptIdx = getFunctionScriptIdx(function);
1470  if (functionScriptIdx == SCRIPTIDX_NONE) {
1471  _Console::printLine("MinitScript::validateContextFunctions(): Function not found: " + function);
1472  return false;
1473  }
1474  //
1475  const auto& script = scripts[functionScriptIdx];
1476  auto statementIdx = STATEMENTIDX_FIRST;
1477  //
1478  functionStack.push_back(script.condition);
1479  //
1480  for (const auto& syntaxTreeNode: script.syntaxTree) {
1481  const auto& statement = script.statements[statementIdx++];
1482  //
1483  if (validateContextFunctions(syntaxTreeNode, functionStack, statement) == false) {
1484  //
1485  return false;
1486  }
1487  }
1488  //
1489  functionStack.erase(functionStack.begin() + functionStack.size() - 1);
1490  //
1491  return true;
1492 }
1493 
1494 bool MinitScript::validateContextFunctions(const SyntaxTreeNode& syntaxTreeNode, vector<string>& functionStack, const Statement& statement) {
1495  //
1496  switch (syntaxTreeNode.type) {
1497  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_LITERAL:
1498  {
1499  break;
1500  }
1501  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_METHOD:
1502  {
1503  const auto& contextFunctions = syntaxTreeNode.getMethod()->getContextFunctions();
1504  if (contextFunctions.empty() == false) {
1505  //
1506  string contextFunctionsString;
1507  for (const auto &contextFunction: contextFunctions) {
1508  if (contextFunctionsString.empty() == false) contextFunctionsString+= ", ";
1509  contextFunctionsString+= contextFunction + "()";
1510  }
1511  //
1512  const auto& functionStackFunction = functionStack[0];
1513  if (find(contextFunctions.begin(), contextFunctions.end(), functionStackFunction) == contextFunctions.end()) {
1514  //
1515  string contextFunctionsString;
1516  for (const auto &contextFunction: contextFunctions) {
1517  if (contextFunctionsString.empty() == false) contextFunctionsString+= ", ";
1518  contextFunctionsString+= contextFunction + "()";
1519  }
1520  //
1522  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1523  ": Method " +
1524  syntaxTreeNode.getMethod()->getMethodName() + "() can only be called within the following functions: " +
1525  contextFunctionsString +
1526  ", but was called from " +
1527  functionStackFunction + "()"
1528  );
1529  //
1530  parseErrors.push_back(
1531  getStatementInformation(statement, syntaxTreeNode.subLineIdx) +
1532  ": Method " +
1533  syntaxTreeNode.getMethod()->getMethodName() + "() can only be called within the following functions: " +
1534  contextFunctionsString +
1535  ", but was called from " +
1536  functionStackFunction + "()"
1537  );
1538  //
1539  return false;
1540  }
1541  }
1542  }
1543  break;
1544  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_FUNCTION:
1545  case SyntaxTreeNode::SCRIPTSYNTAXTREENODE_EXECUTE_STACKLET:
1546  {
1547  for (const auto& argument: syntaxTreeNode.arguments) {
1548  if (validateContextFunctions(argument, functionStack, statement) == false) return false;
1549  }
1550  //
1551  validateContextFunctions(syntaxTreeNode.value.getValueAsString(), functionStack);
1552  //
1553  break;
1554  }
1555  default:
1556  break;
1557  }
1558  //
1559  return true;
1560 }
1561 
1562 void MinitScript::emit(const string& condition) {
1563  // defer emit if a function/stacklet is still running
1564  if (isFunctionRunning() == true) {
1565  deferredEmit = condition;
1566  return;
1567  }
1568  //
1569  auto scriptIdxToStart = 0;
1570  for (const auto& script: scripts) {
1571  auto conditionMet = true;
1572  if (script.name.empty() == false && script.name == condition) {
1573  break;
1574  } else
1575  if (script.condition == condition) {
1576  break;
1577  } else {
1578  scriptIdxToStart++;
1579  }
1580  }
1581  if (scriptIdxToStart == scripts.size()) {
1582  scriptIdxToStart = SCRIPTIDX_NONE;
1583  startErrorScript();
1584  return;
1585  }
1586  //
1587  getScriptState().running = true;
1588  resetScriptExecutationState(scriptIdxToStart, STATEMACHINESTATE_NEXT_STATEMENT);
1589  //
1590  emitted = true;
1591 }
1592 
1593 void MinitScript::executeStateMachine() {
1594  while (true == true) {
1595  {
1596  auto& scriptState = getScriptState();
1597  // determine state machine state if it did change
1598  {
1599  if (scriptState.lastStateMachineState == nullptr || scriptState.state != scriptState.lastState) {
1600  scriptState.lastState = scriptState.state;
1601  scriptState.lastStateMachineState = nullptr;
1602  auto stateMachineStateIt = stateMachineStates.find(scriptState.state);
1603  if (stateMachineStateIt != stateMachineStates.end()) {
1604  scriptState.lastStateMachineState = stateMachineStateIt->second;
1605  }
1606  }
1607  }
1608 
1609  // execute state machine
1610  if (scriptState.lastStateMachineState != nullptr) {
1611  if (native == true && scriptState.state == STATEMACHINESTATE_NEXT_STATEMENT) {
1612  // ignore STATEMACHINESTATE_NEXT_STATEMENT on native
1613  } else {
1614  scriptState.lastStateMachineState->execute();
1615  }
1616  } else {
1617  // we can ignore this here and break as our state machine is unset
1618  break;
1619  }
1620  }
1621 
1622  // native
1623  // also do not run enabled conditions when beeing in (user script) function
1624  if (native == true && isFunctionRunning() == false) {
1625  const auto& scriptState = getScriptState();
1626  // check named conditions
1627  auto now = _Time::getCurrentMillis();
1628  if (enabledNamedConditions.empty() == false &&
1629  (timeEnabledConditionsCheckLast == TIME_NONE || now >= timeEnabledConditionsCheckLast + 100LL)) {
1630  auto scriptIdxToStart = determineNamedScriptIdxToStart();
1631  if (scriptIdxToStart != SCRIPTIDX_NONE && scriptIdxToStart != scriptState.scriptIdx) {
1632  //
1633  resetScriptExecutationState(scriptIdxToStart, STATEMACHINESTATE_NEXT_STATEMENT);
1634  }
1635  timeEnabledConditionsCheckLast = now;
1636  }
1637  // stop here
1638  break;
1639  } else {
1640  // break if no next statement but other state machine state or not running
1641  const auto& scriptState = getScriptState();
1642  if (scriptState.state != STATEMACHINESTATE_NEXT_STATEMENT || scriptState.running == false) break;
1643  }
1644  }
1645 }
1646 
1647 void MinitScript::execute() {
1648  const auto& scriptState = getScriptState();
1649 
1650  //
1651  if (scriptState.running == false || scriptState.state == STATEMACHINESTATE_NONE) return;
1652 
1653  // check named conditions
1654  auto now = _Time::getCurrentMillis();
1655  if (isFunctionRunning() == false &&
1656  enabledNamedConditions.empty() == false &&
1657  (timeEnabledConditionsCheckLast == TIME_NONE || now >= timeEnabledConditionsCheckLast + 100LL)) {
1658  auto scriptIdxToStart = determineNamedScriptIdxToStart();
1659  if (scriptIdxToStart != SCRIPTIDX_NONE && scriptIdxToStart != scriptState.scriptIdx) {
1660  //
1661  resetScriptExecutationState(scriptIdxToStart, STATEMACHINESTATE_NEXT_STATEMENT);
1662  }
1663  timeEnabledConditionsCheckLast = now;
1664  }
1665 
1666  // execute while having statements to be processed
1667  executeStateMachine();
1668 
1669  // try garbage collection
1670  tryGarbageCollection();
1671 }
1672 
1673 bool MinitScript::getNextStatement(const string& scriptCode, int& i, int& line, string& statement) {
1674  string statementCode;
1675  vector<string> statementCodeLines;
1676  statementCodeLines.emplace_back();
1677  auto quote = '\0';
1678  auto expectBracket = false;
1679  auto canExpectStacklet = false;
1680  auto inlineFunctionArguments = false;
1681  auto bracketCount = 0;
1682  auto squareBracketCount = 0;
1683  auto curlyBracketCount = 0;
1684  auto hash = false;
1685  auto expectRightArgument = false;
1686  auto lc = '\0';
1687  auto llc = '\0';
1688  for (; i < scriptCode.size(); i++) {
1689  auto c = scriptCode[i];
1690  auto nc = i + 1 < scriptCode.size()?scriptCode[i + 1]:'\0';
1691  // this is some sort of hack, but it works, we need a more sophisticated parser later
1692  if (c != '-' && c != '>' &&
1693  c != ' ' && c != '\t' && c != '\n' && c != '\r') canExpectStacklet = c == ',' || c == '(';
1694  // handle quotes
1695  if (hash == true && c != '\n') {
1696  // no op
1697  } else
1698  if ((c == '"' || c == '\'') && lc != '\\') {
1699  if (quote == '\0') {
1700  quote = c;
1701  } else
1702  if (quote == c) {
1703  expectRightArgument = false;
1704  quote = '\0';
1705  }
1706  // add char to script line
1707  statementCodeLines.back() += c;
1708  //
1709  inlineFunctionArguments = false;
1710  } else
1711  if (quote != '\0') {
1712  // no op
1713  if (c == '\n') {
1714  _Console::printLine(scriptFileName + ":" + to_string(line) + ": Newline within string literal is not allowed");
1715  parseErrors.push_back(scriptFileName + ":" + to_string(line) + ": Newline within string literal is not allowed");
1716  //
1717  return false;
1718  } else {
1719  statementCodeLines.back() += c;
1720  }
1721  } else
1722  // brackets
1723  if (c == '(') {
1724  inlineFunctionArguments = false;
1725  expectRightArgument = false;
1726  //
1727  bracketCount++;
1728  expectBracket = false;
1729  // add char to script line
1730  statementCodeLines.back() += c;
1731  } else
1732  if (c == ')') {
1733  //
1734  inlineFunctionArguments = false;
1735  expectRightArgument = false;
1736  //
1737  bracketCount--;
1738  // add char to script line
1739  statementCodeLines.back() += c;
1740  //
1741  inlineFunctionArguments = true;
1742  } else
1743  // square brackets
1744  if (c == '[') {
1745  //
1746  inlineFunctionArguments = false;
1747  expectRightArgument = false;
1748  //
1749  squareBracketCount++;
1750  // add char to script line
1751  statementCodeLines.back() += c;
1752  } else
1753  if (c == ']') {
1754  //
1755  inlineFunctionArguments = false;
1756  expectRightArgument = false;
1757  //
1758  squareBracketCount--;
1759  // add char to script line
1760  statementCodeLines.back() += c;
1761  } else
1762  // curly brackets
1763  if (c == '{') {
1764  //
1765  inlineFunctionArguments = false;
1766  expectRightArgument = false;
1767  //
1768  curlyBracketCount++;
1769  // add char to script line
1770  statementCodeLines.back() += c;
1771  } else
1772  if (c == '}') {
1773  //
1774  inlineFunctionArguments = false;
1775  expectRightArgument = false;
1776  //
1777  curlyBracketCount--;
1778  // add char to script line
1779  statementCodeLines.back() += c;
1780  } else
1781  // hash
1782  if (c == '#' && curlyBracketCount == 0 && squareBracketCount == 0) {
1783  // hash
1784  hash = true;
1785  } else
1786  // new line
1787  if (c == '\r') {
1788  // ignore
1789  } else
1790  // assigment operator
1791  if (// TODO: implement those prefix/postfix operators properly, for now they get ignored
1792  //(lc == '+' && c == '+') ||
1793  //(lc == '-' && c == '-') ||
1794  //(lc == '+' && c == '+') ||
1795  //(lc == '-' && c == '-') ||
1796  (isOperatorChar(lc) == false && c == '!' && isOperatorChar(nc) == false) ||
1797  (isOperatorChar(lc) == false && c == '~' && isOperatorChar(nc) == false) ||
1798  (isOperatorChar(lc) == false && c == '*' && isOperatorChar(nc) == false) ||
1799  (isOperatorChar(lc) == false && c == '/' && isOperatorChar(nc) == false) ||
1800  (isOperatorChar(lc) == false && c == '%' && isOperatorChar(nc) == false) ||
1801  (isOperatorChar(lc) == false && c == '+' && isOperatorChar(nc) == false) ||
1802  (isOperatorChar(lc) == false && c == '-' && isOperatorChar(nc) == false) ||
1803  (isOperatorChar(lc) == false && c == '<' && isOperatorChar(nc) == false) ||
1804  (isOperatorChar(lc) == false && c == '>' && isOperatorChar(nc) == false) ||
1805  (isOperatorChar(lc) == false && c == '&' && isOperatorChar(nc) == false) ||
1806  (isOperatorChar(lc) == false && c == '^' && isOperatorChar(nc) == false) ||
1807  (isOperatorChar(lc) == false && c == '|' && isOperatorChar(nc) == false) ||
1808  (isOperatorChar(lc) == false && c == '=' && isOperatorChar(nc) == false) ||
1809  (isOperatorChar(llc) == false && lc == '<' && c == '=' && isOperatorChar(nc) == false) ||
1810  (isOperatorChar(llc) == false && lc == '>' && c == '=' && isOperatorChar(nc) == false) ||
1811  (isOperatorChar(llc) == false && lc == '=' && c == '=' && isOperatorChar(nc) == false) ||
1812  (isOperatorChar(llc) == false && lc == '!' && c == '=' && isOperatorChar(nc) == false) ||
1813  (isOperatorChar(llc) == false && lc == '&' && c == '&' && isOperatorChar(nc) == false) ||
1814  (isOperatorChar(llc) == false && lc == '|' && c == '|' && isOperatorChar(nc) == false)
1815  ) {
1816  if (expectBracket == false && expectRightArgument == false && bracketCount == 0 && squareBracketCount == 0 && curlyBracketCount == 0) {
1817  expectRightArgument = true;
1818  }
1819  // add char to script line
1820  statementCodeLines.back() += c;
1821  } else
1822  if (lc == '-' && c == '>') {
1823  // we expect a bracket now for object->xyz() member method call, if we have a possible identifier
1824  if (inlineFunctionArguments == false && canExpectStacklet == false) expectBracket = true;
1825  //
1826  expectRightArgument = false;
1827  // add char to script line
1828  statementCodeLines.back() += c;
1829  } else
1830  if ((c == '\n' && ++line) || (hash == false && c == ';')) {
1831  // break here and process script line
1832  if (expectBracket == false && expectRightArgument == false && bracketCount == 0 && squareBracketCount == 0 && curlyBracketCount == 0) break;
1833  // unset hash after newline
1834  if (c == '\n') {
1835  //
1836  hash = false;
1837  //
1838  statementCodeLines.emplace_back();
1839  } else {
1840  statementCodeLines.back() += c;
1841  //
1842  inlineFunctionArguments = false;
1843  }
1844  } else {
1845  //
1846  if (_Character::isSpace(c) == false && c != '-' && nc != '>') inlineFunctionArguments = false;
1847  // add char to script line
1848  statementCodeLines.back() += c;
1849  //
1850  if (_Character::isSpace(c) == false) expectRightArgument = false;
1851  }
1852  //
1853  lc = lc == '\\' && c == '\\'?'\0':c;
1854  llc = lc;
1855  }
1856 
1857  //
1858  auto lineIdx = 0;
1859  for (const auto& line: statementCodeLines) {
1860  auto trimmedLine = _StringTools::trim(line);
1861  statementCode+= trimmedLine;
1862  if (statementCode.empty() == false && lineIdx != statementCodeLines.size() - 1) statementCode+= "\n";
1863  lineIdx++;
1864  }
1865 
1866  // add last line index
1867  if (i == scriptCode.size() && scriptCode.back() != '\n') ++line;
1868 
1869  // done
1870  statement = statementCode;
1871 
1872  //
1873  return true;
1874 }
1875 
1876 bool MinitScript::parseScriptInternal(const string& scriptCode, int lineIdxOffset) {
1877  //
1878  auto scriptCount = scripts.size();
1879  auto haveScript = false;
1880  auto lineIdx = LINE_FIRST;
1881  auto currentLineIdx = LINE_NONE;
1882  auto statementIdx = STATEMENTIDX_FIRST;
1883  struct Block {
1884  enum Type { TYPE_FOR, TYPE_FOREACH, TYPE_IF, TYPE_ELSE, TYPE_ELSEIF, TYPE_SWITCH, TYPE_CASE, TYPE_DEFAULT };
1885  Block(Type type, int statementIdx): type(type), statementIdx(statementIdx) {}
1886  Type type;
1887  int statementIdx;
1888  };
1889  vector<Block> blockStack;
1890  //
1891  for (auto i = 0; i < scriptCode.size(); i++) {
1892  //
1893  currentLineIdx = lineIdx;
1894 
1895  // try to get next statement code
1896  string statementCode;
1897  if (getNextStatement(scriptCode, i, lineIdx, statementCode) == false) {
1898  //
1899  scriptValid = false;
1900  return false;
1901  }
1902 
1903  // add last line index
1904  if (i == scriptCode.size() && scriptCode.back() != '\n') ++lineIdx;
1905  //
1906  if (statementCode.empty() == true) continue;
1907 
1908  // no script yet
1909  if (haveScript == false) {
1910  // check if we have to read additional info from code
1911  if (statementCode == "function:" ||
1912  statementCode == "stacklet:" ||
1913  statementCode == "on:" ||
1914  statementCode == "on-enabled:" ||
1915  statementCode == "callable:") {
1916  //
1917  i++;
1918  // we need the condition or name
1919  for (; i < scriptCode.size(); i++) {
1920  string nextStatementCode;
1921  if (getNextStatement(scriptCode, i, lineIdx, nextStatementCode) == false) {
1922  //
1923  scriptValid = false;
1924  return false;
1925  }
1926  //
1927  if (nextStatementCode.empty() == false) {
1928  statementCode+= " " + nextStatementCode;
1929  break;
1930  }
1931  }
1932  }
1933  // check if we need to parse ":= name"
1934  // applies to on: and on-enabled only
1935  if (_StringTools::startsWith(statementCode, "on:") == true ||
1936  _StringTools::startsWith(statementCode, "on-enabled:") == true) {
1937  //
1938  if (statementCode.rfind(":=") == string::npos) {
1939  //
1940  auto gotName = false;
1941  //
1942  auto _i = i;
1943  auto _lineIdx = lineIdx;
1944  auto _statementCode = statementCode;
1945  //
1946  auto endStack = 0;
1947  //
1948  i++;
1949  //
1950  for (; i < scriptCode.size(); i++) {
1951  string nextStatementCode;
1952  if (getNextStatement(scriptCode, i, lineIdx, nextStatementCode) == false) {
1953  //
1954  scriptValid = false;
1955  return false;
1956  }
1957  //
1958  if (nextStatementCode.empty() == false) {
1959  //
1960  if (_StringTools::startsWith(nextStatementCode, "function:") == true ||
1961  _StringTools::startsWith(nextStatementCode, "on:") == true ||
1962  _StringTools::startsWith(nextStatementCode, "on-enabled:") == true ||
1963  _StringTools::startsWith(nextStatementCode, "callable:") == true) break;
1964  //
1965  statementCode+= " " + nextStatementCode;
1966  // break here if we got our := or reached next declaration
1967  auto lc = '\0';
1968  auto quote = '\0';
1969  for (auto j = 0; j < statementCode.size(); j++) {
1970  auto c = statementCode[j];
1971  // handle quotes
1972  if ((c == '"' || c == '\'') && lc != '\\') {
1973  if (quote == '\0') {
1974  quote = c;
1975  } else
1976  if (quote == c) {
1977  quote = '\0';
1978  }
1979  } else
1980  if (quote != '\0') {
1981  // no op
1982  } else
1983  if (lc == ':' && c == '=') {
1984  gotName = true;
1985  //
1986  break;
1987  }
1988  //
1989  lc = lc == '\\' && c == '\\'?'\0':c;
1990  }
1991  //
1992  if (gotName == true) break;
1993  }
1994  }
1995  // did we got our ":= name", nope?
1996  if (gotName == false) {
1997  // reset
1998  i = _i;
1999  lineIdx = _lineIdx;
2000  statementCode = _statementCode;
2001  }
2002  }
2003  // we still need the name
2004  if (_StringTools::endsWith(statementCode, ":=") == true) {
2005  //
2006  i++;
2007  //
2008  for (; i < scriptCode.size(); i++) {
2009  string nextStatementCode;
2010  if (getNextStatement(scriptCode, i, lineIdx, nextStatementCode) == false) {
2011  //
2012  scriptValid = false;
2013  return false;
2014  }
2015  if (nextStatementCode.empty() == false) {
2016  statementCode+= " " + nextStatementCode;
2017  break;
2018  }
2019  }
2020  }
2021  }
2022  // script type
2023  auto callable = false;
2024  auto scriptType = Script::TYPE_NONE;
2025  if (_StringTools::startsWith(statementCode, "function:") == true) scriptType = Script::TYPE_FUNCTION; else
2026  if (_StringTools::startsWith(statementCode, "stacklet:") == true) scriptType = Script::TYPE_STACKLET; else
2027  if (_StringTools::startsWith(statementCode, "on:") == true) scriptType = Script::TYPE_ON; else
2028  if (_StringTools::startsWith(statementCode, "on-enabled:") == true) scriptType = Script::TYPE_ONENABLED; else
2029  if (_StringTools::startsWith(statementCode, "callable:") == true) {
2030  callable = true;
2031  scriptType = Script::TYPE_FUNCTION;
2032  }
2033  // no, but did we got a new script?
2034  if (scriptType != Script::TYPE_NONE) {
2035  // yes
2036  haveScript = true;
2037  // functions: argument names
2038  vector<Script::Argument> arguments;
2039  // determine statement
2040  string statement;
2041  if (scriptType == Script::TYPE_FUNCTION) {
2042  statement = callable == true?
2043  _StringTools::trim(_StringTools::substring(statementCode, string("callable:").size())):
2044  _StringTools::trim(_StringTools::substring(statementCode, string("function:").size()));
2045  } else
2046  if (scriptType == Script::TYPE_STACKLET) {
2047  statement = _StringTools::trim(_StringTools::substring(statementCode, string("stacklet:").size()));
2048  } else
2049  if (scriptType == Script::TYPE_ON)
2050  statement = _StringTools::trim(_StringTools::substring(statementCode, string("on:").size())); else
2051  if (scriptType == Script::TYPE_ONENABLED)
2052  statement = _StringTools::trim(_StringTools::substring(statementCode, string("on-enabled:").size()));
2053  // and name
2054  string name;
2055  auto scriptLineNameSeparatorIdx =
2056  scriptType == Script::TYPE_FUNCTION?
2057  statement.rfind("function:"):
2058  (scriptType == Script::TYPE_STACKLET?
2059  statement.rfind("stacklet:"):
2060  statement.rfind(":=")
2061  );
2062  if (scriptLineNameSeparatorIdx != string::npos) {
2063  name =
2066  statement,
2067  scriptLineNameSeparatorIdx +
2068  (scriptType == Script::TYPE_FUNCTION?string("function").size():(scriptType == Script::TYPE_STACKLET?string("stacklet").size():string(":=").size()))
2069  )
2070  );
2071  statement = _StringTools::trim(_StringTools::substring(statement, 0, scriptLineNameSeparatorIdx));
2072  }
2073  if (scriptType == Script::TYPE_FUNCTION ||
2074  scriptType == Script::TYPE_STACKLET) {
2075  auto leftBracketIdx = statement.find('(');
2076  auto rightBracketIdx = statement.find(')');
2077  if (leftBracketIdx != string::npos || leftBracketIdx != string::npos) {
2078  if (leftBracketIdx == string::npos) {
2079  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": " + (scriptType == Script::TYPE_FUNCTION?"function":"stacklet") + ": Unbalanced bracket count");
2080  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": " + (scriptType == Script::TYPE_FUNCTION?"function":"stacklet") + ": Unbalanced bracket count");
2081  scriptValid = false;
2082  return false;
2083  } else
2084  if (rightBracketIdx == string::npos) {
2085  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": " + (scriptType == Script::TYPE_FUNCTION?"function":"stacklet") + ": Unbalanced bracket count");
2086  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": " + (scriptType == Script::TYPE_FUNCTION?"function":"stacklet") + ": Unbalanced bracket count");
2087  scriptValid = false;
2088  return false;
2089  } else {
2090  auto argumentNamesString = _StringTools::trim(_StringTools::substring(statement, leftBracketIdx + 1, rightBracketIdx));
2091  auto argumentNamesTokenized = _StringTools::tokenize(argumentNamesString, ",");
2092  statement = _StringTools::substring(statement, 0, leftBracketIdx);
2093  for (const auto& argumentName: argumentNamesTokenized) {
2094  auto argumentNameTrimmed = _StringTools::trim(argumentName);
2095  auto reference = false;
2096  auto privateScope = false;
2097  if (_StringTools::startsWith(argumentNameTrimmed, "&&") == true) {
2098  reference = true;
2099  privateScope = true;
2100  argumentNameTrimmed = _StringTools::trim(_StringTools::substring(argumentNameTrimmed, 2));
2101  } else
2102  if (_StringTools::startsWith(argumentNameTrimmed, "&") == true) {
2103  reference = true;
2104  argumentNameTrimmed = _StringTools::trim(_StringTools::substring(argumentNameTrimmed, 1));
2105  }
2106  if (scriptType == Script::TYPE_FUNCTION) {
2107  if (_StringTools::regexMatch(argumentNameTrimmed, "\\$[a-zA-Z0-9_]+") == true) {
2108  arguments.emplace_back(
2109  argumentNameTrimmed,
2110  reference,
2111  privateScope
2112  );
2113  } else {
2114  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": " + (scriptType == Script::TYPE_FUNCTION?"function":"stacklet") + ": Invalid argument name: '" + argumentNameTrimmed + "'");
2115  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": " + (scriptType == Script::TYPE_FUNCTION?"function":"stacklet") + ": Invalid argument name: '" + argumentNameTrimmed + "'");
2116  scriptValid = false;
2117  return false;
2118 
2119  }
2120  } else
2121  if (scriptType == Script::TYPE_STACKLET) {
2122  if (_StringTools::regexMatch(argumentNameTrimmed, "[a-zA-Z0-9_]+") == true) {
2123  arguments.emplace_back(
2124  argumentNameTrimmed,
2125  reference,
2126  false
2127  );
2128  } else {
2129  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": " + (scriptType == Script::TYPE_FUNCTION?"function":"stacklet") + ": Invalid stacklet parent stacklet/function: '" + argumentNameTrimmed + "'");
2130  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": " + (scriptType == Script::TYPE_FUNCTION?"function":"stacklet") + ": Invalid stacklet parent stacklet/function: '" + argumentNameTrimmed + "'");
2131  scriptValid = false;
2132  return false;
2133  }
2134  }
2135  }
2136  }
2137 
2138  }
2139  }
2140  auto trimmedStatement = _StringTools::trim(statement);
2141  Statement evaluateStatement(
2142  currentLineIdx + lineIdxOffset,
2143  STATEMENTIDX_FIRST,
2144  "internal.script.evaluate(" + _StringTools::replace(_StringTools::replace(trimmedStatement, "\\", "\\\\"), "\"", "\\\"") + ")",
2145  "internal.script.evaluate(" + _StringTools::replace(_StringTools::replace(trimmedStatement, "\\", "\\\\"), "\"", "\\\"") + ")",
2146  STATEMENTIDX_NONE
2147  );
2148  auto conditionOrNameExecutable = doStatementPreProcessing(trimmedStatement, evaluateStatement);
2149  auto conditionOrName = trimmedStatement;
2150  auto emitCondition = _StringTools::regexMatch(conditionOrName, "[a-zA-Z0-9_]+");
2151  statementIdx = STATEMENTIDX_FIRST;
2152  // add to user functions
2153  if (scriptType == Script::TYPE_FUNCTION || scriptType == Script::TYPE_STACKLET) {
2154  functions[conditionOrName] = scripts.size();
2155  }
2156 
2157  // push to scripts
2158  scripts.emplace_back(
2159  scriptType,
2160  currentLineIdx + lineIdxOffset,
2161  conditionOrName,
2162  conditionOrNameExecutable,
2163  Statement(currentLineIdx + lineIdxOffset, statementIdx, conditionOrName, conditionOrNameExecutable, STATEMENTIDX_NONE),
2164  SyntaxTreeNode(),
2165  name,
2166  emitCondition,
2167  initializer_list<Statement>{},
2168  initializer_list<SyntaxTreeNode>{},
2169  callable,
2170  arguments
2171  );
2172  } else {
2173  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": expecting 'on:', 'on-enabled:', 'stacklet:', 'function:', 'callable:'");
2174  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": expecting 'on:', 'on-enabled:', 'stacklet:', 'function:', 'callable:'");
2175  scriptValid = false;
2176  return false;
2177  }
2178  } else {
2179  //
2180  if (_StringTools::startsWith(statementCode, "function:") == true ||
2181  _StringTools::startsWith(statementCode, "stacklet:") == true ||
2182  _StringTools::startsWith(statementCode, "on:") == true ||
2183  _StringTools::startsWith(statementCode, "on-enabled:") == true ||
2184  _StringTools::startsWith(statementCode, "callable:") == true
2185  ) {
2186  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": Unbalanced if/elseif/else/switch/case/default/forCondition/forTime/end");
2187  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": Unbalanced if/elseif/else/switch/case/default/forCondition/forTime/end");
2188  scriptValid = false;
2189  return false;
2190  } else {
2191  //
2192  auto regexStatementCode = _StringTools::replace(statementCode, "\n", " ");
2193  //
2194  if (statementCode == "end") {
2195  if (blockStack.empty() == false) {
2196  auto block = blockStack.back();
2197  blockStack.erase(blockStack.begin() + blockStack.size() - 1);
2198  switch(block.type) {
2199  case Block::TYPE_FOR:
2200  case Block::TYPE_FOREACH:
2201  {
2202  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, block.statementIdx);
2203  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size();
2204  }
2205  break;
2206  case Block::TYPE_IF:
2207  {
2208  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size();
2209  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, STATEMENTIDX_NONE);
2210  }
2211  break;
2212  case Block::TYPE_ELSE:
2213  {
2214  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size();
2215  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, STATEMENTIDX_NONE);
2216  }
2217  break;
2218  case Block::TYPE_ELSEIF:
2219  {
2220  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size();
2221  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, STATEMENTIDX_NONE);
2222  }
2223  break;
2224  case Block::TYPE_SWITCH:
2225  {
2226  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, STATEMENTIDX_NONE);
2227  }
2228  break;
2229  case Block::TYPE_CASE:
2230  {
2231  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size() + 1;
2232  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, STATEMENTIDX_NONE);
2233  }
2234  break;
2235  case Block::TYPE_DEFAULT:
2236  {
2237  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size() + 1;
2238  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, STATEMENTIDX_NONE);
2239  }
2240  break;
2241  }
2242  } else{
2243  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, STATEMENTIDX_NONE);
2244  haveScript = false;
2245  }
2246  } else
2247  if (statementCode == "else") {
2248  if (blockStack.empty() == false) {
2249  auto block = blockStack.back();
2250  blockStack.erase(blockStack.begin() + blockStack.size() - 1);
2251  switch(block.type) {
2252  case Block::TYPE_IF:
2253  {
2254  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size();
2255  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, STATEMENTIDX_NONE);
2256  }
2257  break;
2258  case Block::TYPE_ELSEIF:
2259  {
2260  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size();
2261  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, statementCode, STATEMENTIDX_NONE);
2262  }
2263  break;
2264  default:
2265  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": else without if/elseif");
2266  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": else without if/elseif");
2267  scriptValid = false;
2268  return false;
2269  }
2270  blockStack.emplace_back(
2271  Block::TYPE_ELSE,
2272  statementIdx
2273  );
2274  } else {
2275  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": else without if");
2276  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": else without if");
2277  scriptValid = false;
2278  return false;
2279  }
2280  } else
2281  if (_StringTools::regexMatch(regexStatementCode, "^elseif[\\s]*\\(.*\\)$") == true) {
2282  Statement elseIfStatement(
2283  currentLineIdx + lineIdxOffset,
2284  STATEMENTIDX_FIRST,
2285  _StringTools::replace(_StringTools::replace(statementCode, "\\", "\\\\"), "\"", "\\\""),
2286  _StringTools::replace(_StringTools::replace(statementCode, "\\", "\\\\"), "\"", "\\\""),
2287  STATEMENTIDX_NONE
2288  );
2289  auto executableStatement = doStatementPreProcessing(statementCode, elseIfStatement);
2290  if (blockStack.empty() == false) {
2291  auto block = blockStack.back();
2292  blockStack.erase(blockStack.begin() + blockStack.size() - 1);
2293  switch(block.type) {
2294  case Block::TYPE_IF:
2295  {
2296  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size();
2297  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, executableStatement, STATEMENTIDX_NONE);
2298  }
2299  break;
2300  case Block::TYPE_ELSEIF:
2301  {
2302  scripts.back().statements[block.statementIdx].gotoStatementIdx = scripts.back().statements.size();
2303  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx, statementCode, executableStatement, STATEMENTIDX_NONE);
2304  }
2305  break;
2306  default:
2307  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": elseif without if");
2308  parseErrors.push_back(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": elseif without if");
2309  scriptValid = false;
2310  return false;
2311  }
2312  blockStack.emplace_back(
2313  Block::TYPE_ELSEIF,
2314  statementIdx
2315  );
2316  } else {
2317  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": elseif without if");
2318  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": elseif without if");
2319  scriptValid = false;
2320  return false;
2321  }
2322  } else {
2323  smatch matches;
2324  Statement generatedStatement(
2325  currentLineIdx + lineIdxOffset,
2326  STATEMENTIDX_FIRST,
2327  statementCode,
2328  statementCode,
2329  STATEMENTIDX_NONE
2330  );
2331  if (_StringTools::regexMatch(regexStatementCode, "^for[\\s]*\\(.*\\)$") == true) {
2332  // parse for statement
2333  string_view forMethodName;
2334  vector<ParserArgument> forArguments;
2335  string accessObjectMemberStatement;
2336  string executableStatement = doStatementPreProcessing(statementCode, generatedStatement);
2337  // success?
2338  if (parseStatement(executableStatement, forMethodName, forArguments, generatedStatement, accessObjectMemberStatement) == true &&
2339  forArguments.size() == 3) {
2340  // create initialize statement
2341  string initializeStatement = string(forArguments[0].argument);
2342  scripts.back().statements.emplace_back(currentLineIdx + lineIdxOffset, statementIdx++, statementCode, initializeStatement, STATEMENTIDX_NONE);
2343  //
2344  blockStack.emplace_back(
2345  Block::TYPE_FOR,
2346  statementIdx
2347  );
2348  //
2349  statementCode = "forCondition(" + string(forArguments[1].argument) + ", -> { " + string(forArguments[2].argument) + " })";
2350  } else {
2351  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": Invalid for statement");
2352  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": Invalid for statement");
2353  scriptValid = false;
2354  return false;
2355  }
2356  } else
2357  // array/set forEach
2358  if (_StringTools::regexMatch(regexStatementCode, "^forEach[\\s]*\\([\\s]*(&?\\$[a-zA-Z0-9_]+)[\\s]*\\in[\\s]*((\\$[a-zA-Z0-9_]+)|(\\[.*\\])|(\\{.*\\}))[\\s]*\\)$", &matches) == true) {
2359  auto iterationDepth = 0;
2360  for (const auto& block: blockStack) {
2361  if (block.type == Block::TYPE_FOREACH) iterationDepth++;
2362  }
2363  //
2364  auto entryReference = false;
2365  auto entryVariable = matches[1].str();
2366  if (_StringTools::startsWith(entryVariable, "&") == true) {
2367  entryReference = true;
2368  entryVariable = _StringTools::substring(entryVariable, 1);
2369  }
2370  auto containerByInitializer = false;
2371  auto containerVariable = matches[2].str();
2372  string containerInitializer;
2373  if (_StringTools::startsWith(containerVariable, "[") == true || _StringTools::startsWith(containerVariable, "{") == true) {
2374  containerByInitializer = true;
2375  containerInitializer = containerVariable;
2376  containerVariable = string("$___cv_" + to_string(iterationDepth));
2377  }
2378  auto initializationStackletVariable = string("$___is_" + to_string(iterationDepth));
2379  auto containerVariableType = string("$___vt_" + to_string(iterationDepth));
2380  auto iterationVariable = string("$___it_" + to_string(iterationDepth));
2381  auto entryVariableBackup = string("$___evb_" + to_string(iterationDepth));
2382  auto containerArrayVariable = string("$___cav_" + to_string(iterationDepth));
2383  auto containerArrayVariableBackup = string("$___cavb_" + to_string(iterationDepth));
2384  string iterationUpdate =
2385  entryReference == true?
2386  "setVariableReference(\"" + entryVariable + "\", " + containerArrayVariable + "[" + iterationVariable + "])":
2387  entryVariable + " = " + containerArrayVariable + "[" + iterationVariable + "]";
2388  //
2389  string initialization =
2390  initializationStackletVariable + " = -> { " +
2391  "if (script.isNative() == true); " +
2392  "setVariableReference(\"" + containerArrayVariableBackup + "\", " + containerArrayVariable + "); " +
2393  "setVariableReference(\"" + entryVariableBackup + "\", " + entryVariable + "); " +
2394  "end; " +
2395  containerVariableType + " = getType(" + containerVariable + "); " +
2396  "if (" + containerVariableType + " == \"Array\"); " +
2397  "setVariableReference(\"" + containerArrayVariable + "\", " + containerVariable + "); " +
2398  "elseif (" + containerVariableType + " == \"Set\"); " +
2399  containerArrayVariable + " = Set::getKeys(" + containerVariable + "); " +
2400  "else; " +
2401  "console.printLine(\"forEach() expects array or set as container, but got \" + String::toLowerCase(getType(" + containerVariable + "))); " +
2402  "script.emit(\"error\"); " +
2403  "end; " +
2404  iterationVariable + " = 0; " +
2405  "if (" + iterationVariable + " < Array::getSize(" + containerArrayVariable + ")); " +
2406  iterationUpdate + "; " +
2407  "end;" +
2408  "}";
2409  //
2410  if (containerByInitializer == true) {
2411  scripts.back().statements.emplace_back(
2412  currentLineIdx + lineIdxOffset,
2413  statementIdx++,
2414  statementCode,
2415  doStatementPreProcessing(containerVariable + " = " + containerInitializer, generatedStatement),
2416  STATEMENTIDX_NONE
2417  );
2418  }
2419  scripts.back().statements.emplace_back(
2420  currentLineIdx + lineIdxOffset,
2421  statementIdx++,
2422  statementCode,
2423  doStatementPreProcessing(initialization, generatedStatement),
2424  STATEMENTIDX_NONE
2425  );
2426  scripts.back().statements.emplace_back(
2427  currentLineIdx + lineIdxOffset,
2428  statementIdx++,
2429  statementCode,
2430  "internal.script.callStacklet(" + initializationStackletVariable + ")",
2431  STATEMENTIDX_NONE
2432  );
2433  blockStack.emplace_back(
2434  Block::TYPE_FOREACH,
2435  statementIdx
2436  );
2437  //
2438  statementCode =
2439  "forCondition(" + iterationVariable + " < Array::getSize(" + containerArrayVariable + "), " +
2440  "-> { " +
2441  iterationVariable + "++" + "; " +
2442  "if (" + iterationVariable + " < Array::getSize(" + containerArrayVariable + ")); " +
2443  iterationUpdate + "; " +
2444  "else; " +
2445  "if (script.isNative() == true); " +
2446  "setVariableReference(\"" + containerArrayVariable + "\", " + containerArrayVariableBackup + "); " +
2447  "setVariableReference(\"" + entryVariable + "\", " + entryVariableBackup + "); " +
2448  "else; " +
2449  "unsetVariableReference(\"" + containerArrayVariable + "\"); " +
2450  "unsetVariableReference(\"" + entryVariable + "\"); " +
2451  "end; " +
2452  "setVariable(\"" + containerArrayVariable + "\", $$.___ARRAY); " +
2453  "setVariable(\"" + entryVariable + "\", $$.___NULL); " +
2454  (containerByInitializer == true?"setVariable(\"" + containerVariable + "\", $$.___ARRAY); ":"") +
2455  "end; " +
2456  "})";
2457  } else
2458  // map forEach
2459  if (_StringTools::regexMatch(regexStatementCode, "^forEach[\\s]*\\([\\s]*(\\$[a-zA-Z0-9_]+)[\\s]*,[\\s]*(&?\\$[a-zA-Z0-9_]+)[\\s]*\\in[\\s]*((\\$[a-zA-Z0-9_]+)|(\\[.*\\])|(\\{.*\\}))[\\s]*\\)$", &matches) == true) {
2460  auto iterationDepth = 0;
2461  for (const auto& block: blockStack) {
2462  if (block.type == Block::TYPE_FOREACH) iterationDepth++;
2463  }
2464  //
2465  auto entryKeyVariable = matches[1].str();
2466  auto entryValueReference = false;
2467  auto entryValueVariable = matches[2].str();
2468  if (_StringTools::startsWith(entryValueVariable, "&") == true) {
2469  entryValueReference = true;
2470  entryValueVariable = _StringTools::substring(entryValueVariable, 1);
2471  }
2472  auto containerByInitializer = false;
2473  auto containerVariable = matches[3].str();
2474  string containerInitializer;
2475  if (_StringTools::startsWith(containerVariable, "[") == true || _StringTools::startsWith(containerVariable, "{") == true) {
2476  containerByInitializer = true;
2477  containerInitializer = containerVariable;
2478  containerVariable = string("$___cv_" + to_string(iterationDepth));
2479  }
2480  auto initializationStackletVariable = string("$___is_" + to_string(iterationDepth));
2481  auto containerVariableType = string("$___vt_" + to_string(iterationDepth));
2482  auto iterationVariable = string("$___it_" + to_string(iterationDepth));
2483  auto entryValueVariableBackup = string("$___evb_" + to_string(iterationDepth));
2484  auto containerArrayVariable = string("$___cav_" + to_string(iterationDepth));
2485  auto containerArrayVariableBackup = string("$___cavb_" + to_string(iterationDepth));
2486  string iterationUpdate =
2487  entryKeyVariable + " = " + containerArrayVariable + "[" + iterationVariable + "]; " +
2488  (entryValueReference == true?
2489  "setVariableReference(\"" + entryValueVariable + "\", Map::getReference(" + containerVariable + ", " + entryKeyVariable + "))":
2490  entryValueVariable + " = Map::get(" + containerVariable + ", " + entryKeyVariable + ")"
2491  );
2492  //
2493  string initialization =
2494  initializationStackletVariable + " = -> { " +
2495  "if (script.isNative() == true); " +
2496  "setVariableReference(\"" + containerArrayVariable + "\", " + containerArrayVariableBackup + "); " +
2497  "setVariableReference(\"" + entryValueVariable + "\", " + entryValueVariableBackup + "); " +
2498  "end; " +
2499  containerVariableType + " = getType(" + containerVariable + "); " +
2500  "if (" + containerVariableType + " == \"Map\"); " +
2501  containerArrayVariable + " = Map::getKeys(" + containerVariable + "); " +
2502  "else; " +
2503  "console.printLine(\"forEach() expects map as container, but got \" + String::toLowerCase(getType(" + containerVariable + "))); " +
2504  "script.emit(\"error\"); " +
2505  "end; " +
2506  iterationVariable + " = 0; " +
2507  "if (" + iterationVariable + " < Array::getSize(" + containerArrayVariable + ")); " +
2508  iterationUpdate + "; " +
2509  "end; " +
2510  "}";
2511  //
2512  if (containerByInitializer == true) {
2513  scripts.back().statements.emplace_back(
2514  currentLineIdx + lineIdxOffset,
2515  statementIdx++,
2516  statementCode,
2517  doStatementPreProcessing(containerVariable + " = " + containerInitializer, generatedStatement),
2518  STATEMENTIDX_NONE
2519  );
2520  }
2521  scripts.back().statements.emplace_back(
2522  currentLineIdx + lineIdxOffset,
2523  statementIdx++,
2524  statementCode,
2525  doStatementPreProcessing(initialization, generatedStatement),
2526  STATEMENTIDX_NONE
2527  );
2528  scripts.back().statements.emplace_back(
2529  currentLineIdx + lineIdxOffset,
2530  statementIdx++,
2531  statementCode,
2532  "internal.script.callStacklet(" + initializationStackletVariable + ")",
2533  STATEMENTIDX_NONE
2534  );
2535  blockStack.emplace_back(
2536  Block::TYPE_FOREACH,
2537  statementIdx
2538  );
2539  //
2540  statementCode =
2541  "forCondition(" + iterationVariable + " < Array::getSize(" + containerArrayVariable + "), " +
2542  "-> { " +
2543  iterationVariable + "++" + "; " +
2544  "if (" + iterationVariable + " < Array::getSize(" + containerArrayVariable + ")); " +
2545  iterationUpdate + "; " +
2546  "else; " +
2547  "if (script.isNative() == true); " +
2548  "setVariableReference(\"" + containerArrayVariable + "\", " + containerArrayVariableBackup + "); " +
2549  "setVariableReference(\"" + entryValueVariable + "\", " + entryValueVariableBackup + "); " +
2550  "else; "
2551  "unsetVariableReference(\"" + containerArrayVariable + "\"); " +
2552  "unsetVariableReference(\"" + entryValueVariable + "\"); " +
2553  "end; " +
2554  "setVariable(\"" + containerArrayVariable + "\", $$.___ARRAY); " +
2555  "setVariable(\"" + entryKeyVariable + "\", $$.___NULL); "
2556  "setVariable(\"" + entryValueVariable + "\", $$.___NULL); " +
2557  (containerByInitializer == true?"setVariable(\"" + containerVariable + "\", $$.___ARRAY); ":"") +
2558  "end;"
2559  "})";
2560  } else
2561  if (_StringTools::regexMatch(regexStatementCode, "^forEach[\\s]*\\(.*\\)$", &matches) == true) {
2562  _Console::printLine(scriptFileName + ":" + to_string(currentLineIdx + lineIdxOffset) + ": Invalid forEach statement");
2563  parseErrors.push_back(to_string(currentLineIdx + lineIdxOffset) + ": Invalid forEach statement");
2564  scriptValid = false;
2565  return false;
2566  } else
2567  if (_StringTools::regexMatch(regexStatementCode, "^forTime[\\s]*\\(.*\\)$") == true ||
2568  _StringTools::regexMatch(regexStatementCode, "^forCondition[\\s]*\\(.*\\)$") == true) {
2569  blockStack.emplace_back(
2570  Block::TYPE_FOR,
2571  statementIdx
2572  );
2573  } else
2574  if (_StringTools::regexMatch(regexStatementCode, "^if[\\s]*\\(.*\\)$") == true) {
2575  blockStack.emplace_back(
2576  Block::TYPE_IF,
2577  statementIdx
2578  );
2579  } else
2580  if (_StringTools::regexMatch(regexStatementCode, "^switch[\\s]*\\(.*\\)$") == true) {
2581  blockStack.emplace_back(
2582  Block::TYPE_SWITCH,
2583  STATEMENTIDX_NONE
2584  );
2585  } else
2586  if (_StringTools::regexMatch(regexStatementCode, "^case[\\s]*\\(.*\\)$") == true) {
2587  blockStack.emplace_back(
2588  Block::TYPE_CASE,
2589  statementIdx
2590  );
2591  } else
2592  if (_StringTools::regexMatch(regexStatementCode, "^default[\\s]*$") == true) {
2593  blockStack.emplace_back(
2594  Block::TYPE_DEFAULT,
2595  statementIdx
2596  );
2597  }
2598  scripts.back().statements.emplace_back(
2599  currentLineIdx + lineIdxOffset,
2600  statementIdx,
2601  statementCode,
2602  doStatementPreProcessing(statementCode, generatedStatement),
2603  STATEMENTIDX_NONE
2604  );
2605  }
2606  statementIdx++;
2607  }
2608  }
2609  }
2610 
2611  // check for unbalanced if/elseif/else/switch/case/default/forCondition/forTime/end
2612  if (scriptValid == true && blockStack.empty() == false) {
2613  _Console::printLine(scriptFileName + ": Unbalanced if/elseif/else/switch/case/default/forCondition/forTime/end");
2614  parseErrors.push_back("Unbalanced if/elseif/else/switch/case/default/forCondition/forTime/end");
2615  scriptValid = false;
2616  return false;
2617  }
2618 
2619  // create syntax tree
2620  for (auto scriptIdx = scriptCount; scriptIdx < scripts.size(); scriptIdx++) {
2621  auto& script = scripts[scriptIdx];
2622  // create syntax tree of executable condition if we have any
2623  if (script.emitCondition == false && script.executableCondition.empty() == false) {
2624  string_view method;
2625  vector<ParserArgument> arguments;
2626  string accessObjectMemberStatement;
2627  if (parseStatement(script.executableCondition, method, arguments, script.conditionStatement, accessObjectMemberStatement) == false) {
2628  scriptValid = false;
2629  return false;
2630  } else
2631  if (createStatementSyntaxTree(SCRIPTIDX_NONE, method, arguments, script.conditionStatement, script.conditionSyntaxTree) == false) {
2632  scriptValid = false;
2633  return false;
2634  }
2635  }
2636  // create script syntax tree
2637  for (auto statementIdx = STATEMENTIDX_FIRST; statementIdx < script.statements.size(); statementIdx++) {
2638  const auto& statement = script.statements[statementIdx];
2639  script.syntaxTree.emplace_back();
2640  auto& syntaxTree = script.syntaxTree.back();
2641  string_view methodName;
2642  vector<ParserArgument> arguments;
2643  string accessObjectMemberStatement;
2644  if (parseStatement(statement.executableStatement, methodName, arguments, statement, accessObjectMemberStatement) == false) {
2645  scriptValid = false;
2646  return false;
2647  } else
2648  if (createStatementSyntaxTree(scriptIdx, methodName, arguments, statement, syntaxTree) == false) {
2649  scriptValid = false;
2650  return false;
2651  }
2652  }
2653  }
2654  //
2655  return scriptValid;
2656 }
2657 
2658 void MinitScript::parseScript(const string& pathName, const string& fileName, bool nativeOnly) {
2659  //
2660  scriptValid = true;
2661  scriptPathName = pathName;
2662  scriptFileName = fileName;
2663 
2664  //
2665  for (const auto& [methodName, method]: methods) delete method;
2666  for (const auto& [stateMachineStateId, stateMachineState]: stateMachineStates) delete stateMachineState;
2667  methods.clear();
2668  stateMachineStates.clear();
2669  while (scriptStateStack.empty() == false) popScriptState();
2670 
2671  // shutdown script state
2672  pushScriptState();
2673  resetScriptExecutationState(SCRIPTIDX_NONE, STATEMACHINESTATE_WAIT_FOR_CONDITION);
2674 
2675  //
2676  string scriptCode;
2677  if (native == false || nativeOnly == false) {
2678  try {
2679  scriptCode = _FileSystem::getContentAsString(scriptPathName, scriptFileName);
2680  } catch (_FileSystem::FileSystemException& fse) {
2681  _Console::printLine(scriptPathName + "/" + scriptFileName + ": An error occurred: " + fse.what());
2682  //
2683  scriptValid = true;
2684  //
2685  parseErrors.push_back("An error occurred: " + string(fse.what()));
2686  //
2687  return;
2688  }
2689  }
2690 
2691  //
2692  {
2693  auto scriptHash = _SHA256::encode(scriptCode);
2694  if (native == true) {
2695  if (nativeOnly == true || scriptHash == nativeHash) {
2696  scripts = nativeScripts;
2697  registerStateMachineStates();
2698  registerMethods();
2699  registerVariables();
2700  startScript();
2701  return;
2702  } else {
2703  _Console::printLine(scriptPathName + "/" + scriptFileName + ": Script has changed. Script will be run in interpreted mode. Retranspile and recompile your script if you want to run it natively.");
2704  native = false;
2705  }
2706  }
2707  nativeHash = scriptHash;
2708  }
2709 
2710  //
2711  registerStateMachineStates();
2712  registerMethods();
2713  registerVariables();
2714 
2715  //
2716  if (parseScriptInternal(scriptCode) == false) return;
2717 
2718  // parse deferred function script codes,
2719  // which are created by parsing map initializers and possible map inline functions
2720  do {
2721  auto deferredInlineScriptCodesCopy = deferredInlineScriptCodes;
2722  deferredInlineScriptCodes.clear();
2723  for (const auto& functionScriptCodePair: deferredInlineScriptCodesCopy) {
2724  parseScriptInternal(functionScriptCodePair.second, functionScriptCodePair.first);
2725  }
2726  } while (deferredInlineScriptCodes.empty() == false);
2727  // set up stacklet and function indices
2728  for (auto scriptIdx = 0; scriptIdx < scripts.size(); scriptIdx++) {
2729  //
2730  if (setupFunctionAndStackletScriptIndices(scriptIdx) == false) {
2731  scriptValid = false;
2732  return;
2733  }
2734  }
2735  // validations
2736  if (scriptValid == true) {
2737  for (auto scriptIdx = 0; scriptIdx < scripts.size(); scriptIdx++) {
2738  const auto& script = scripts[scriptIdx];
2739  //
2740  if (script.type == MinitScript::Script::TYPE_STACKLET) {
2741  // valid: root scope
2742  if (script.arguments.empty()) continue;
2743  // invalid: more than 1 argument
2744  if (script.arguments.size() != 1) {
2745  _Console::printLine(scriptFileName + ": Stacklet: " + script.condition + ": invalid arguments: only none(for root scope) or one argument is allowed, which defines a function/stacklet as stacklet scope");
2746  parseErrors.push_back(scriptFileName + ": Stacklet: " + script.condition + ": invalid arguments: only none(for root scope) or one argument is allowed, which defines a function/stacklet as stacklet scope");
2747  scriptValid = false;
2748  return;
2749  }
2750  //
2751  auto stackletScopeScriptIdx = getFunctionScriptIdx(script.arguments[0].name);
2752  // invalid: scope function/stacklet not found
2753  if (stackletScopeScriptIdx == SCRIPTIDX_NONE) {
2754  _Console::printLine(scriptFileName + ": Stacklet: " + script.condition + ": invalid arguments: scope function/stacklet not found: " + script.arguments[0].name);
2755  parseErrors.push_back(scriptFileName + ": Stacklet: " + script.condition + ": invalid arguments: scope function/stacklet not found: " + script.arguments[0].name);
2756  scriptValid = false;
2757  return;
2758  } else
2759  // invalid: stacklet can not have itself as scope stacklet
2760  if (stackletScopeScriptIdx == scriptIdx) {
2761  _Console::printLine(scriptFileName + ": Stacklet: " + script.condition + ": invalid arguments: scope function/stacklet can not be the stacklet itself");
2762  parseErrors.push_back(scriptFileName + ": Stacklet: " + script.condition + ": invalid arguments: scope function/stacklet can not be the stacklet itself");
2763  scriptValid = false;
2764  return;
2765  }
2766  }
2767  //
2768  if (script.type == MinitScript::Script::TYPE_FUNCTION ||
2769  script.type == MinitScript::Script::TYPE_ON ||
2770  script.type == MinitScript::Script::TYPE_ONENABLED) {
2771  //
2772  if (validateStacklets(scriptIdx) == false) {
2773  scriptValid = false;
2774  return;
2775  }
2776  }
2777  //
2778  if (script.type == MinitScript::Script::TYPE_FUNCTION) {
2779  //
2780  if (script.callable == true) {
2781  //
2782  if (validateCallable(script.condition) == false) {
2783  scriptValid = false;
2784  return;
2785  }
2786  } else {
2787  //
2788  vector<string> functionStack;
2789  //
2790  if (validateContextFunctions(script.condition, functionStack) == false) {
2791  scriptValid = false;
2792  return;
2793  }
2794  }
2795  }
2796  }
2797  }
2798 
2799  // check for initialize and error condition
2800  auto haveErrorScript = false;
2801  for (const auto& script: scripts) {
2802  if (script.type == Script::TYPE_ONENABLED) {
2803  // no op
2804  } else
2805  if (script.condition == "error") {
2806  haveErrorScript = true;
2807  }
2808  }
2809  if (haveErrorScript == false) {
2810  _Console::printLine(scriptPathName + "/" + scriptFileName + ": Script needs to define an error condition");
2811  parseErrors.push_back("Script needs to define an error condition");
2812  scriptValid = false;
2813  return;
2814  }
2815 
2816  //
2817  startScript();
2818 }
2819 
2820 void MinitScript::startScript() {
2821  if (VERBOSE == true) _Console::printLine("MinitScript::startScript(): '" + scriptFileName + ": Starting script.");
2822  if (scriptValid == false) {
2823  _Console::printLine(scriptFileName + ": Script not valid: not starting");
2824  return;
2825  }
2826  auto& scriptState = getScriptState();
2827  for (const auto& [variableName, variable]: scriptState.variables) delete variable;
2828  scriptState.variables.clear();
2829  scriptState.running = true;
2830  registerVariables();
2831  //
2832  if (hasCondition("initialize") == true) emit("initialize");
2833 }
2834 
2835 int MinitScript::determineScriptIdxToStart() {
2836  if (VERBOSE == true) _Console::printLine("MinitScript::determineScriptIdxToStart()");
2837  auto nothingScriptIdx = SCRIPTIDX_NONE;
2838  auto scriptIdx = 0;
2839  for (const auto& script: scripts) {
2840  if (script.type != Script::TYPE_ON) {
2841  // no op
2842  } else
2843  if (script.emitCondition == true && script.condition == "nothing") {
2844  nothingScriptIdx = scriptIdx;
2845  // no op
2846  } else
2847  if (script.emitCondition == true) {
2848  // emit condition
2849  } else {
2850  auto conditionMet = true;
2851  Variable returnValue;
2852  if (evaluateInternal(script.condition, script.executableCondition, returnValue) == true) {
2853  auto returnValueBoolValue = false;
2854  if (returnValue.getBooleanValue(returnValueBoolValue, false) == false) {
2855  _Console::printLine("MinitScript::determineScriptIdxToStart(): " + script.condition + ": Expecting boolean return value, but got: " + returnValue.getAsString());
2856  conditionMet = false;
2857  } else
2858  if (returnValueBoolValue == false) {
2859  conditionMet = false;
2860  }
2861  } else {
2862  _Console::printLine("MinitScript::determineScriptIdxToStart(): " + scriptFileName + ":" + to_string(script.line) + ": " + script.condition + ": Parse error");
2863  }
2864  if (conditionMet == false) {
2865  if (VERBOSE == true) {
2866  _Console::print("MinitScript::determineScriptIdxToStart(): " + script.condition + ": FAILED");
2867  }
2868  } else {
2869  if (VERBOSE == true) {
2870  _Console::print("MinitScript::determineScriptIdxToStart(): " + script.condition + ": OK");
2871  }
2872  return scriptIdx;
2873  }
2874  }
2875  scriptIdx++;
2876  }
2877  return nothingScriptIdx;
2878 }
2879 
2880 int MinitScript::determineNamedScriptIdxToStart() {
2881  if (VERBOSE == true) _Console::printLine("MinitScript::determineNamedScriptIdxToStart()");
2882  // TODO: we could have a hash map here to speed up enabledConditionName -> script lookup
2883  const auto& scriptState = getScriptState();
2884  for (const auto& enabledConditionName: enabledNamedConditions) {
2885  auto scriptIdx = 0;
2886  for (const auto& script: scripts) {
2887  if (script.type != Script::TYPE_ONENABLED ||
2888  script.name != enabledConditionName) {
2889  // no op
2890  } else {
2891  auto conditionMet = true;
2892  Variable returnValue;
2893  if (evaluateInternal(script.condition, script.executableCondition, returnValue) == true) {
2894  auto returnValueBoolValue = false;
2895  if (returnValue.getBooleanValue(returnValueBoolValue, false) == false) {
2896  _Console::printLine("MinitScript::determineNamedScriptIdxToStart(): " + script.condition + ": Expecting boolean return value, but got: " + returnValue.getAsString());
2897  conditionMet = false;
2898  } else
2899  if (returnValueBoolValue == false) {
2900  conditionMet = false;
2901  }
2902  } else {
2903  _Console::printLine("MinitScript::determineNamedScriptIdxToStart(): " + scriptFileName + ":" + to_string(script.line) + ": " + script.condition + ": Parse error");
2904  }
2905  if (conditionMet == false) {
2906  if (VERBOSE == true) {
2907  _Console::print("MinitScript::determineNamedScriptIdxToStart(): " + script.condition + ": FAILED");
2908  }
2909  } else {
2910  if (VERBOSE == true) {
2911  _Console::print("MinitScript::determineNamedScriptIdxToStart(): " + script.condition + ": OK");
2912  }
2913  return scriptIdx;
2914  }
2915  }
2916  scriptIdx++;
2917  }
2918  }
2919  return SCRIPTIDX_NONE;
2920 }
2921 
2922 const string MinitScript::doStatementPreProcessing(const string& processedStatement, const Statement& statement) {
2923  auto preprocessedStatement = processedStatement;
2924  //
2925  struct StatementOperator {
2926  int idx { OPERATORIDX_NONE };
2927  Operator operator_;
2928  string invalidOperator;
2929  };
2930  //
2931  auto trimArgument = [](const string& argument) -> const string {
2932  auto leftNewLineCount = 0;
2933  for (auto i = 0; i < argument.size(); i++) {
2934  auto c = argument[i];
2935  if (c == '\n') {
2936  leftNewLineCount++;
2937  } else
2938  if (_Character::isSpace(c) == false) {
2939  break;
2940  }
2941  }
2942  auto rightNewLineCount = 0;
2943  for (int i = argument.size() - 1; i >= 0; i--) {
2944  auto c = argument[i];
2945  if (c == '\n') {
2946  rightNewLineCount++;
2947  } else
2948  if (_Character::isSpace(c) == false) {
2949  break;
2950  }
2951  }
2952  auto processedArgument = _StringTools::trim(argument);
2953  if (_StringTools::startsWith(processedArgument, "(") == true && _StringTools::endsWith(processedArgument, ")") == true) {
2954  processedArgument = _StringTools::substring(processedArgument, 1, processedArgument.size() - 1);
2955  }
2956  return processedArgument.empty() == true?string():_StringTools::generate("\n", leftNewLineCount) + processedArgument + _StringTools::generate("\n", rightNewLineCount);
2957  };
2958  //
2959  auto findLeftArgument = [&](const string& statement, int position, int& length, string& brackets) -> const string {
2960  //
2961  auto bracketCount = 0;
2962  auto squareBracketCount = 0;
2963  auto curlyBracketCount = 0;
2964  auto quote = '\0';
2965  auto lc = '\0';
2966  string argument;
2967  length = 0;
2968  for (int i = position; i >= 0; i--) {
2969  auto c = statement[i];
2970  auto lc = i > 0?statement[i - 1]:'\0';
2971  if (lc == '\\' && c == '\\') lc = '\0';
2972  //
2973  if ((c == '"' || c == '\'') && lc != '\\') {
2974  if (quote == '\0') {
2975  quote = c;
2976  } else
2977  if (quote == c) {
2978  quote = '\0';
2979  }
2980  argument = c + argument;
2981  } else
2982  if (quote == '\0') {
2983  if (c == ')') {
2984  bracketCount++;
2985  argument = c + argument;
2986  } else
2987  if (c == ']') {
2988  squareBracketCount++;
2989  argument = c + argument;
2990  } else
2991  if (c == '}') {
2992  curlyBracketCount++;
2993  argument = c + argument;
2994  } else
2995  if (c == '(') {
2996  bracketCount--;
2997  if (bracketCount < 0) {
2998  brackets = "()";
2999  return trimArgument(argument);
3000  }
3001  argument = c + argument;
3002  } else
3003  if (c == '[') {
3004  squareBracketCount--;
3005  if (squareBracketCount < 0) {
3006  brackets = "[]";
3007  return trimArgument(argument);
3008  }
3009  argument = c + argument;
3010  } else
3011  if (c == '{') {
3012  curlyBracketCount--;
3013  if (curlyBracketCount < 0) {
3014  brackets = "{}";
3015  return trimArgument(argument);
3016  }
3017  argument = c + argument;
3018  } else
3019  if (squareBracketCount == 0 && curlyBracketCount == 0 && c == ',') {
3020  if (bracketCount == 0) return trimArgument(argument);
3021  argument = c + argument;
3022  } else {
3023  argument = c + argument;
3024  }
3025  } else
3026  if (quote != '\0') {
3027  argument = c + argument;
3028  }
3029  length++;
3030  }
3031  return trimArgument(argument);
3032  };
3033  //
3034  auto findRightArgument = [&](const string& statement, int position, int& length, string& brackets) -> const string {
3035  //
3036  auto bracketCount = 0;
3037  auto squareBracketCount = 0;
3038  auto curlyBracketCount = 0;
3039  auto quote = '\0';
3040  auto lc = '\0';
3041  string argument;
3042  length = 0;
3043  for (auto i = position; i < statement.size(); i++) {
3044  auto c = statement[i];
3045  // quote?
3046  if ((c == '"' || c == '\'') && lc != '\\') {
3047  if (quote == '\0') {
3048  quote = c;
3049  } else
3050  if (quote == c) {
3051  quote = '\0';
3052  }
3053  argument+= c;
3054  } else
3055  // no quote
3056  if (quote == '\0') {
3057  if (c == '(') {
3058  bracketCount++;
3059  argument+= c;
3060  } else
3061  if (c == '[') {
3062  squareBracketCount++;
3063  argument+= c;
3064  } else
3065  if (c == '{') {
3066  curlyBracketCount++;
3067  argument+= c;
3068  } else
3069  if (c == ')') {
3070  bracketCount--;
3071  if (bracketCount < 0) {
3072  brackets = "()";
3073  return trimArgument(argument);
3074  }
3075  argument+= c;
3076  } else
3077  if (c == ']') {
3078  squareBracketCount--;
3079  // array initializer?
3080  if (_StringTools::startsWith(argument, "[") == false) {
3081  // no
3082  if (squareBracketCount < 0) {
3083  brackets = "[]";
3084  return trimArgument(argument);
3085  }
3086  }
3087  argument+= c;
3088  } else
3089  if (c == '}') {
3090  curlyBracketCount--;
3091  if (curlyBracketCount < 0) {
3092  brackets = "{}";
3093  return trimArgument(argument);
3094  }
3095  argument+= c;
3096  } else
3097  if (squareBracketCount == 0 && curlyBracketCount == 0 && c == ',') {
3098  if (bracketCount == 0) return trimArgument(argument);
3099  //
3100  if (argument.empty() == true && (c == ' ' || c == '\t')) {
3101  // no op
3102  } else {
3103  argument+= c;
3104  }
3105  } else {
3106  //
3107  if (argument.empty() == true && (c == ' ' || c == '\t')) {
3108  // no op
3109  } else {
3110  argument+= c;
3111  }
3112  }
3113  } else
3114  if (quote != '\0') {
3115  argument+= c;
3116  }
3117  length++;
3118  //
3119  lc = lc == '\\' && c == '\\'?'\0':c;
3120  }
3121  //
3122  return trimArgument(argument);
3123  };
3124  //
3125  auto viewIsLamdaFunction = [](const string_view& candidate) -> bool {
3126  if (candidate.empty() == true) return false;
3127  //
3128  auto i = 0;
3129  // (
3130  if (candidate[i++] != '(') return false;
3131  // spaces
3132  for (; i < candidate.size() && _Character::isSpace(candidate[i]) == true; i++);
3133  if (i >= candidate.size()) return false;
3134  //
3135  auto argumentStartIdx = string::npos;
3136  auto argumentEndIdx = string::npos;
3137  //
3138  for (; i < candidate.size(); i++) {
3139  auto c = candidate[i];
3140  if (c == '&') {
3141  if (argumentStartIdx == string::npos) {
3142  argumentStartIdx = i;
3143  } else {
3144  return false;
3145  }
3146  } else
3147  if (c == '$') {
3148  if (argumentStartIdx == string::npos) {
3149  argumentStartIdx = i;
3150  } else
3151  if (argumentStartIdx == i - 1 && candidate[argumentStartIdx] == '&') {
3152  // no op
3153  } else {
3154  return false;
3155  }
3156  } else
3157  if (c == ',' || c == ')') {
3158  if (argumentEndIdx == string::npos) {
3159  if (argumentStartIdx != string::npos) {
3160  argumentEndIdx = i;
3161  }
3162  //
3163  argumentStartIdx = string::npos;
3164  argumentEndIdx = string::npos;
3165  } else {
3166  return false;
3167  }
3168  if (c == ')') {
3169  i++;
3170  break;
3171  }
3172  } else
3173  if (argumentStartIdx != string::npos && _Character::isAlphaNumeric(candidate[i]) == false && c != '_') {
3174  return false;
3175  }
3176  }
3177  //
3178  if (i >= candidate.size()) return false;
3179  // spaces
3180  for (; i < candidate.size() && _Character::isSpace(candidate[i]) == true; i++);
3181  if (i >= candidate.size()) return false;
3182  // -
3183  if (candidate[i++] != '-') return false;
3184  //
3185  if (i >= candidate.size()) return false;
3186  // >
3187  if (candidate[i++] != '>') return false;
3188  // spaces
3189  for (; i < candidate.size() && _Character::isSpace(candidate[i]) == true; i++);
3190  if (i >= candidate.size()) return false;
3191  //
3192  if (candidate[i++] != '{') return false;
3193  //
3194  return true;
3195  };
3196  //
3197  auto getNextStatementOperator = [&](const string& processedStatement, StatementOperator& nextOperator, const Statement& statement) {
3198  bool lamdaFunctionDeclaration = false;
3199  auto curlyBracketCount = 0;
3200  auto bracketCount = 0;
3201  auto quote = '\0';
3202  auto lc = '\0';
3203  for (auto i = 0; i < processedStatement.size(); i++) {
3204  auto c = processedStatement[i];
3205  if ((c == '"' || c == '\'') && lc != '\\') {
3206  if (quote == '\0') {
3207  quote = c;
3208  } else
3209  if (quote == c) {
3210  quote = '\0';
3211  }
3212  } else
3213  if (quote == '\0') {
3214  if (c == '(') {
3215  // read ahead if inline/lambda function
3216  string_view lamdaFunctionCandidate(&processedStatement[i], processedStatement.size() - i);
3217  if (viewIsLamdaFunction(lamdaFunctionCandidate) == true) lamdaFunctionDeclaration = true;
3218  bracketCount++;
3219  } else
3220  if (c == ')') {
3221  if (lamdaFunctionDeclaration == true) lamdaFunctionDeclaration = false;
3222  bracketCount--;
3223  } else
3224  if (c == '{') {
3225  curlyBracketCount++;
3226  } else
3227  if (c == '}') {
3228  curlyBracketCount--;
3229  } else
3230  if (curlyBracketCount == 0 && lamdaFunctionDeclaration == false) {
3231  {
3232  // find operator from current statement character index
3233  string operatorCandidate;
3234  for (int j = i - 1; j >= 0; j--) {
3235  if (isOperatorChar(processedStatement[j]) == false) break;
3236  operatorCandidate = processedStatement[j] + operatorCandidate;
3237  }
3238  for (auto j = i; j < processedStatement.size(); j++) {
3239  if (isOperatorChar(processedStatement[j]) == false) break;
3240  operatorCandidate = operatorCandidate + processedStatement[j];
3241  }
3242  // skip on empty operator or -> object member call
3243  if (operatorCandidate.empty() == false && operatorCandidate != "->") {
3244  // invalid operator
3245  if (isOperator(operatorCandidate) == false) {
3246  nextOperator.idx = i;
3247  nextOperator.operator_ = OPERATOR_NONE;
3248  nextOperator.invalidOperator = operatorCandidate;
3249  }
3250  }
3251  }
3252  for (int j = OPERATOR_NONE + 1; j < OPERATOR_MAX; j++) {
3253  auto priorizedOperator = static_cast<Operator>(j);
3254  string operatorCandidate;
3255  auto operatorString = getOperatorAsString(priorizedOperator);
3256  if (operatorString.size() == 1) operatorCandidate+= processedStatement[i];
3257  if (operatorString.size() == 2 && i + 1 < processedStatement.size()) {
3258  operatorCandidate+= processedStatement[i];
3259  operatorCandidate+= processedStatement[i + 1];
3260  }
3261  if (operatorString == operatorCandidate && (nextOperator.idx == OPERATORIDX_NONE || priorizedOperator > nextOperator.operator_)) {
3262  auto isMemberAccessOrAssignmentOperator = [](const string& candidate) { return candidate == "->"; };
3263  if (operatorString.size() == 1 &&
3264  i > 0 &&
3265  isOperatorChar(processedStatement[i - 1]) == true &&
3266  (isMemberAccessOrAssignmentOperator(processedStatement[i - 1] + operatorString) == true ||
3267  isOperator(processedStatement[i - 1] + operatorString) == true)) {
3268  continue;
3269  }
3270  if (operatorString.size() == 1 &&
3271  i + 1 < processedStatement.size() &&
3272  isOperatorChar(processedStatement[i + 1]) == true &&
3273  (isMemberAccessOrAssignmentOperator(operatorString + processedStatement[i + 1]) == true ||
3274  isOperator(operatorString + processedStatement[i + 1]) == true)) {
3275  continue;
3276  }
3277  if (operatorString.size() == 2 &&
3278  i > 0 &&
3279  isOperatorChar(processedStatement[i - 1]) == true) {
3280  continue;
3281  }
3282  if (operatorString.size() == 2 &&
3283  i + 2 < processedStatement.size() &&
3284  isOperatorChar(processedStatement[i + 2]) == true) {
3285  continue;
3286  } else
3287  if (priorizedOperator == OPERATOR_SUBTRACTION) {
3288  string leftArgumentBrackets;
3289  auto leftArgumentLeft = 0;
3290  auto leftArgument = findLeftArgument(processedStatement, i - 1, leftArgumentLeft, leftArgumentBrackets);
3291  if (leftArgument.length() == 0) continue;
3292  }
3293  nextOperator.idx = i;
3294  nextOperator.operator_ = priorizedOperator;
3295  }
3296  }
3297  }
3298  }
3299  lc = lc == '\\' && c == '\\'?'\0':c;
3300  }
3301 
3302  //
3303  if (bracketCount > 0) {
3304  //
3305  return false;
3306  }
3307  //
3308  return nextOperator.idx != OPERATORIDX_NONE;
3309  };
3310  //
3311  auto encodeOperatorString = [](const string& operatorString) -> int64_t {
3312  int64_t result = 0ll;
3313  if (operatorString.size() >= 1) result+= static_cast<int64_t>(operatorString[0]);
3314  if (operatorString.size() >= 2) result+= static_cast<int64_t>(operatorString[1]) << 8;
3315  return result;
3316  };
3317  //
3318  StatementOperator nextOperator;
3319  while (getNextStatementOperator(preprocessedStatement, nextOperator, statement) == true) {
3320  //
3321  if (nextOperator.invalidOperator.empty() == false) {
3322  _Console::printLine(getStatementInformation(statement, getStatementSubLineIdx(preprocessedStatement, nextOperator.idx)) + ": Invalid operator: " + nextOperator.invalidOperator);
3323  scriptValid = false;
3324  return preprocessedStatement;
3325  }
3326  //
3327  Method* method { nullptr };
3328  Method* prefixOperatorMethod { nullptr };
3329  Method* postfixOperatorMethod { nullptr };
3330  //
3331  {
3332  // resolve method
3333  method = getOperatorMethod(nextOperator.operator_);
3334  if (method == nullptr) {
3335  _Console::printLine(getStatementInformation(statement, getStatementSubLineIdx(preprocessedStatement, nextOperator.idx)) + ": No operator method found");
3336  scriptValid = false;
3337  return preprocessedStatement;
3338  }
3339  // default for methods with 1 argument is post fix operator method
3340  postfixOperatorMethod = method;
3341  //
3342  // special case prefix or postfix operator methods, that have 1 argument only
3343  if (method->getOperator() == OPERATOR_POSTFIX_INCREMENT ||
3344  method->getOperator() == OPERATOR_PREFIX_INCREMENT) {
3345  prefixOperatorMethod = getOperatorMethod(OPERATOR_POSTFIX_INCREMENT);
3346  postfixOperatorMethod = getOperatorMethod(OPERATOR_PREFIX_INCREMENT);
3347  }
3348  //
3349  if (method->getOperator() == OPERATOR_POSTFIX_DECREMENT ||
3350  method->getOperator() == OPERATOR_PREFIX_DECREMENT) {
3351  prefixOperatorMethod = getOperatorMethod(OPERATOR_POSTFIX_DECREMENT);
3352  postfixOperatorMethod = getOperatorMethod(OPERATOR_PREFIX_DECREMENT);
3353  }
3354  //
3355  if (method->getOperator() == OPERATOR_POSTFIX_INCREMENT ||
3356  method->getOperator() == OPERATOR_PREFIX_INCREMENT ||
3357  method->getOperator() == OPERATOR_POSTFIX_DECREMENT ||
3358  method->getOperator() == OPERATOR_PREFIX_DECREMENT) {
3359  //
3360  if (prefixOperatorMethod == nullptr) {
3361  _Console::printLine(getStatementInformation(statement, getStatementSubLineIdx(preprocessedStatement, nextOperator.idx)) + ": No prefix operator method found");
3362  scriptValid = false;
3363  return preprocessedStatement;
3364  }
3365  //
3366  if (postfixOperatorMethod == nullptr) {
3367  _Console::printLine(getStatementInformation(statement, getStatementSubLineIdx(preprocessedStatement, nextOperator.idx)) + ": No postfix operator method found");
3368  scriptValid = false;
3369  return preprocessedStatement;
3370  }
3371  }
3372  }
3373  //
3374  if (method->isVariadic() == false &&
3375  method->getArgumentTypes().size() == 2) {
3376  // find the single argument right
3377  auto operatorString = getOperatorAsString(nextOperator.operator_);
3378  // find left argument
3379  string leftArgumentBrackets;
3380  int leftArgumentLength = 0;
3381  auto leftArgument = findLeftArgument(preprocessedStatement, nextOperator.idx - 1, leftArgumentLength, leftArgumentBrackets);
3382  // find argument right
3383  string rightArgumentBrackets;
3384  int rightArgumentLength = 0;
3385  auto rightArgument = findRightArgument(preprocessedStatement, nextOperator.idx + operatorString.size(), rightArgumentLength, rightArgumentBrackets);
3386  //
3387  if (leftArgument.empty() == false) {
3388  //
3389  auto leftArgumentNewLines = 0;
3390  for (auto i = 0; i < leftArgument.size(); i++) {
3391  auto c = leftArgument[i];
3392  if (c == '\n') leftArgumentNewLines++; else break;
3393  }
3394  // substitute with method call
3395  preprocessedStatement =
3396  _StringTools::substring(preprocessedStatement, 0, nextOperator.idx - leftArgumentLength) +
3397  _StringTools::generate("\n", leftArgumentNewLines) +
3398  prefixOperatorMethod->getMethodName() + "(" + _StringTools::substring(leftArgument, leftArgumentNewLines) + ", " + to_string(encodeOperatorString(operatorString)) + ")" +
3399  _StringTools::substring(preprocessedStatement, nextOperator.idx + operatorString.size(), preprocessedStatement.size()
3400  );
3401  } else
3402  if (rightArgument.empty() == false) {
3403  // substitute with method call
3404  preprocessedStatement =
3405  _StringTools::substring(preprocessedStatement, 0, nextOperator.idx) +
3406  postfixOperatorMethod->getMethodName() + "(" + rightArgument + ", " + to_string(encodeOperatorString(operatorString)) + ")" +
3407  _StringTools::substring(preprocessedStatement, nextOperator.idx + operatorString.size() + rightArgumentLength, preprocessedStatement.size());
3408  } else {
3409  _Console::printLine(getStatementInformation(statement, getStatementSubLineIdx(preprocessedStatement, nextOperator.idx)) + ": Requires left or right argument");
3410  scriptValid = false;
3411  return preprocessedStatement;
3412  }
3413  } else
3414  if (method->isVariadic() == true ||
3415  method->getArgumentTypes().size() == 3) {
3416  //
3417  auto operatorString = getOperatorAsString(nextOperator.operator_);
3418  // find left argument
3419  string leftArgumentBrackets;
3420  int leftArgumentLength = 0;
3421  auto leftArgument = findLeftArgument(preprocessedStatement, nextOperator.idx - 1, leftArgumentLength, leftArgumentBrackets);
3422  // find right argument
3423  string rightArgumentBrackets;
3424  int rightArgumentLength = 0;
3425  auto rightArgument = findRightArgument(preprocessedStatement, nextOperator.idx + operatorString.size(), rightArgumentLength, rightArgumentBrackets);
3426  //
3427  if (leftArgument.empty() == true || rightArgument.empty() == true) {
3428  _Console::printLine(getStatementInformation(statement, getStatementSubLineIdx(preprocessedStatement, nextOperator.idx)) + ": Requires left and right argument");
3429  scriptValid = false;
3430  return preprocessedStatement;
3431  }
3432  //
3433  if (nextOperator.operator_ == OPERATOR_SET) {
3434  leftArgument = "\"" + _StringTools::replace(doStatementPreProcessing(leftArgument, statement), "\"", "\\\"") + "\"";
3435  }
3436  //
3437  auto leftArgumentNewLines = 0;
3438  for (auto i = 0; i < leftArgument.size(); i++) {
3439  auto c = leftArgument[i];
3440  if (c == '\n') leftArgumentNewLines++; else break;
3441  }
3442  // substitute with method call
3443  preprocessedStatement =
3444  _StringTools::substring(preprocessedStatement, 0, nextOperator.idx - leftArgumentLength) +
3445  _StringTools::generate("\n", leftArgumentNewLines) +
3446  method->getMethodName() + "(" + _StringTools::substring(leftArgument, leftArgumentNewLines) + ", " + rightArgument + ", " + to_string(encodeOperatorString(operatorString)) + ")" +
3447  _StringTools::substring(preprocessedStatement, nextOperator.idx + operatorString.size() + rightArgumentLength, preprocessedStatement.size()
3448  );
3449  //
3450  }
3451  //
3452  nextOperator = StatementOperator();
3453  }
3454  //
3455  return preprocessedStatement;
3456 }
3457 
3458 bool MinitScript::getObjectMemberAccess(const string_view& executableStatement, string_view& object, string_view& method, int& methodStartIdx, const Statement& statement) {
3459  //
3460  auto objectMemberAccess = false;
3461  auto objectStartIdx = string::npos;
3462  auto objectEndIdx = string::npos;
3463  auto memberCallStartIdx = string::npos;
3464  auto memberCallEndIdx = string::npos;
3465  //
3466  auto quote = '\0';
3467  auto lc = '\0';
3468  auto bracketCount = 0;
3469  auto squareBracketCount = 0;
3470  auto curlyBracketCount = 0;
3471  for (auto i = 0; i < executableStatement.size(); i++) {
3472  //
3473  auto c = executableStatement[i];
3474  // handle quotes
3475  if ((c == '"' || c == '\'') && lc != '\\') {
3476  if (quote == '\0') {
3477  quote = c;
3478  } else
3479  if (quote == c) {
3480  quote = '\0';
3481  }
3482  } else
3483  if (quote != '\0') {
3484  // no op
3485  } else
3486  if (c == '(') {
3487  bracketCount++;
3488  } else
3489  if (c == ')') {
3490  bracketCount--;
3491  } else
3492  if (c == '[') {
3493  squareBracketCount++;
3494  } else
3495  if (c == ']') {
3496  squareBracketCount--;
3497  } else
3498  if (c == '{') {
3499  curlyBracketCount++;
3500  } else
3501  if (c == '}') {
3502  curlyBracketCount--;
3503  } else
3504  if (i > 2 &&
3505  lc == '-' && c == '>' &&
3506  bracketCount == 0 &&
3507  squareBracketCount == 0 &&
3508  curlyBracketCount == 0) {
3509  //
3510  auto objectStartIdxCandidate = 0;
3511  auto objectEndIdxCandidate = i - 1;
3512  auto memberCallStartIdxCandidate = i + 1;
3513  auto memberCallEndIdxCandidate = executableStatement.size();
3514  //
3515  auto objectCandidate = _StringTools::viewTrim(string_view(&executableStatement[objectStartIdxCandidate], objectEndIdxCandidate - objectStartIdxCandidate));
3516  auto methodCandidate = _StringTools::viewTrim(string_view(&executableStatement[memberCallStartIdxCandidate], memberCallEndIdxCandidate - memberCallStartIdxCandidate));
3517  // we need to check the serious candidates here
3518  if (objectCandidate.empty() == false &&
3519  methodCandidate.empty() == false &&
3520  (viewIsStringLiteral(objectCandidate) == true || viewIsVariableAccess(objectCandidate) == true || viewIsCall(objectCandidate) == true)) {
3521  //
3522  objectStartIdx = objectStartIdxCandidate;
3523  objectEndIdx = objectEndIdxCandidate;
3524  memberCallStartIdx = memberCallStartIdxCandidate;
3525  memberCallEndIdx = memberCallEndIdxCandidate;
3526  //
3527  object = objectCandidate;
3528  method = methodCandidate;
3529  //
3530  methodStartIdx = memberCallStartIdx;
3531  objectMemberAccess = true;
3532  // dont break here, we can have multiple member access operators here, but we want the last one in this step
3533  }
3534  }
3535  //
3536  lc = lc == '\\' && c == '\\'?'\0':c;
3537  }
3538  //
3539  return objectMemberAccess;
3540 }
3541 
3542 void MinitScript::dumpScriptState(ScriptState& scriptState, const string& message) {
3543  _Console::printLine("MinitScript::dumpScriptState(): " + (message.empty() == false?message + ": ":"") + to_string(scriptStateStack.size()) + " on stack");
3544  _Console::printLine(string("\t") + "state: " + to_string(scriptState.state));
3545  _Console::printLine(string("\t") + "lastState: " + to_string(scriptState.lastState));
3546  _Console::printLine(string("\t") + "running: " + (scriptState.running == true?"true":"false"));
3547  _Console::printLine(string("\t") + "scriptIdx: " + to_string(scriptState.scriptIdx));
3548  _Console::printLine(string("\t") + "statementIdx: " + to_string(scriptState.statementIdx));
3549  _Console::printLine(string("\t") + "gotoStatementIdx: " + to_string(scriptState.gotoStatementIdx));
3550  _Console::printLine(string("\t") + "variable count: " + to_string(scriptState.variables.size()));
3551  _Console::printLine(string("\t") + "block stack count: " + to_string(scriptState.blockStack.size()));
3552  array<string, 9> blockStackTypes {
3553  "TYPE_NONE",
3554  "TYPE_GLOBAL",
3555  "TYPE_STACKLET",
3556  "TYPE_FUNCTION",
3557  "TYPE_FOR",
3558  "TYPE_FORTIME",
3559  "TYPE_IF",
3560  "TYPE_SWITCH",
3561  "TYPE_CASE"
3562  };
3563  for (const auto& block: scriptState.blockStack) {
3564  _Console::printLine(string("\t\t") + blockStackTypes[block.type]);
3565  }
3566  _Console::printLine(string("\t") + "returnValue: " + scriptState.returnValue.getValueAsString());
3567 }
3568 
3569 bool MinitScript::call(int scriptIdx, span<Variable>& arguments, Variable& returnValue, bool pushScriptState) {
3570  //
3571  if (scriptIdx < 0 || scriptIdx >= scripts.size()) {
3572  _Console::printLine("MinitScript::call(): Invalid script index: " + to_string(scriptIdx));
3573  return false;
3574  }
3575  auto& script = scripts[scriptIdx];
3576  //
3577  if (script.type != Script::TYPE_FUNCTION &&
3578  script.type != Script::TYPE_STACKLET) {
3579  _Console::printLine("MinitScript::call(): " + (script.name.empty() == false?script.name:script.condition) + ": Script is not a function/callable/stacklet.");
3580  return false;
3581  }
3582  // copy script state
3583  ScriptState currentScriptState = getScriptState();
3584  //
3585  if (pushScriptState == true) {
3586  if (script.type == Script::TYPE_STACKLET) {
3587  _Console::printLine("MinitScript::call(): " + script.condition + ": Stacklets can not be called with a stack.");
3588  return false;
3589  }
3590  this->pushScriptState();
3591  // script state vector could get modified, so
3592  auto& scriptState = getScriptState();
3593  // also put named arguments into state context variables
3594  auto argumentIdx = 0;
3595  for (const auto& argument: script.arguments) {
3596  if (argumentIdx == arguments.size()) {
3597  break;
3598  }
3599  // private scope
3600  if (argument.privateScope == true) {
3601  arguments[argumentIdx].setPrivateScope();
3602  } else {
3603  arguments[argumentIdx].unsetPrivateScope();
3604  }
3605  //
3606  setVariable(argument.name, arguments[argumentIdx], nullptr, argument.reference);
3607  argumentIdx++;
3608  }
3609  //
3610  resetScriptExecutationState(scriptIdx, STATEMACHINESTATE_NEXT_STATEMENT);
3611  } else {
3612  //
3613  if (script.type != Script::TYPE_STACKLET) {
3614  _Console::printLine("MinitScript::call(): " + script.condition + ": Function/Callable can not be called with no stack.");
3615  return false;
3616  }
3617  //
3618  resetStackletScriptExecutationState(scriptIdx, STATEMACHINESTATE_NEXT_STATEMENT);
3619  }
3620  // script state vector could get modified, so
3621  {
3622  auto& scriptState = getScriptState();
3623  // run this function dude
3624  scriptState.running = true;
3625  }
3626  // execute
3627  for (;true;) {
3628  execute();
3629  // run this function dude
3630  if (getScriptState().running == false) break;
3631  }
3632  // get return value
3633  {
3634  const auto& scriptState = getScriptState();
3635  // run this function dude
3636  returnValue.setValue(scriptState.returnValue);
3637  }
3638  // done, pop the function script state
3639  if (pushScriptState == true) {
3640  popScriptState();
3641  } else {
3642  auto& scriptState = getScriptState();
3643  scriptState.state = currentScriptState.state;
3644  scriptState.lastState = currentScriptState.lastState;
3645  scriptState.lastStateMachineState = currentScriptState.lastStateMachineState;
3646  scriptState.running = currentScriptState.running;
3647  scriptState.scriptIdx = currentScriptState.scriptIdx;
3648  scriptState.statementIdx = currentScriptState.statementIdx;
3649  scriptState.gotoStatementIdx = currentScriptState.gotoStatementIdx;
3650  scriptState.returnValue = currentScriptState.returnValue;
3651  }
3652  // try garbage collection
3653  tryGarbageCollection();
3654  // if function calls are worked off, we can do the deferred emit
3655  if (isFunctionRunning() == false && deferredEmit.empty() == false) {
3656  auto condition = deferredEmit;
3657  deferredEmit.clear();
3658  emit(condition);
3659  }
3660  //
3661  return true;
3662 }
3663 
3664 const vector<MinitScript::Method*> MinitScript::getMethods() {
3665  vector<Method*> methods;
3666  for (const auto& [methodName, method]: this->methods) {
3667  if (method->isPrivate() == true) continue;
3668  methods.push_back(method);
3669  }
3670  //
3671  struct {
3672  bool operator()(Method* a, Method* b) const {
3673  auto aPrefix = _StringTools::substring(a->getMethodName(), 0, _StringTools::lastIndexOf(a->getMethodName(), ".") + 1);
3675  auto bPrefix = _StringTools::substring(b->getMethodName(), 0, _StringTools::lastIndexOf(b->getMethodName(), ".") + 1);
3677  array<string, 6> prefixes {
3678  "is",
3679  "has",
3680  "get",
3681  "set",
3682  "unset",
3683  "compute"
3684  };
3685  array<string, 6> sortPrefixes {
3686  "0",
3687  "1",
3688  "2",
3689  "3",
3690  "4",
3691  "5"
3692  };
3693  int aPrefixIdx = 0;
3694  for (const auto& prefix: prefixes) {
3695  if ((aName != prefix || aPrefix.empty() == false) && _StringTools::startsWith(aName, prefix) == true) {
3696  aName = _StringTools::substring(aName, prefix.size());
3697  break;
3698  }
3699  aPrefixIdx++;
3700  }
3701  int bPrefixIdx = 0;
3702  for (const auto& prefix: prefixes) {
3703  if ((bName != prefix || bPrefix.empty() == false) && _StringTools::startsWith(bName, prefix) == true) {
3704  bName = _StringTools::substring(bName, prefix.size());
3705  break;
3706  }
3707  bPrefixIdx++;
3708  }
3709  if (aName == bName) {
3710  return aPrefix + (aPrefixIdx < 6?sortPrefixes[aPrefixIdx]:"") + aName < bPrefix + (bPrefixIdx < 6?sortPrefixes[bPrefixIdx]:"") + bName;
3711  } else {
3712  return aPrefix + aName < bPrefix + bName;
3713  }
3714  }
3715  } sortFunction;
3716  //
3717  sort(methods.begin(), methods.end(), sortFunction);
3718  //
3719  return methods;
3720 }
3721 
3722 const vector<MinitScript::Method*> MinitScript::getOperatorMethods() {
3723  vector<Method*> methods;
3724  for (const auto& [operatorId, method]: operators) {
3725  methods.push_back(method);
3726  }
3727  return methods;
3728 }
3729 
3730 const string MinitScript::getScriptInformation(int scriptIdx, bool includeStatements) {
3731  if (scriptIdx < 0 || scriptIdx >= scripts.size()) {
3732  _Console::printLine("MinitScript::getScriptInformation(): invalid script index: " + to_string(scriptIdx));
3733  return string();
3734  }
3735  const auto& script = scripts[scriptIdx];
3736  string result;
3737  string argumentsString;
3738  switch(script.type) {
3739  case Script::TYPE_FUNCTION:
3740  case Script::TYPE_STACKLET: {
3741  for (const auto& argument: script.arguments) {
3742  if (argumentsString.empty() == false) argumentsString+= ", ";
3743  if (argument.reference == true) argumentsString+= "&";
3744  if (argument.privateScope == true) argumentsString+= "&";
3745  argumentsString+= argument.name;
3746  }
3747  argumentsString = "(" + argumentsString + ")";
3748  if (script.type == Script::TYPE_FUNCTION) result+= "function: "; else result+= "stacklet: ";
3749  break;
3750  }
3751  case Script::TYPE_ON: result+= "on: "; break;
3752  case Script::TYPE_ONENABLED: result+= "on-enabled: "; break;
3753  default: break;
3754  }
3755  if (script.condition.empty() == false)
3756  result+= script.condition + argumentsString + "; ";
3757  if (script.name.empty() == false) {
3758  result+= "name = '" + script.name + argumentsString + "';\n";
3759  } else {
3760  result+= "\n";
3761  }
3762  if (includeStatements == true) {
3763  auto indent = 1;
3764  auto formatStatement = [&](const string& statement) {
3765  string result;
3766  auto statementMethodEndIdx = _StringTools::indexOf(statement, "(");
3767  if (statementMethodEndIdx == string::npos) statementMethodEndIdx = statement.size();
3768  auto statementMethod = _StringTools::trim(_StringTools::substring(statement, 0, statementMethodEndIdx));
3769  if (statementMethod == "elseif") indent-= 1; else
3770  if (statementMethod == "else") indent-= 1; else
3771  if (statementMethod == "case") indent-= 1; else
3772  if (statementMethod == "default") indent-= 1; else
3773  if (statementMethod == "end") indent-= 1;
3774  for (auto i = 0; i < indent; i++) result+= " ";
3775  result+= statement;
3776  if (statementMethod == "if") indent+= 1; else
3777  if (statementMethod == "elseif") indent+= 1; else
3778  if (statementMethod == "else") indent+= 1; else
3779  if (statementMethod == "forTime") indent+= 1; else
3780  if (statementMethod == "forCondition") indent+= 1; else
3781  if (statementMethod == "switch") indent+= 1; else
3782  if (statementMethod == "case") indent+= 1; else
3783  if (statementMethod == "default") indent+= 1;
3784  return result;
3785  };
3786  result+=
3787  string() +
3788  "\t" + " " + ": start" + "\n";
3789  for (const auto& statement: script.statements) {
3790  string newLineIndent; for (auto i = 0; i < indent + 2; i++) newLineIndent+= " ";
3791  result+=
3792  "\t" +
3793  /*
3794  _StringTools::padLeft(to_string(statement.statementIdx), "0", 4) +
3795  "|" +
3796  */
3797  _StringTools::padLeft(to_string(statement.line), "0", 4) +
3798  ": " +
3799  _StringTools::replace(formatStatement(statement.executableStatement), "\n", "\n\t :" + newLineIndent)
3800  /*
3801  +
3802  (statement.gotoStatementIdx != STATEMENTIDX_NONE?" (gotoStatement " + to_string(statement.gotoStatementIdx) + ")":"")
3803  */
3804  + "\n";
3805  }
3806  result+= "\n";
3807  }
3808  //
3809  return result;
3810 }
3811 
3812 const string MinitScript::getInformation() {
3813  string result;
3814  result+= "Script: " + scriptPathName + "/" + scriptFileName + " (runs " + (native == true?"natively":"interpreted") + ")" + "\n\n";
3815  result+= "\n";
3816 
3817  //
3818  result+="State Machine States:\n";
3819  {
3820  vector<string> states;
3821  for (const auto& [stateMachineStateId, stateMachineState]: stateMachineStates) {
3822  string state;
3823  state = stateMachineState->getName() + "(" + to_string(stateMachineState->getId()) + ")";
3824  states.push_back(state);
3825  }
3826  sort(states.begin(), states.end());
3827  for (const auto& state: states) result+= state+ "\n";
3828  }
3829  result+= "\n";
3830 
3831  //
3832  result+= "Methods:\n";
3833  {
3834  vector<string> methods;
3835  for (const auto& [methodName, method]: this->methods) {
3836  if (method->isPrivate() == true) continue;
3837  string methodDescription;
3838  methodDescription+= method->getMethodName();
3839  methodDescription+= "(";
3840  methodDescription+= method->getArgumentsInformation();
3841  methodDescription+= "): ";
3842  methodDescription+= Variable::getReturnTypeAsString(method->getReturnValueType(), method->isReturnValueNullable());
3843  methods.push_back(methodDescription);
3844  }
3845  sort(methods.begin(), methods.end());
3846  for (const auto& method: methods) result+= method + "\n";
3847  }
3848  result+= "\n";
3849 
3850  //
3851  result+= "Operators:\n";
3852  {
3853  vector<string> operators;
3854  for (const auto& [operatorId, method]: this->operators) {
3855  string operatorString;
3856  operatorString+= getOperatorAsString(method->getOperator());
3857  operatorString+= " --> ";
3858  operatorString+= method->getMethodName();
3859  operatorString+= "(";
3860  operatorString+= method->getArgumentsInformation();
3861  operatorString+= "): ";
3862  operatorString+= Variable::getReturnTypeAsString(method->getReturnValueType(), method->isReturnValueNullable());
3863  operators.push_back(operatorString);
3864  }
3865  sort(operators.begin(), operators.end());
3866  for (const auto& method: operators) result+= method + "\n";
3867  }
3868  result+= "\n";
3869 
3870  //
3871  result+= "Variables:\n";
3872  {
3873  const auto& scriptState = getScriptState();
3874  vector<string> variables;
3875  for (const auto& [scriptVariableName, scriptVariableValue]: scriptState.variables) {
3876  string variable;
3877  variable+= scriptVariableName + " = " + scriptVariableValue->getAsString();
3878  variables.push_back(variable);
3879  }
3880  sort(variables.begin(), variables.end());
3881  for (const auto& variable: variables) result+= variable + "\n";
3882  }
3883  result+= "\n";
3884 
3885  //
3886  result+="Script:\n";
3887  {
3888  auto scriptIdx = 0;
3889  for (const auto& script: scripts) {
3890  result+= getScriptInformation(scriptIdx);
3891  scriptIdx++;
3892  }
3893  }
3894 
3895  //
3896  return result;
3897 }
3898 
3899 void MinitScript::registerStateMachineStates() {
3900  // base
3901  if (native == false) {
3902  //
3903  class ScriptStateNextStatement: public StateMachineState {
3904  private:
3905  MinitScript* minitScript { nullptr };
3906  public:
3907  ScriptStateNextStatement(MinitScript* minitScript): StateMachineState(), minitScript(minitScript) {}
3908  virtual const string getName() override {
3909  return "STATEMACHINESTATE_NEXT_STATEMENT";
3910  }
3911  virtual int getId() override {
3912  return STATEMACHINESTATE_NEXT_STATEMENT;
3913  }
3914  virtual void execute() override {
3915  if (minitScript->getScriptState().statementIdx == STATEMENTIDX_NONE) {
3916  minitScript->enabledNamedConditions.clear();
3917  minitScript->timeEnabledConditionsCheckLast = TIME_NONE;
3918  minitScript->setScriptStateState(STATEMACHINESTATE_WAIT_FOR_CONDITION);
3919  return;
3920  }
3921  if (minitScript->native == false) minitScript->executeNextStatement();
3922  }
3923  };
3924  registerStateMachineState(new ScriptStateNextStatement(this));
3925  }
3926  {
3927  //
3928  class ScriptStateWait: public StateMachineState {
3929  private:
3930  MinitScript* minitScript { nullptr };
3931  public:
3932  ScriptStateWait(MinitScript* minitScript): StateMachineState(), minitScript(minitScript) {}
3933  virtual const string getName() override {
3934  return "STATEMACHINESTATE_WAIT";
3935  }
3936  virtual int getId() override {
3937  return STATEMACHINESTATE_WAIT;
3938  }
3939  virtual void execute() override {
3940  auto now = _Time::getCurrentMillis();
3941  if (now > minitScript->getScriptState().timeWaitStarted + minitScript->getScriptState().timeWaitTime) {
3942  minitScript->setScriptStateState(STATEMACHINESTATE_NEXT_STATEMENT);
3943  } else {
3944  #if !defined(MINITSCRIPT_NO_SLEEP)
3945  _Thread::sleep(10);
3946  #endif
3947  }
3948  }
3949  };
3950  registerStateMachineState(new ScriptStateWait(this));
3951  }
3952  {
3953  //
3954  class ScriptStateWaitForCondition: public StateMachineState {
3955  private:
3956  MinitScript* minitScript { nullptr };
3957  public:
3958  ScriptStateWaitForCondition(MinitScript* minitScript): StateMachineState(), minitScript(minitScript) {}
3959  virtual const string getName() override {
3960  return "STATEMACHINESTATE_WAIT_FOR_CONDITION";
3961  }
3962  virtual int getId() override {
3963  return STATEMACHINESTATE_WAIT_FOR_CONDITION;
3964  }
3965  virtual void execute() override {
3966  auto now = _Time::getCurrentMillis();
3967  if (now < minitScript->getScriptState().timeWaitStarted + minitScript->getScriptState().timeWaitTime) {
3968  return;
3969  }
3970  auto scriptIdxToStart = minitScript->determineScriptIdxToStart();
3971  if (scriptIdxToStart == SCRIPTIDX_NONE) {
3972  minitScript->getScriptState().timeWaitStarted = now;
3973  minitScript->getScriptState().timeWaitTime = 100LL;
3974  return;
3975  }
3976  minitScript->resetScriptExecutationState(scriptIdxToStart, STATEMACHINESTATE_NEXT_STATEMENT);
3977  }
3978  };
3979  registerStateMachineState(new ScriptStateWaitForCondition(this));
3980  }
3981 }
3982 
3983 void MinitScript::registerMethods() {
3984  // unregister old registered methods
3985  for (const auto& [scriptMethodId, scriptMethod]: methods) delete scriptMethod;
3986  methods.clear();
3987 
3988  // register math methods
3989  minitScriptMath = make_unique<MathMethods>(this);
3990  minitScriptMath->registerMethods();
3991 
3992  // base script methods
3993  // register base methods
3994  BaseMethods::registerMethods(this);
3995 
3996  // register string methods
3997  StringMethods::registerMethods(this);
3998 
3999  // register byte array methods
4000  ByteArrayMethods::registerMethods(this);
4001 
4002  // register array methods
4003  ArrayMethods::registerMethods(this);
4004 
4005  // register map methods
4006  MapMethods::registerMethods(this);
4007 
4008  // register set methods
4009  SetMethods::registerMethods(this);
4010 
4011  // register script methods
4012  ScriptMethods::registerMethods(this);
4013 
4014  // additional script methods
4015  // register application methods
4016  ApplicationMethods::registerMethods(this);
4017 
4018  // register console methods
4019  ConsoleMethods::registerMethods(this);
4020 
4021  // register context methods
4022  ContextMethods::registerMethods(this);
4023 
4024  // register cryptography methods
4025  CryptographyMethods::registerMethods(this);
4026 
4027  // register file system methods
4028  FileSystemMethods::registerMethods(this);
4029 
4030  // register JSON methods
4031  JSONMethods::registerMethods(this);
4032 
4033  // register network methods
4034  NetworkMethods::registerMethods(this);
4035 
4036  // register time methods
4037  TimeMethods::registerMethods(this);
4038 
4039  // register XML methods
4040  XMLMethods::registerMethods(this);
4041 
4042  //
4043  for (const auto dataType: dataTypes) {
4044  if (dataType->isMathDataType() == true) minitScriptMath->registerDataType(dataType);
4045  dataType->registerMethods(this);
4046  }
4047 
4048  // determine operators
4049  for (const auto& [scriptMethodName, scriptMethod]: methods) {
4050  auto methodOperator = scriptMethod->getOperator();
4051  if (methodOperator != OPERATOR_NONE) {
4052  auto methodOperatorString = getOperatorAsString(methodOperator);
4053  auto scriptOperatorIt = operators.find(static_cast<uint8_t>(methodOperator));
4054  if (scriptOperatorIt != operators.end()) {
4055  _Console::printLine("MinitScript::registerMethods(): Operator '" + methodOperatorString + "' already registered for method " + scriptMethod->getMethodName() + "");
4056  continue;
4057  }
4058  operators[static_cast<uint8_t>(methodOperator)] = scriptMethod;
4059  }
4060  }
4061 }
4062 
4063 void MinitScript::registerVariables() {
4064  //
4065  for (const auto& [variableName, variable]: getRootScriptState().variables) delete variable;
4066  getRootScriptState().variables.clear();
4067 
4068  //
4069  minitScriptMath->registerConstants();
4070 
4071  // base script constants
4072  // register base constants
4073  BaseMethods::registerConstants(this);
4074 
4075  // register string constants
4076  StringMethods::registerConstants(this);
4077 
4078  // register byte array constants
4079  ByteArrayMethods::registerConstants(this);
4080 
4081  // register array constants
4082  ArrayMethods::registerConstants(this);
4083 
4084  // register map constants
4085  MapMethods::registerConstants(this);
4086 
4087  // register set constants
4088  SetMethods::registerConstants(this);
4089 
4090  // register script constants
4091  ScriptMethods::registerConstants(this);
4092 
4093  // additional script constants
4094  // register application constants
4095  ApplicationMethods::registerConstants(this);
4096 
4097  // register console constants
4098  ConsoleMethods::registerConstants(this);
4099 
4100  // register context constants
4101  ContextMethods::registerConstants(this);
4102 
4103  // register cryptography constants
4104  CryptographyMethods::registerConstants(this);
4105 
4106  // register file system constants
4107  FileSystemMethods::registerConstants(this);
4108 
4109  // register JSON constants
4110  JSONMethods::registerConstants(this);
4111 
4112  // register network constants
4113  NetworkMethods::registerConstants(this);
4114 
4115  // register time constants
4116  TimeMethods::registerConstants(this);
4117 
4118  // register XML constants
4119  XMLMethods::registerConstants(this);
4120 
4121  //
4122  for (const auto dataType: dataTypes) dataType->registerConstants(this);
4123 }
4124 
4125 void MinitScript::createLamdaFunction(Variable& variable, const vector<string_view>& arguments, const string_view& functionScriptCode, int lineIdx, bool populateThis, const Statement& statement, const string& nameHint) {
4126  // function declaration
4127  auto functionName = string() + "lamda_function_" + (nameHint.empty() == true?"":_StringTools::toLowerCase(nameHint) + "_") + to_string(inlineFunctionIdx++);
4128  auto inlineFunctionScriptCode = "function: " + functionName + "(";
4129  if (populateThis == true) inlineFunctionScriptCode+= "&&$this";
4130  auto argumentIdx = 0;
4131  for (const auto& argument: arguments) {
4132  if (argumentIdx > 0 || populateThis == true) inlineFunctionScriptCode+= ",";
4133  inlineFunctionScriptCode+= argument;
4134  argumentIdx++;
4135  }
4136  inlineFunctionScriptCode+= string() + ")" + "\n";
4137  // function definition
4138  inlineFunctionScriptCode+= string(functionScriptCode);
4139  inlineFunctionScriptCode+= "\n";
4140  inlineFunctionScriptCode+= string() + "end" + "\n";
4141  // store it to be parsed later
4142  // we can reduce line index by function head
4143  // our line counting does not start at 1 here, but at zero
4144  deferredInlineScriptCodes.push_back(make_pair(lineIdx - 2, inlineFunctionScriptCode));
4145  //
4146  variable.setFunctionAssignment(functionName);
4147 }
4148 
4149 void MinitScript::createStacklet(Variable& variable, const string& scopeName, const vector<string_view>& arguments, const string_view& stackletScriptCode, int lineIdx, const Statement& statement) {
4150  // stacklet declaration
4151  auto stackletName = string() + "stacklet_" + to_string(inlineStackletIdx++);
4152  auto inlineStackletScriptCode = "stacklet: " + stackletName + "(" + scopeName + ")" + "\n";
4153  // stacklet definition
4154  inlineStackletScriptCode+= string(stackletScriptCode);
4155  inlineStackletScriptCode+= "\n";
4156  inlineStackletScriptCode+= string() + "end" + "\n";
4157  // store it to be parsed later
4158  // we can reduce line index by function head
4159  // our line counting does not start at 1 here, but at zero
4160  deferredInlineScriptCodes.push_back(make_pair(lineIdx - 2, inlineStackletScriptCode));
4161  //
4162  variable.setStackletAssignment(stackletName);
4163 }
4164 
4165 const MinitScript::Variable MinitScript::initializeArray(const string_view& initializerString, MinitScript* minitScript, int scriptIdx, const Statement& statement) {
4166  Variable variable;
4167  variable.setType(TYPE_ARRAY);
4168  //
4169  auto lineIdx = statement.line;
4170  auto bracketCount = 0;
4171  auto squareBracketCount = 0;
4172  auto curlyBracketCount = 0;
4173  auto quote = '\0';
4174  auto arrayValueStart = string::npos;
4175  auto arrayValueEnd = string::npos;
4176  auto inlineFunctionSignatureStartCandidate = string::npos;
4177  auto inlineFunctionLineIdxCandidate = LINE_NONE;
4178  auto inlineFunctionLineIdx = LINE_NONE;
4179  //
4180  auto lc = '\0';
4181  auto i = 0;
4182  //
4183  auto pushToArray = [&]() -> void {
4184  // array value
4185  if (arrayValueStart != string::npos) {
4186  arrayValueEnd = i - 1;
4187  auto arrayValueLength = arrayValueEnd - arrayValueStart + 1;
4188  if (arrayValueLength > 0) {
4189  auto arrayValueStringView = _StringTools::viewTrim(string_view(&initializerString[arrayValueStart], arrayValueLength));
4190  if (arrayValueStringView.empty() == false) {
4191  Variable arrayValue;
4192  arrayValue.setImplicitTypedValueFromStringView(arrayValueStringView, minitScript, scriptIdx, statement);
4193  variable.pushArrayEntry(arrayValue);
4194  }
4195  }
4196  }
4197  //
4198  arrayValueStart = string::npos;
4199  arrayValueEnd = string::npos;
4200  //
4201  inlineFunctionSignatureStartCandidate = string::npos;
4202  inlineFunctionLineIdxCandidate = LINE_NONE;
4203  inlineFunctionLineIdx = LINE_NONE;
4204  };
4205  //
4206  for (; i < initializerString.size(); i++) {
4207  auto c = initializerString[i];
4208  // newline/line index
4209  if (c == '\n') {
4210  lineIdx++;
4211  // check for comment line
4212  auto comment = false;
4213  for (auto j = i + 1; j < initializerString.size(); j++) {
4214  auto _c = initializerString[j];
4215  // space after newline
4216  if (_Character::isSpace(_c) == true) {
4217  // no op
4218  } else
4219  // comment start
4220  if (_c == '#') {
4221  comment = true;
4222  // iterate until next new line
4223  for (j++; j < initializerString.size(); j++) {
4224  if (initializerString[j] == '\n') break;
4225  }
4226  //
4227  i = j - 1;
4228  break;
4229  } else {
4230  // non hash as first character after new line
4231  break;
4232  }
4233  }
4234  //
4235  if (comment == true) continue;
4236  }
4237  // quotes
4238  if (squareBracketCount == 1 && curlyBracketCount == 0 && (c == '"' || c == '\'') && lc != '\\') {
4239  if (quote == '\0') {
4240  quote = c;
4241  if (arrayValueStart == string::npos) arrayValueStart = i;
4242  } else
4243  if (quote == c) {
4244  quote = '\0';
4245  if (arrayValueEnd == string::npos) arrayValueEnd = i;
4246  }
4247  } else
4248  // no quote
4249  if (quote == '\0') {
4250  // , -> push to array
4251  if (squareBracketCount == 1 && curlyBracketCount == 0 && bracketCount == 0 && c == ',') {
4252  pushToArray();
4253  } else
4254  // possible function call
4255  if (c == '(') {
4256  //
4257  bracketCount++;
4258  //
4259  if (bracketCount == 1) {
4260  inlineFunctionSignatureStartCandidate = i;
4261  inlineFunctionLineIdxCandidate = lineIdx;
4262  }
4263  } else
4264  if (c == ')') {
4265  bracketCount--;
4266  // function assignment
4267  if (inlineFunctionSignatureStartCandidate != string::npos && bracketCount == 0 && arrayValueStart == string::npos) {
4268  arrayValueStart = inlineFunctionSignatureStartCandidate;
4269  inlineFunctionLineIdx = inlineFunctionLineIdxCandidate;
4270  }
4271  //
4272  inlineFunctionSignatureStartCandidate = string::npos;
4273  inlineFunctionLineIdxCandidate = LINE_NONE;
4274  } else
4275  // array initializer
4276  if (c == '[' && curlyBracketCount == 0 && bracketCount == 0) {
4277  // we have a inner array initializer, mark it
4278  if (squareBracketCount == 1) arrayValueStart = i;
4279  // increase square bracket count
4280  squareBracketCount++;
4281  } else
4282  // end of array initializer
4283  if (c == ']' && curlyBracketCount == 0 && bracketCount == 0) {
4284  squareBracketCount--;
4285  // done? push to array
4286  if (squareBracketCount == 0) {
4287  // push to array
4288  pushToArray();
4289  } else
4290  // otherwise push inner array initializer
4291  if (squareBracketCount == 1) {
4292  // parse and push
4293  inlineFunctionSignatureStartCandidate = string::npos;
4294  inlineFunctionLineIdxCandidate = LINE_NONE;
4295  //
4296  if (arrayValueStart != string::npos) {
4297  arrayValueEnd = i;
4298  auto arrayValueLength = arrayValueEnd - arrayValueStart + 1;
4299  if (arrayValueLength > 0) {
4300  auto arrayValueStringView = _StringTools::viewTrim(string_view(&initializerString[arrayValueStart], arrayValueLength));
4301  if (arrayValueStringView.empty() == false) {
4302  auto arrayValue = initializeArray(arrayValueStringView, minitScript, scriptIdx, statement);
4303  variable.pushArrayEntry(arrayValue);
4304  }
4305  }
4306  //
4307  arrayValueStart = string::npos;
4308  arrayValueEnd = string::npos;
4309  }
4310  //
4311  inlineFunctionLineIdx = LINE_NONE;
4312  }
4313  } else
4314  // map/set initializer
4315  if (c == '{' && squareBracketCount == 1 && bracketCount == 0) {
4316  // we have a inner map/set initializer, mark it
4317  if (curlyBracketCount == 0) {
4318  if (arrayValueStart == string::npos) arrayValueStart = i;
4319  }
4320  // increase curly bracket count
4321  curlyBracketCount++;
4322  } else
4323  // end of map/set initializer or inline lamda function
4324  if (c == '}' && squareBracketCount == 1 && bracketCount == 0) {
4325  curlyBracketCount--;
4326  // otherwise push inner array initializer
4327  if (curlyBracketCount == 0) {
4328  // parse and push
4329  inlineFunctionSignatureStartCandidate = string::npos;
4330  inlineFunctionLineIdxCandidate = LINE_NONE;
4331  //
4332  if (arrayValueStart != string::npos) {
4333  arrayValueEnd = i;
4334  auto arrayValueLength = arrayValueEnd - arrayValueStart + 1;
4335  if (arrayValueLength > 0) {
4336  auto arrayValueStringView = _StringTools::viewTrim(string_view(&initializerString[arrayValueStart], arrayValueLength));
4337  if (arrayValueStringView.empty() == false) {
4338  vector<string_view> lamdaFunctionArguments;
4339  string_view lamdaFunctionScriptCode;
4340  int lamdaFunctionLineIdx = inlineFunctionLineIdx;
4341  if (viewIsLamdaFunction(arrayValueStringView, lamdaFunctionArguments, lamdaFunctionScriptCode, lamdaFunctionLineIdx) == true) {
4342  Variable arrayValue;
4343  minitScript->createLamdaFunction(arrayValue, lamdaFunctionArguments, lamdaFunctionScriptCode, lamdaFunctionLineIdx, false, statement);
4344  variable.pushArrayEntry(arrayValue);
4345  } else {
4346  auto arrayValue = initializeMapSet(arrayValueStringView, minitScript, scriptIdx, statement);
4347  variable.pushArrayEntry(arrayValue);
4348  }
4349  }
4350  }
4351  //
4352  arrayValueStart = string::npos;
4353  arrayValueEnd = string::npos;
4354  }
4355  //
4356  inlineFunctionLineIdx = LINE_NONE;
4357  }
4358  } else
4359  // set up argument start
4360  if (squareBracketCount == 1 && curlyBracketCount == 0 && bracketCount == 0 && arrayValueStart == string::npos && c != ' ' && c != '\t' && c != '\n') {
4361  arrayValueStart = i;
4362  }
4363  }
4364  //
4365  lc = lc == '\\' && c == '\\'?'\0':c;
4366  }
4367  //
4368  auto initalizer = make_unique<MinitScript::Variable::Initializer>(string(initializerString), statement, nullptr);
4369  variable.initializerReferenceUnion.initializer->copy(initalizer.get());
4370  //
4371  return variable;
4372 }
4373 
4374 const MinitScript::Variable MinitScript::initializeMapSet(const string_view& initializerString, MinitScript* minitScript, int scriptIdx, const Statement& statement) {
4375  //
4376  Variable variable;
4377  variable.setType(TYPE_MAP);
4378  //
4379  auto subLineIdx = 0;
4380  auto bracketCount = 0;
4381  auto curlyBracketCount = 0;
4382  auto squareBracketCount = 0;
4383  auto quote = '\0';
4384  auto mapKeyStart = string::npos;
4385  auto mapKeyEnd = string::npos;
4386  auto mapValueStart = string::npos;
4387  auto mapValueEnd = string::npos;
4388  enum ParseMode { PARSEMODE_KEY, PARSEMODE_VALUE };
4389  auto parseMode = PARSEMODE_KEY;
4390  auto inlineFunctionSignatureStartCandidate = string::npos;
4391  auto inlineFunctionLineIdxCandidate = LINE_NONE;
4392  auto inlineFunctionLineIdx = LINE_NONE;
4393  auto mapKeyLineIdx = LINE_NONE;
4394  auto hasValues = false;
4395  //
4396  auto i = 0;
4397 
4398  //
4399  auto insertMapKeyValuePair = [&]() -> void {
4400  //
4401  string_view mapKey;
4402  auto dequotedMapKey = false;
4403  // map key
4404  if (mapKeyStart != string::npos && mapKeyEnd != string::npos) {
4405  //
4406  auto mapKeyLength = mapKeyEnd - mapKeyStart + 1;
4407  if (mapKeyLength > 0) {
4408  mapKey = _StringTools::viewTrim(string_view(&initializerString[mapKeyStart], mapKeyLength));
4409  if (viewIsStringLiteral(mapKey) == true) {
4410  mapKey = dequote(mapKey);
4411  dequotedMapKey = true;
4412  }
4413  if (mapKey.empty() == true) mapKey = string_view();
4414  }
4415  }
4416  //
4417  mapKeyStart = string::npos;
4418  mapKeyEnd = string::npos;
4419  // validate map key
4420  if (mapKey.empty() == true) {
4421  // no op
4422  } else
4423  if (viewIsKey(mapKey) == false) {
4424  _Console::printLine(minitScript->getStatementInformation(statement, mapKeyLineIdx) + ": Invalid key name, ignoring map entry: " + string(mapKey));
4425  } else {
4426  auto _private = dequotedMapKey == true?false:viewIsKeyPrivate(mapKey);
4427  if (_private == true) mapKey = viewGetPrivateKey(mapKey);
4428  // map value
4429  if (mapValueStart != string::npos && mapValueEnd != string::npos) {
4430  auto mapValueLength = mapValueEnd - mapValueStart + 1;
4431  //
4432  if (mapValueLength > 0) {
4433  auto mapValueStringView = _StringTools::viewTrim(string_view(&initializerString[mapValueStart], mapValueLength));
4434  if (mapValueStringView.empty() == false) {
4435  //
4436  Variable mapValue;
4437  mapValue.setImplicitTypedValueFromStringView(mapValueStringView, minitScript, scriptIdx, statement);
4438  //
4439  variable.setMapEntry(string(mapKey), mapValue, _private);
4440  //
4441  hasValues = true;
4442  }
4443  }
4444  } else {
4445  //
4446  variable.setMapEntry(string(mapKey), Variable(), _private);
4447  }
4448  }
4449  //
4450  mapKeyLineIdx = LINE_NONE;
4451  //
4452  mapValueStart = string::npos;
4453  mapValueEnd = string::npos;
4454  //
4455  inlineFunctionSignatureStartCandidate = string::npos;
4456  inlineFunctionLineIdxCandidate = LINE_NONE;
4457  inlineFunctionLineIdx = LINE_NONE;
4458  //
4459  parseMode = PARSEMODE_KEY;
4460  };
4461  //
4462  auto lc = '\0';
4463  for (; i < initializerString.size(); i++) {
4464  //
4465  auto c = initializerString[i];
4466  auto nc = i < initializerString.size() - 1?initializerString[i + 1]:'\0';
4467  // newline/line index
4468  if (c == '\n') {
4469  //
4470  subLineIdx++;
4471  // check for comment line
4472  auto comment = false;
4473  for (auto j = i + 1; j < initializerString.size(); j++) {
4474  auto _c = initializerString[j];
4475  // space after newline
4476  if (_Character::isSpace(_c) == true) {
4477  // no op
4478  } else
4479  // comment start
4480  if (_c == '#') {
4481  comment = true;
4482  // iterate until next new line
4483  for (j++; j < initializerString.size(); j++) {
4484  if (initializerString[j] == '\n') break;
4485  }
4486  //
4487  i = j - 1;
4488  break;
4489  } else {
4490  // non hash as first character after new line
4491  break;
4492  }
4493  }
4494  //
4495  if (comment == true) continue;
4496  }
4497  // quotes
4498  if (curlyBracketCount == 1 && squareBracketCount == 0 && (c == '"' || c == '\'') && lc != '\\') {
4499  // we have a new quote here
4500  if (quote == '\0') {
4501  quote = c;
4502  // key?
4503  if (parseMode == PARSEMODE_KEY) {
4504  if (mapKeyStart == string::npos) {
4505  mapKeyStart = i;
4506  mapKeyLineIdx = subLineIdx;
4507  }
4508  } else
4509  // value
4510  if (parseMode == PARSEMODE_VALUE) {
4511  if (mapValueStart == string::npos) mapValueStart = i;
4512  }
4513  } else
4514  // finish the quote
4515  if (quote == c) {
4516  quote = '\0';
4517  }
4518  } else
4519  // no quote
4520  if (quote == '\0') {
4521  // : -> map key separator
4522  if (curlyBracketCount == 1 && squareBracketCount == 0 && bracketCount == 0 && c == ':' && nc != ':' && lc != ':' && lc != '\\') {
4523  //
4524  if (mapKeyStart != string::npos) {
4525  mapKeyEnd = i - 1;
4526  }
4527  //
4528  parseMode = PARSEMODE_VALUE;
4529  //
4530  } else
4531  // , -> insert map
4532  if (curlyBracketCount == 1 && squareBracketCount == 0 && bracketCount == 0 && c == ',') {
4533  // TODO: use parse mode here
4534  if (mapValueStart != string::npos) {
4535  mapValueEnd = i - 1;
4536  } else
4537  if (mapKeyStart != string::npos && mapValueStart == string::npos) {
4538  mapKeyEnd = i - 1;
4539  }
4540  // insert map key value pair
4541  insertMapKeyValuePair();
4542  // nada
4543  } else
4544  // possible function call or inline function as value
4545  if (c == '(') {
4546  //
4547  bracketCount++;
4548  //
4549  if (bracketCount == 1) {
4550  inlineFunctionSignatureStartCandidate = i;
4551  inlineFunctionLineIdxCandidate = statement.line + subLineIdx;
4552  }
4553  } else
4554  if (c == ')') {
4555  bracketCount--;
4556  // function assignment
4557  if (inlineFunctionSignatureStartCandidate != string::npos && bracketCount == 0 && mapValueStart == string::npos) {
4558  mapValueStart = inlineFunctionSignatureStartCandidate;
4559  inlineFunctionLineIdx = inlineFunctionLineIdxCandidate;
4560  }
4561  //
4562  inlineFunctionSignatureStartCandidate = string::npos;
4563  inlineFunctionLineIdxCandidate = LINE_NONE;
4564  } else
4565  // map/set initializer
4566  if (c == '{' && squareBracketCount == 0 && bracketCount == 0) {
4567  // TODO: unexpected character if beeing in key parse mode
4568  // increase square bracket count
4569  curlyBracketCount++;
4570  // we have a inner map/set initializer, mark it
4571  if (curlyBracketCount == 2) {
4572  if (mapValueStart == string::npos) mapValueStart = i;
4573  }
4574  } else
4575  // end of map/set initializer or inline lamda function
4576  if (c == '}' && squareBracketCount == 0 && bracketCount == 0) {
4577  // TODO: unexpected character if beeing in key parse mode
4578  curlyBracketCount--;
4579  // done? insert into map
4580  if (curlyBracketCount == 0) {
4581  // TODO: use parse mode here
4582  // first guess, we have a value to finish
4583  if (mapValueStart != string::npos) {
4584  mapValueEnd = i - 1;
4585  } else
4586  // or a key if parsing a set
4587  if (mapKeyStart != string::npos) {
4588  mapKeyEnd = i - 1;
4589  }
4590  // insert map key value pair
4591  insertMapKeyValuePair();
4592  } else
4593  // otherwise push inner map initializer
4594  if (curlyBracketCount == 1) {
4595  // parse and insert into map
4596  string_view mapKey;
4597  auto dequotedMapKey = false;
4598  // map key
4599  if (mapKeyStart != string::npos) {
4600  if (mapKeyEnd == string::npos) mapKeyEnd = i;
4601  auto mapKeyLength = mapKeyEnd - mapKeyStart + 1;
4602  if (mapKeyLength > 0) mapKey = _StringTools::viewTrim(string_view(&initializerString[mapKeyStart], mapKeyLength));
4603  if (viewIsStringLiteral(mapKey) == true) {
4604  mapKey = dequote(mapKey);
4605  dequotedMapKey = true;
4606  }
4607  }
4608  //
4609  mapKeyStart = string::npos;
4610  mapKeyEnd = string::npos;
4611  // validate map key
4612  if (mapKey.empty() == true || viewIsKey(mapKey) == false) {
4613  _Console::printLine(minitScript->getStatementInformation(statement, mapKeyLineIdx) + ": Invalid key name, ignoring map entry: " + string(mapKey));
4614  } else {
4615  auto _private = dequotedMapKey == true?false:viewIsKeyPrivate(mapKey);
4616  if (_private == true) mapKey = viewGetPrivateKey(mapKey);
4617  //
4618  inlineFunctionSignatureStartCandidate = string::npos;
4619  inlineFunctionLineIdxCandidate = LINE_NONE;
4620  // map value
4621  if (mapValueStart != string::npos) {
4622  mapValueEnd = i;
4623  auto mapValueLength = mapValueEnd - mapValueStart + 1;
4624  if (mapValueLength > 0) {
4625  auto mapValueStringView = _StringTools::viewTrim(string_view(&initializerString[mapValueStart], mapValueLength));
4626  if (mapValueStringView.empty() == false) {
4627  vector<string_view> lamdaFunctionArguments;
4628  string_view lamdaFunctionScriptCode;
4629  int lamdaFunctionLineIdx = inlineFunctionLineIdx;
4630  if (viewIsLamdaFunction(mapValueStringView, lamdaFunctionArguments, lamdaFunctionScriptCode, lamdaFunctionLineIdx) == true) {
4631  Variable mapValue;
4632  minitScript->createLamdaFunction(mapValue, lamdaFunctionArguments, lamdaFunctionScriptCode, lamdaFunctionLineIdx, true, statement, string(mapKey));
4633  variable.setMapEntry(string(mapKey), mapValue, _private);
4634  //
4635  hasValues = true;
4636  } else {
4637  // map/set
4638  auto mapValue = initializeMapSet(mapValueStringView, minitScript, scriptIdx, statement);
4639  variable.setMapEntry(string(mapKey), mapValue, _private);
4640  //
4641  hasValues = true;
4642  }
4643 
4644  }
4645  }
4646  //
4647  mapValueStart = string::npos;
4648  mapValueEnd = string::npos;
4649  }
4650  //
4651  inlineFunctionLineIdx = LINE_NONE;
4652  }
4653  //
4654  parseMode = PARSEMODE_KEY;
4655  mapKeyLineIdx = LINE_NONE;
4656  }
4657  } else
4658  // array initializer
4659  if (c == '[' && curlyBracketCount == 1 && bracketCount == 0) {
4660  // we have a inner array initializer, mark it
4661  if (squareBracketCount == 0) {
4662  if (mapValueStart == string::npos) mapValueStart = i;
4663  }
4664  // increase square bracket count
4665  squareBracketCount++;
4666  } else
4667  // end of array initializer
4668  if (c == ']' && squareBracketCount == 1 && curlyBracketCount == 1 && bracketCount == 0) {
4669  squareBracketCount--;
4670  // otherwise push inner array initializer
4671  if (squareBracketCount == 0 && mapValueStart != string::npos && initializerString[mapValueStart] == '[') {
4672  // parse and insert into map
4673  string_view mapKey;
4674  auto dequotedMapKey = false;
4675  // map key
4676  if (mapKeyStart != string::npos) {
4677  auto mapKeyLength = mapKeyEnd - mapKeyStart + 1;
4678  if (mapKeyLength > 0) mapKey = _StringTools::viewTrim(string_view(&initializerString[mapKeyStart], mapKeyLength));
4679  if (viewIsStringLiteral(mapKey) == true) {
4680  mapKey = dequote(mapKey);
4681  dequotedMapKey = true;
4682  }
4683  }
4684  //
4685  mapKeyStart = string::npos;
4686  mapKeyEnd = string::npos;
4687  // validate map key
4688  if (mapKey.empty() == true || viewIsKey(mapKey) == false) {
4689  _Console::printLine(minitScript->getStatementInformation(statement, mapKeyLineIdx) + ": Invalid key name, ignoring map entry: " + string(mapKey));
4690  } else {
4691  auto _private = dequotedMapKey == true?false:viewIsKeyPrivate(mapKey);
4692  if (_private == true) mapKey = viewGetPrivateKey(mapKey);
4693  //
4694  inlineFunctionSignatureStartCandidate = string::npos;
4695  inlineFunctionLineIdxCandidate = LINE_NONE;
4696  // array value
4697  if (mapValueStart != string::npos) {
4698  mapValueEnd = i;
4699  auto mapValueLength = mapValueEnd - mapValueStart + 1;
4700  if (mapValueLength > 0) {
4701  auto mapValueStringView = _StringTools::viewTrim(string_view(&initializerString[mapValueStart], mapValueLength));
4702  if (mapValueStringView.empty() == false) {
4703  auto mapValue = initializeArray(mapValueStringView, minitScript, scriptIdx, statement);
4704  variable.setMapEntry(string(mapKey), mapValue, _private);
4705  //
4706  hasValues = true;
4707  }
4708  }
4709  //
4710  mapValueStart = string::npos;
4711  mapValueEnd = string::npos;
4712  }
4713  //
4714  inlineFunctionLineIdx = LINE_NONE;
4715  }
4716  //
4717  parseMode = PARSEMODE_KEY;
4718  mapKeyLineIdx = LINE_NONE;
4719  }
4720  } else
4721  // set up map key start
4722  if (curlyBracketCount == 1 && squareBracketCount == 0 && bracketCount == 0 && c != ' ' && c != '\t' && c != '\n') {
4723  if (parseMode == PARSEMODE_KEY && mapKeyStart == string::npos) {
4724  mapKeyStart = i;
4725  mapKeyLineIdx = subLineIdx;
4726  } else
4727  if (parseMode == PARSEMODE_VALUE && mapValueStart == string::npos) {
4728  mapValueStart = i;
4729  }
4730  }
4731  }
4732  //
4733  lc = lc == '\\' && c == '\\'?'\0':c;
4734  }
4735  // convert to set if no values given
4736  if (hasValues == false) {
4737  Variable setVariable;
4738  setVariable.setType(TYPE_SET);
4739  const auto& mapValueReference = variable.getMapValueReference();
4740  for (const auto& [mapVariableKey, mapVariableValue]: mapValueReference) {
4741  setVariable.insertSetKey(mapVariableKey);
4742  }
4743  variable = setVariable;
4744  }
4745  //
4746  auto initalizer = make_unique<MinitScript::Variable::Initializer>(string(initializerString), statement, nullptr);
4747  variable.initializerReferenceUnion.initializer->copy(initalizer.get());
4748  //
4749  return variable;
4750 }
4751 
4752 void MinitScript::Variable::setFunctionCallStatement(const string& initializerStatement, MinitScript* minitScript, int scriptIdx, const Statement& statement) {
4753  setType(TYPE_FUNCTION_CALL);
4754  getStringValueReference().setValue(initializerStatement);
4755  //
4756  Statement initializerScriptStatement(
4757  statement.line,
4758  statement.statementIdx,
4759  initializerStatement,
4760  initializerStatement,
4761  MinitScript::STATEMENTIDX_NONE
4762  );
4763  //
4764  string_view methodName;
4765  vector<ParserArgument> arguments;
4766  string accessObjectMemberStatement;
4767  SyntaxTreeNode* evaluateSyntaxTree = new SyntaxTreeNode();
4768  //
4769  if (minitScript->parseStatement(initializerStatement, methodName, arguments, initializerScriptStatement, accessObjectMemberStatement) == false) {
4770  //
4771  } else
4772  if (minitScript->createStatementSyntaxTree(scriptIdx, methodName, arguments, initializerScriptStatement, *evaluateSyntaxTree) == false) {
4773  //
4774  } else {
4775  getInitializerReference() = new Initializer(initializerStatement, statement, evaluateSyntaxTree);
4776  }
4777 }
4778 
4779 inline MinitScript::Variable* MinitScript::evaluateVariableAccessIntern(MinitScript::Variable* variablePtr, const string& variableStatement, const string& callerMethod, Variable*& parentVariable, int64_t& arrayIdx, string& key, int& setAccessBool, const SubStatement* subStatement, bool expectVariable) {
4780  // get root variable
4781  key.clear();
4782  // no array idx by default
4783  arrayIdx = ARRAYIDX_NONE;
4784  // determine left and right access operator position if there are any
4785  auto accessOperatorLeftIdx = string::npos;
4786  auto accessOperatorRightIdx = string::npos;
4787  if (getVariableAccessOperatorLeftRightIndices(variableStatement, callerMethod, accessOperatorLeftIdx, accessOperatorRightIdx, subStatement) == false) {
4788  return nullptr;
4789  }
4790  // access operator, if we have any, evaluate the array index
4791  auto haveAccessOperator = accessOperatorLeftIdx != string::npos && accessOperatorRightIdx != string::npos;
4792  if (haveAccessOperator == true &&
4793  evaluateAccess(variableStatement, callerMethod, accessOperatorLeftIdx, accessOperatorRightIdx, arrayIdx, key, subStatement) == false) {
4794  return nullptr;
4795  }
4796  //
4797  // get pointer to children variable
4798  if (haveAccessOperator == false) {
4799  //
4800  return variablePtr;
4801  } else {
4802  // resolve first parsed access pattern and repeat until resolved
4803  while (haveAccessOperator == true) {
4804  //
4805  bool privateParentScope = variablePtr->isPrivateScope();
4806  // map key access
4807  if (key.empty() == false) {
4808  if (variablePtr->getType() == TYPE_MAP) {
4809  //
4810  auto& mapValueReference = variablePtr->getMapValueReference();
4811  // key
4812  auto mapIt = mapValueReference.find(key);
4813  if (mapIt != mapValueReference.end()) {
4814  //
4815  parentVariable = variablePtr;
4816  //
4817  variablePtr = mapIt->second;
4818  //
4819  if (variablePtr->isPrivate() == true && privateParentScope == false) {
4820  //
4821  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Private variable: " + variableStatement + ": access not allowed from outside of object");
4822  //
4823  parentVariable = nullptr;
4824  return nullptr;
4825  }
4826  } else {
4827  if (expectVariable == true) {
4828  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": key not found: '" + key + "'");
4829  }
4830  // we have our parent
4831  parentVariable = variablePtr;
4832  //
4833  return nullptr;
4834  }
4835  } else
4836  if (variablePtr->getType() == TYPE_SET) {
4837  //
4838  auto& setValueReference = variablePtr->getSetValueReference();
4839  // key
4840  auto setIt = setValueReference.find(key);
4841  if (setIt != setValueReference.end()) {
4842  //
4843  setAccessBool = SETACCESSBOOL_TRUE;
4844  //
4845  parentVariable = variablePtr;
4846  } else {
4847  //
4848  setAccessBool = SETACCESSBOOL_FALSE;
4849  // we have our parent
4850  parentVariable = variablePtr;
4851  //
4852  return nullptr;
4853  }
4854  } else {
4855  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": map/set access operator, but variable is not of type map/set");
4856  return nullptr;
4857  }
4858  } else
4859  if (variablePtr->getType() == TYPE_ARRAY) {
4860  // otherwise array
4861  if (arrayIdx == ARRAYIDX_ADD) {
4862  // we have our parent
4863  parentVariable = variablePtr;
4864  //
4865  return nullptr;
4866  } else
4867  if (arrayIdx >= ARRAYIDX_FIRST && arrayIdx < variablePtr->getArrayValueReference().size()) {
4868  //
4869  parentVariable = variablePtr;
4870  //
4871  variablePtr = variablePtr->getArrayValueReference()[arrayIdx];
4872  } else {
4873  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": index out of bounds: 0 <= " + to_string(arrayIdx) + " < " + to_string(variablePtr->getArrayValueReference().size()));
4874  return nullptr;
4875  }
4876  } else {
4877  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": access operator, expected array, but got: " + variablePtr->getValueAsString());
4878  return nullptr;
4879  }
4880 
4881  //
4882  auto accessOperatorStartIdx = accessOperatorRightIdx;
4883  accessOperatorLeftIdx = string::npos;
4884  accessOperatorRightIdx = string::npos;
4885  if (getVariableAccessOperatorLeftRightIndices(variableStatement, callerMethod, accessOperatorLeftIdx, accessOperatorRightIdx, subStatement, accessOperatorStartIdx) == false) {
4886  // fail
4887  return nullptr;
4888  }
4889 
4890  // do we have a next array access next to previous one?
4891  haveAccessOperator = accessOperatorLeftIdx != string::npos && accessOperatorRightIdx != string::npos;
4892  if (haveAccessOperator == false) {
4893  return variablePtr;
4894  } else {
4895  // yep, evaluate it
4896  if (evaluateAccess(variableStatement, callerMethod, accessOperatorLeftIdx, accessOperatorRightIdx, arrayIdx, key, subStatement) == false) {
4897  return nullptr;
4898  }
4899  }
4900 
4901  //
4902  if (parentVariable != nullptr && parentVariable->isPrivateScope() == true) privateParentScope = true;
4903  }
4904  //
4905  return variablePtr;
4906  }
4907 }
4908 
4909 inline void MinitScript::setVariableInternal(const string& variableStatement, Variable* parentVariable, Variable* variablePtr, int64_t arrayIdx, const string& key, const Variable& variable, const SubStatement* subStatement, bool createReference) {
4910  // common case
4911  if (variablePtr != nullptr) {
4912  if (variablePtr->isConstant() == false) {
4913  if (createReference == true) {
4914  variablePtr->setReference(&variable);
4915  } else {
4916  variablePtr->setValue(variable);
4917  }
4918  } else {
4919  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Constant: " + variableStatement + ": assignment of constant is not allowed");
4920  }
4921  return;
4922  } else
4923  // array add operator
4924  if (key.empty() == false) {
4925  if (parentVariable == nullptr) {
4926  // no op, we complain somewhere else, lol
4927  } else
4928  // all checks passed, push to map
4929  if (parentVariable->getType() == MinitScript::TYPE_MAP) {
4930  // check if our parent is not a const variable
4931  if (parentVariable->isConstant() == false) {
4932  parentVariable->setMapEntry(key, createReference == false?Variable::createNonReferenceVariable(&variable):Variable::createReferenceVariable(&variable));
4933  } else {
4934  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Constant: " + variableStatement + ": assignment of constant is not allowed");
4935  }
4936  } else
4937  if (parentVariable->getType() == MinitScript::TYPE_SET) {
4938  // check if our parent is not a const variable
4939  if (parentVariable->isConstant() == false) {
4940  bool booleanValue;
4941  if (variable.getBooleanValue(booleanValue, false) == true) {
4942  if (booleanValue == true) {
4943  parentVariable->insertSetKey(key);
4944  } else {
4945  parentVariable->removeSetKey(key);
4946  }
4947  } else {
4948  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": set access operator: expected boolean variable to remove/insert key in set, but got " + variable.getTypeAsString());
4949  }
4950  } else {
4951  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Constant: " + variableStatement + ": assignment of constant is not allowed");
4952  }
4953  } else {
4954  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": map/set access operator: expected map/set, but got " + parentVariable->getTypeAsString() + ": '" + key + "'");
4955  }
4956  //
4957  return;
4958  } else
4959  if (arrayIdx == ARRAYIDX_ADD) {
4960  if (parentVariable == nullptr) {
4961  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": [] array push operator without array");
4962  } else
4963  if (parentVariable->getType() == MinitScript::TYPE_ARRAY) {
4964  // check if our parent is not a const variable
4965  if (parentVariable->isConstant() == false) {
4966  // all checks passed, push variable to array
4967  parentVariable->pushArrayEntry(createReference == false?Variable::createNonReferenceVariable(&variable):Variable::createReferenceVariable(&variable));
4968  } else {
4969  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Constant: " + variableStatement + ": assignment of constant is not allowed");
4970  }
4971  } else {
4972  _Console::printLine((subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": [] array push operator: expected array, but got " + parentVariable->getTypeAsString());
4973  }
4974  //
4975  return;
4976  }
4977 }
4978 
4979 inline bool MinitScript::evaluateInternal(const string& statement, const string& executableStatement, Variable& returnValue, bool pushScriptState) {
4980  Statement evaluateStatement(
4981  LINE_NONE,
4982  0,
4983  "internal.script.evaluate(" + statement + ")",
4984  "internal.script.evaluate(" + executableStatement + ")",
4985  STATEMENTIDX_NONE
4986  );
4987  auto scriptEvaluateStatement = "internal.script.evaluate(" + executableStatement + ")";
4988  //
4989  string_view methodName;
4990  vector<ParserArgument> arguments;
4991  string accessObjectMemberStatement;
4992  SyntaxTreeNode evaluateSyntaxTree;
4993  if (parseStatement(scriptEvaluateStatement, methodName, arguments, evaluateStatement, accessObjectMemberStatement) == false) {
4994  return false;
4995  } else
4996  if (createStatementSyntaxTree(SCRIPTIDX_NONE, methodName, arguments, evaluateStatement, evaluateSyntaxTree) == false) {
4997  return false;
4998  } else {
4999  //
5000  if (pushScriptState == true) {
5001  this->pushScriptState();
5002  resetScriptExecutationState(SCRIPTIDX_NONE, STATEMACHINESTATE_NEXT_STATEMENT);
5003  }
5004  getScriptState().running = true;
5005  //
5006  returnValue.setValue(
5007  executeStatement(
5008  evaluateSyntaxTree,
5009  evaluateStatement
5010  )
5011  );
5012  //
5013  if (pushScriptState == true) popScriptState();
5014  //
5015  return true;
5016  }
5017 }
5018 
5019 inline const MinitScript::Variable MinitScript::initializeVariable(const Variable& variable) {
5020  switch (variable.getType()) {
5021  case TYPE_ARRAY:
5022  {
5023  Variable arrayVariable;
5024  //
5025  arrayVariable.setType(TYPE_ARRAY);
5026  auto arrayPointer = variable.getArrayPointer();
5027  if (arrayPointer == nullptr) break;
5028  for (const auto arrayEntry: *arrayPointer) {
5029  arrayVariable.pushArrayEntry(initializeVariable(*arrayEntry));
5030  }
5031  //
5032  return arrayVariable;
5033  }
5034  case TYPE_MAP:
5035  {
5036  Variable mapVariable;
5037  //
5038  auto mapPointer = variable.getMapPointer();
5039  if (mapPointer == nullptr) break;
5040  for (const auto& [mapKey, mapValue]: *mapPointer) {
5041  mapVariable.setMapEntry(mapKey, initializeVariable(*mapValue));
5042  }
5043  //
5044  return mapVariable;
5045  }
5046  case TYPE_FUNCTION_CALL:
5047  {
5048  return executeStatement(
5049  *variable.getInitializer()->getSyntaxTree(),
5050  variable.getInitializer()->getStatement()
5051  );
5052  }
5053  default: break;
5054  }
5055  //
5056  return variable;
5057 }
5058 
5059 inline bool MinitScript::viewIsKey(const string_view& candidate) {
5060  if (candidate.empty() == true) return false;
5061  auto i = 0;
5062  if (candidate[i] == '-') i++;
5063  for (; i < candidate.size(); i++) {
5064  auto c = candidate[i];
5065  if (_Character::isAlphaNumeric(c) == false && c != '_') return false;
5066  }
5067  return true;
5068 }
5069 
5070 inline bool MinitScript::viewIsKeyPrivate(const string_view& candidate) {
5071  if (candidate.empty() == true) return false;
5072  if (candidate[0] == '-') return true;
5073  return false;
5074 }
5075 
5076 inline const string_view MinitScript::viewGetPrivateKey(const string_view& candidate) {
5077  return string_view(&candidate.data()[1], candidate.size() - 1);
5078 }
5079 
5080 inline bool MinitScript::getVariableAccessOperatorLeftRightIndices(const string& variableStatement, const string& callerMethod, string::size_type& accessOperatorLeftIdx, string::size_type& accessOperatorRightIdx, const SubStatement* subStatement, int startIdx) {
5081  accessOperatorLeftIdx = string::npos;
5082  accessOperatorRightIdx = string::npos;
5083  auto haveKey = false;
5084  auto squareBracketsCount = 0;
5085  // improve me!
5086  if (startIdx > 0) {
5087  haveKey = variableStatement[startIdx - 1] == '.';
5088  if (haveKey == true) accessOperatorLeftIdx = startIdx - 1;
5089  } else
5090  if (startIdx == 0) {
5091  //
5092  auto lc = '\0';
5093  for (auto i = 0; i < variableStatement.length(); i++) {
5094  auto c = variableStatement[i];
5095  if (lc == ':' && c == ':') {
5096  startIdx = i + 1;
5097  break;
5098  }
5099  //
5100  lc = c;
5101  }
5102  }
5103  for (auto i = startIdx; i < variableStatement.length(); i++) {
5104  auto c = variableStatement[i];
5105  if (haveKey == true) {
5106  if (c == '.') {
5107  //
5108  accessOperatorRightIdx = i;
5109  //
5110  return true;
5111  } else
5112  if (c == '[') {
5113  //
5114  accessOperatorRightIdx = i;
5115  //
5116  return true;
5117  }
5118  if (c == ']') {
5119  _Console::printLine("MinitScript::" + callerMethod + "(): " + (subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": unexpected char: ']'");
5120  return false;
5121  }
5122  } else
5123  if (c == '.' && squareBracketsCount == 0) {
5124  haveKey = true;
5125  accessOperatorLeftIdx = i;
5126  } else
5127  if (c == '[') {
5128  if (squareBracketsCount == 0) accessOperatorLeftIdx = i;
5129  squareBracketsCount++;
5130  } else
5131  if (c == ']') {
5132  squareBracketsCount--;
5133  if (squareBracketsCount == 0) {
5134  //
5135  accessOperatorRightIdx = i + 1;
5136  //
5137  return true;
5138  } else
5139  if (squareBracketsCount < 0) {
5140  _Console::printLine("MinitScript::" + callerMethod + "(): " + (subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": unexpected char: ']'");
5141  return false;
5142  }
5143  }
5144  }
5145  //
5146  if (haveKey == true) accessOperatorRightIdx = variableStatement.size();
5147  //
5148  return true;
5149 }
5150 
5151 inline bool MinitScript::evaluateAccess(const string& variableStatement, const string& callerMethod, string::size_type& arrayAccessOperatorLeftIdx, string::size_type& arrayAccessOperatorRightIdx, int64_t& arrayIdx, string& key, const SubStatement* subStatement) {
5152  key.clear();
5153  arrayIdx = ARRAYIDX_NONE;
5154  // check for dot access
5155  if (variableStatement.data()[arrayAccessOperatorLeftIdx] == '.') {
5156  key = string(_StringTools::viewTrim(string_view(&variableStatement.data()[arrayAccessOperatorLeftIdx + 1], arrayAccessOperatorRightIdx - arrayAccessOperatorLeftIdx - 1)));
5157  return true;
5158  }
5159  // evaluate array index
5160  auto arrayIdxExpressionStringView = _StringTools::viewTrim(string_view(&variableStatement.data()[arrayAccessOperatorLeftIdx + 1], arrayAccessOperatorRightIdx - arrayAccessOperatorLeftIdx - 2));
5161  if (arrayIdxExpressionStringView.empty() == false) {
5162  // integer first for performance
5163  if (_Integer::viewIs(arrayIdxExpressionStringView) == true) {
5164  arrayIdx = _Integer::viewParse(arrayIdxExpressionStringView);
5165  } else {
5166  // TODO: as evaluate statement we also might need the expression that had not yet a preprocessor run for error messages and such
5167  Variable statementReturnValue;
5168  auto evaluateStatement = string(arrayIdxExpressionStringView);
5169  if (evaluateInternal(evaluateStatement, evaluateStatement, statementReturnValue, false) == false || statementReturnValue.getIntegerValue(arrayIdx, false) == false) {
5170  _Console::printLine("MinitScript::" + callerMethod + "(): " + (subStatement != nullptr?getSubStatementInformation(*subStatement):scriptFileName) + ": Variable: " + variableStatement + ": failed to evaluate expression: '" + string(arrayIdxExpressionStringView) + "'");
5171  return false;
5172  }
5173  }
5174  } else {
5175  arrayIdx = ARRAYIDX_ADD;
5176  }
5177  //
5178  return true;
5179 }
5180 
5181 void MinitScript::setConstantInternal(Variable& variable) {
5182  variable.setConstant();
5183  switch (variable.getType()) {
5184  case TYPE_ARRAY:
5185  {
5186  auto arrayPointer = variable.getArrayPointer();
5187  if (arrayPointer == nullptr) break;
5188  for (const auto arrayEntry: *arrayPointer) {
5189  setConstant(*arrayEntry);
5190  }
5191  //
5192  break;
5193  }
5194  case TYPE_MAP:
5195  {
5196  auto mapPointer = variable.getMapPointer();
5197  if (mapPointer == nullptr) break;
5198  for (const auto& [mapKey, mapValue]: *mapPointer) {
5199  setConstant(*mapValue);
5200  }
5201  //
5202  break;
5203  }
5204  default:
5205  break;
5206  }
5207 }
5208 
5209 void MinitScript::unsetConstantInternal(Variable& variable) {
5210  if (variable.isReference() == true) {
5211  _Console::printLine("MinitScript::unsetConstantInternal(): Can not unset constant if reference variable is given.");
5212  return;
5213  }
5214  variable.unsetConstant();
5215  switch (variable.getType()) {
5216  case TYPE_ARRAY:
5217  {
5218  auto arrayPointer = variable.getArrayPointer();
5219  if (arrayPointer == nullptr) break;
5220  for (const auto arrayEntry: *arrayPointer) {
5221  unsetConstant(*arrayEntry);
5222  }
5223  //
5224  break;
5225  }
5226  case TYPE_MAP:
5227  {
5228  auto mapPointer = variable.getMapPointer();
5229  if (mapPointer == nullptr) break;
5230  for (const auto& [mapKey, mapValue]: *mapPointer) {
5231  unsetConstant(*mapValue);
5232  }
5233  //
5234  break;
5235  }
5236  default:
5237  break;
5238  }
5239 }
5240 
5241 void MinitScript::garbageCollection() {
5242  auto garbageCollectionDataTypesIndicesCopy = garbageCollectionDataTypesIndices;
5243  for (auto index: garbageCollectionDataTypesIndicesCopy) {
5244  auto& garbageCollectionDataType = garbageCollectionDataTypes[index];
5245  garbageCollectionDataType.dataType->garbageCollection(garbageCollectionDataType.context);
5246  }
5247 }
Standard math functions.
Definition: Math.h:19
static auto abs(auto value)
Returns absolute value.
Definition: Math.h:63
MinitScript script application methods.
MinitScript script array methods.
Definition: ArrayMethods.h:12
MinitScript script base methods.
Definition: BaseMethods.h:13
MinitScript script byte array methods.
MinitScript script console methods.
MinitScript script context methods.
MinitScript script cryptography methods.
MinitScript script file system methods.
MinitScript script JSON methods.
Definition: JSONMethods.h:12
MinitScript script map methods.
Definition: MapMethods.h:12
MinitScript math methods.
Definition: MathMethods.h:16
void setType(MinitScript::VariableType type)
Set type.
Definition: MinitScript.h:281
virtual const vector< string > & getContextFunctions()
Definition: MinitScript.h:2786
const vector< ArgumentType > & getArgumentTypes() const
Definition: MinitScript.h:2712
void copy(Initializer *initializer)
Copy from initializer.
Definition: MinitScript.h:480
InitializerReferenceUnion initializerReferenceUnion
Definition: MinitScript.h:609
bool getIntegerValue(int64_t &value, bool optional=false) const
Get integer value from given variable.
Definition: MinitScript.h:1436
void setImplicitTypedValueFromStringView(const string_view &value, MinitScript *minitScript, int scriptIdx, const Statement &statement)
Set implicit typed value given by value string.
Definition: MinitScript.h:2191
bool getStackletValue(string &stacklet, int &scriptIdx, bool optional=false) const
Get stacklet values from given variable.
Definition: MinitScript.h:1528
void setFunctionAssignment(const string &function, int scriptIdx=MinitScript::SCRIPTIDX_NONE)
Set function assignment from given value into variable.
Definition: MinitScript.h:2154
const unordered_map< string, Variable * > * getMapPointer() const
Definition: MinitScript.h:1924
static const string & getTypeAsString(VariableType type)
Returns given variable type as string.
Definition: MinitScript.h:2326
unordered_map< string, Variable * > & getMapValueReference()
Definition: MinitScript.h:800
void setMapEntry(const string &key, const Variable &value, bool _private=false)
Set entry in map with given key.
Definition: MinitScript.h:1993
vector< Variable * > & getArrayValueReference()
Definition: MinitScript.h:786
void insertSetKey(const string &key)
Insert given key in set.
Definition: MinitScript.h:2099
void pushArrayEntry(const Variable &value)
Push entry to array.
Definition: MinitScript.h:1892
void setStackletAssignment(const string &stacklet, int scriptIdx=MinitScript::SCRIPTIDX_NONE)
Set stacklet assignment from given value into variable.
Definition: MinitScript.h:2166
unordered_set< string > & getSetValueReference()
Definition: MinitScript.h:814
const vector< Variable * > * getArrayPointer() const
Definition: MinitScript.h:1839
bool getBooleanValue(bool &value, bool optional=false) const
Get boolean value from given variable.
Definition: MinitScript.h:1405
Initializer * getInitializer() const
Return initializer.
Definition: MinitScript.h:1376
const string getValueAsString(bool formatted=false, bool jsonCompatible=false, int depth=0) const
Print string representation of variable.
Definition: MinitScript.h:2404
void setReference(const Variable *variable)
Set reference.
Definition: MinitScript.h:891
void setType(VariableType newType)
Set type.
Definition: MinitScript.h:1253
void removeSetKey(const string &key)
Remove key in set with given key.
Definition: MinitScript.h:2108
bool getFunctionValue(string &function, int &scriptIdx, bool optional=false) const
Get function values from given variable.
Definition: MinitScript.h:1506
void setValue(const Variable &variable)
Set value from given variable into variable.
Definition: MinitScript.h:1618
bool parseStatement(const string_view &executableStatement, string_view &methodName, vector< ParserArgument > &arguments, const Statement &statement, string &accessObjectMemberStatement)
Parse a statement.
void setScriptStateState(int state)
Set script state machine state.
Definition: MinitScript.h:3416
const string getStatementInformation(const Statement &statement, int subLineIdx=-1)
Return statement information.
Definition: MinitScript.h:4556
void createLamdaFunction(Variable &variable, const vector< string_view > &arguments, const string_view &functionScriptCode, int lineIdx, bool populateThis, const Statement &statement, const string &nameHint=string())
void resetScriptExecutationState(int scriptIdx, StateMachineStateId stateMachineState)
Reset script execution state.
Definition: MinitScript.h:3335
bool createStatementSyntaxTree(int scriptIdx, const string_view &methodName, const vector< ParserArgument > &arguments, const Statement &statement, SyntaxTreeNode &syntaxTree, int subLineIdx=0)
Create statement syntax tree.
virtual int determineScriptIdxToStart()
Determine script index to start.
void executeNextStatement()
Execute next statement.
MinitScript script network methods.
MinitScript script script methods.
Definition: ScriptMethods.h:12
MinitScript script set methods.
Definition: SetMethods.h:12
MinitScript script string methods.
Definition: StringMethods.h:12
MinitScript script time methods.
Definition: TimeMethods.h:12
MinitScript script xml methods.
Definition: XMLMethods.h:12
static const string getContentAsString(const string &pathName, const string &fileName)
Get content as string.
Definition: FileSystem.cpp:121
Base class for threads.
Definition: Thread.h:20
static void sleep(const uint64_t milliseconds)
sleeps current thread for given time in milliseconds
Definition: Thread.h:48
static bool isSpace(uint32_t character)
Returns if character is a white space.
Definition: Character.h:50
static bool isAlphaNumeric(uint32_t character)
Returns if character is alpha numeric.
Definition: Character.h:42
static void printLine()
Print newline to console.
Definition: Console.cpp:66
static void initialize()
Initialize.
Definition: Console.cpp:35
static void print(const string_view &str)
Print given string without trainling newline to console.
Definition: Console.cpp:54
static bool viewIs(const string_view &str)
Check if given string is a integer string.
Definition: Integer.cpp:39
static int viewParse(const string_view &str)
Parse integer.
Definition: Integer.cpp:59
SHA256 hash class.
Definition: SHA256.h:16
static const string encode(const string &decodedString)
Encodes an string to SHA256 string.
Definition: SHA256.h:23
static const string_view viewTrim(const string_view &str)
Trim string.
Definition: StringTools.cpp:82
static const bool startsWith(const string &str, const string &prefix)
Checks if string starts with prefix.
Definition: StringTools.h:29
static const string substring(const string &str, int64_t beginIndex)
Returns substring of given string from begin index.
Definition: StringTools.h:203
static const string toLowerCase(const string &str)
Transform string to lower case.
Definition: StringTools.cpp:94
static const string replace(const string &str, const char what, const char by, int64_t beginIndex=0)
Replace char with another char.
Definition: StringTools.cpp:33
static const bool endsWith(const string &str, const string &suffix)
Checks if string ends with suffix.
Definition: StringTools.h:49
static const string padLeft(const string &str, const string &by, int64_t toSize)
Pad a string left.
Definition: StringTools.h:318
static int64_t lastIndexOf(const string &str, const char what, int64_t endIndex=string::npos)
Finds last index of given character.
Definition: StringTools.h:138
static const string trim(const string &str)
Trim string.
Definition: StringTools.cpp:57
static const string generate(const string &what, int64_t count=1)
Generate a string.
Definition: StringTools.h:356
static bool regexMatch(const string &str, const string &pattern, smatch *matches=nullptr)
Check if pattern matches whole string.
static int64_t indexOf(const string &str, char what, int64_t beginIndex=0)
Finds first index of given character.
Definition: StringTools.h:94
static const vector< string > tokenize(const string &str, const string &delimiters, bool emptyTokens=false)
Tokenize.
Time utility class.
Definition: Time.h:20
static int64_t getCurrentMillis()
Retrieve current time in milliseconds.
Definition: Time.h:27
#define MINITSCRIPT_DATA
Definition: minitscript.h:22
unordered_map< string, Variable * > variables
Definition: MinitScript.h:3109
void setScriptIdx(uint64_t scriptIdx)
Set function/stacklet script index.
Definition: MinitScript.h:2885