Files
WLED_MM_Infinity/usermods/customeffects/arti.h
2023-01-07 15:10:21 +01:00

2721 lines
98 KiB
C++

/*
@title Arduino Real Time Interpreter (ARTI)
@file arti.h
@date 20220818
@author Ewoud Wijma
@Copyright (c) 2023 Ewoud Wijma
@repo https://github.com/ewoudwijma/ARTI
@remarks
- #define ARDUINOJSON_DEFAULT_NESTING_LIMIT 100 //set this in ArduinoJson!!!, currently not necessary...
- IF UPDATING THIS FILE IN THE WLED REPO, SEND A PULL REQUEST TO https://github.com/ewoudwijma/ARTI AS WELL!!!
@later
- Code improvememt
- See 'for some weird reason this causes a crash on esp32'
- check why column/lineno not correct
- Definition improvements
- support string (e.g. for print)
- add integer and string stacks
- print every x seconds (to use it in loops. e.g. to show free memory)
- reserved words (ext functions and variables cannot be used as variables)
- check on return values
- arrays (indices) for varref
- WLED improvements
- rename program to sketch?
@progress
- SetPixelColor without colorwheel
- extend errorOccurred and add warnings (continue) next to errors (stop). Include stack full/empty
- WLED: *arti in SEGENV.data: not working well as change mode will free(data)
- move code from interpreter to analyzer to speed up interpreting
@done?
@done
- save log after first run of loop to get runtime errors included (or 30 iterations to also capture any stack overflows)
@todo
- check why statement is not 'shrinked'
- make default work in js (remove default as we have now load template)
- add PI
- color_fade_pulse because /pixelCount instead of ledCount should not crash
*/
#pragma once
#define ARTI_SERIAL 1
#define ARTI_FILE 2
#if ARTI_PLATFORM == ARTI_ARDUINO //defined in arti_definition.h e.g. arti_wled.h!
#include "../../wled00/wled.h"
#include "../../wled00/src/dependencies/json/ArduinoJson-v6.h"
File logFile;
#define ARTI_ERRORWARNING 1 //shows lexer, parser, analyzer and interpreter errors
// #define ARTI_DEBUG 1
// #define ARTI_ANDBG 1
// #define ARTI_RUNLOG 1 //if set on arduino this will create massive amounts of output (as ran in a loop)
#define ARTI_MEMORY 1 //to do analyses of memory usage, trace memoryleaks (works only on arduino)
#define ARTI_PRINT 1 //will show the printf calls
const char spaces[51] PROGMEM = " ";
#define FREE_SIZE esp_get_free_heap_size()
// #define OPTIMIZED_TREE 1
#else //embedded
#include "dependencies/ArduinoJson-recent.h"
FILE * logFile; // FILE needed to use in fprintf (std stream does not work)
#define ARTI_ERRORWARNING 1
#define ARTI_DEBUG 1
#define ARTI_ANDBG 1
#define ARTI_RUNLOG 1
#define ARTI_MEMORY 1
#define ARTI_PRINT 1
#include <math.h>
#include <iostream>
#include <fstream>
#include <sstream>
const char spaces[51] = " ";
#define FREE_SIZE (unsigned int)0
// #define OPTIMIZED_TREE 1
#endif
bool logToFile = true; //print output to file (e.g. default.wled.log)
uint32_t frameCounter = 0; //tbd move to class if more instances run
void artiPrintf(char const * format, ...)
{
va_list argp;
va_start(argp, format);
if (!logToFile)
{
vprintf(format, argp);
}
else
{
#if ARTI_PLATFORM == ARTI_ARDUINO
// rocket science here! As logfile.printf causes crashes/wrong output we create our own printf here
// logFile.printf(format, argp);
for (int i = 0; i < strlen(format); i++)
{
if (format[i] == '%')
{
switch (format[i+1])
{
case 's':
logFile.print(va_arg(argp, const char *));
break;
case 'u':
logFile.print(va_arg(argp, unsigned int));
break;
case 'c':
logFile.print((char)va_arg(argp, int));
break;
case 'f':
logFile.print(va_arg(argp, double));
break;
case '%':
logFile.print("%"); // in case of %%
break;
default:
va_arg(argp, int);
// logFile.print(x);
logFile.print(format[i]);
logFile.print(format[i+1]);
}
i++;
}
else
{
logFile.print(format[i]);
}
}
#else
vfprintf(logFile, format, argp);
#endif
}
va_end(argp);
}
//add millis function for non arduino
#if ARTI_PLATFORM != ARTI_ARDUINO
uint32_t millis()
{
return std::chrono::system_clock::now().time_since_epoch().count();
}
#endif
#ifdef ARTI_DEBUG
#define DEBUG_ARTI(...) artiPrintf(__VA_ARGS__)
#else
#define DEBUG_ARTI(...)
#endif
#ifdef ARTI_ANDBG
#define ANDBG_ARTI(...) artiPrintf(__VA_ARGS__)
#else
#define ANDBG_ARTI(...)
#endif
#ifdef ARTI_RUNLOG
#define RUNLOG_ARTI(...) artiPrintf(__VA_ARGS__)
#else
#define RUNLOG_ARTI(...)
#endif
#ifdef ARTI_PRINT
#define PRINT_ARTI(...) artiPrintf(__VA_ARGS__)
#else
#define PRINT_ARTI(...)
#endif
#ifdef ARTI_ERRORWARNING
#define ERROR_ARTI(...) artiPrintf(__VA_ARGS__)
#define WARNING_ARTI(...) artiPrintf(__VA_ARGS__)
#else
#define ERROR_ARTI(...)
#define WARNING_ARTI(...)
#endif
#ifdef ARTI_MEMORY
#define MEMORY_ARTI(...) artiPrintf(__VA_ARGS__)
#else
#define MEMORY_ARTI(...)
#endif
#define charLength 30
#define fileNameLength 50
#define arrayLength 30
#define floatNull -32768
const char * stringOrEmpty(const char *charS) {
if (charS == nullptr)
return "";
else
return charS;
}
//define strupr as only supported in windows toolchain
char* strupr(char* s)
{
char* tmp = s;
for (;*tmp;++tmp) {
*tmp = toupper((unsigned char) *tmp);
}
return s;
}
enum Tokens
{
F_integerConstant,
F_realConstant,
F_plus,
F_minus,
F_multiplication,
F_division,
F_modulo,
F_bitShiftLeft,
F_bitShiftRight,
F_equal,
F_notEqual,
F_lessThen,
F_lessThenOrEqual,
F_greaterThen,
F_greaterThenOrEqual,
F_and,
F_or,
F_plusplus,
F_minmin,
F_NoToken = 255
};
const char * tokenToString(uint8_t key)
{
switch (key) {
case F_integerConstant:
return "integer";
break;
case F_realConstant:
return "real";
break;
case F_plus:
return "+";
break;
case F_minus:
return "-";
break;
case F_multiplication:
return "*";
break;
case F_division:
return "/";
break;
case F_modulo:
return "%";
break;
case F_bitShiftLeft:
return "<<";
break;
case F_bitShiftRight:
return ">>";
break;
case F_equal:
return "==";
break;
case F_notEqual:
return "!=";
break;
case F_lessThen:
return "<";
break;
case F_lessThenOrEqual:
return "<=";
break;
case F_greaterThen:
return ">";
break;
case F_greaterThenOrEqual:
return ">=";
break;
case F_and:
return "&&";
break;
case F_or:
return "||";
break;
case F_plusplus:
return "++";
break;
case F_minmin:
return "--";
break;
}
return "unknown key";
}
uint8_t stringToToken(const char * token, const char * value)
{
if (strcmp(token, "INTEGER_CONST") == 0)
return F_integerConstant;
else if (strcmp(token, "REAL_CONST") == 0)
return F_realConstant;
else if (strcmp(value, "+") == 0)
return F_plus;
else if (strcmp(value, "-") == 0)
return F_minus;
else if (strcmp(value, "*") == 0)
return F_multiplication;
else if (strcmp(value, "/") == 0)
return F_division;
else if (strcmp(value, "%") == 0)
return F_modulo;
else if (strcmp(value, "<<") == 0)
return F_bitShiftLeft;
else if (strcmp(value, ">>") == 0)
return F_bitShiftRight;
else if (strcmp(value, "==") == 0)
return F_equal;
else if (strcmp(value, "!=") == 0)
return F_notEqual;
else if (strcmp(value, ">") == 0)
return F_greaterThen;
else if (strcmp(value, ">=") == 0)
return F_greaterThenOrEqual;
else if (strcmp(value, "<") == 0)
return F_lessThen;
else if (strcmp(value, "<=") == 0)
return F_lessThenOrEqual;
else if (strcmp(value, "&&") == 0)
return F_and;
else if (strcmp(value, "||") == 0)
return F_or;
else if (strcmp(value, "+=") == 0)
return F_plus;
else if (strcmp(value, "-=") == 0)
return F_minus;
else if (strcmp(value, "*=") == 0)
return F_multiplication;
else if (strcmp(value, "/=") == 0)
return F_division;
else if (strcmp(value, "++") == 0)
return F_plusplus;
else if (strcmp(value, "--") == 0)
return F_minmin;
else
return F_NoToken;
}
enum Nodes
{
F_Program,
F_Function,
F_Call,
F_VarDef,
F_Assign,
F_Formal,
F_VarRef,
F_For,
F_If,
F_Cex,
F_Expr,
F_Term,
#ifdef OPTIMIZED_TREE
F_Statement,
F_Indices,
F_Formals,
F_Factor,
F_Block,
F_Actuals,
F_Increment,
F_AssignOperator,
#endif
F_NoNode = 255
};
//Tokens
// ID
//Optimizer
// level
// index
// external
// block
// formals
// actuals
// increment
// assignoperator
// type (not used yet in wled)
//expr
//indices
const char * nodeToString(uint8_t key)
{
switch (key) {
case F_Program:
return "program";
case F_Function:
return "function";
case F_Call:
return "call";
case F_VarDef:
return "variable";
case F_Assign:
return "assign";
case F_Formal:
return "formal";
case F_VarRef:
return "varref";
case F_For:
return "for";
case F_If:
return "if";
case F_Cex:
return "cex";
case F_Expr:
return "expr";
case F_Term:
return "term";
#ifdef OPTIMIZED_TREEX
case F_Statement:
return "statement";
case F_Indices:
return "indices";
case F_Formals:
return "formals";
case F_Factor:
return "factor";
case F_Block:
return "block";
case F_Actuals:
return "actuals";
#endif
}
return "unknown key";
}
uint8_t stringToNode(const char * node)
{
if (strcmp(node, "program") == 0)
return F_Program;
else if (strcmp(node, "function") == 0)
return F_Function;
else if (strcmp(node, "call") == 0)
return F_Call;
else if (strcmp(node, "variable") == 0)
return F_VarDef;
else if (strcmp(node, "assign") == 0)
return F_Assign;
else if (strcmp(node, "formal") == 0)
return F_Formal;
else if (strcmp(node, "varref") == 0)
return F_VarRef;
else if (strcmp(node, "for") == 0)
return F_For;
else if (strcmp(node, "if") == 0)
return F_If;
else if (strcmp(node, "cex") == 0)
return F_Cex;
else if (strcmp(node, "expr") == 0)
return F_Expr;
else if (strcmp(node, "term") == 0)
return F_Term;
#ifdef OPTIMIZED_TREE
else if (strcmp(node, "statement") == 0)
return F_Statement;
else if (strcmp(node, "indices") == 0)
return F_Indices;
else if (strcmp(node, "formals") == 0)
return F_Formals;
else if (strcmp(node, "factor") == 0)
return F_Factor;
else if (strcmp(node, "block") == 0)
return F_Block;
else if (strcmp(node, "actuals") == 0)
return F_Actuals;
else if (strcmp(node, "increment") == 0)
return F_Increment;
else if (strcmp(node, "assignoperator") == 0)
return F_AssignOperator;
#endif
return F_NoNode;
}
bool errorOccurred = false;
struct Token {
uint16_t lineno;
uint16_t column;
char type[charLength];
char value[charLength];
};
struct LexerPosition {
uint16_t pos;
char current_char;
uint16_t lineno;
uint16_t column;
char type[charLength];
char value[charLength];
};
#define nrOfPositions 20
class Lexer {
private:
public:
const char * text;
uint16_t pos;
char current_char;
uint16_t lineno;
uint16_t column;
JsonObject definitionJson;
Token current_token;
LexerPosition positions[nrOfPositions]; //should be array of pointers but for some reason get seg fault (because a struct and not a class...)
uint8_t positions_index = 0;
Lexer(const char * programText, JsonObject definitionJson) {
this->text = programText;
this->definitionJson = definitionJson;
this->pos = 0;
this->current_char = this->text[this->pos];
this->lineno = 1;
this->column = 1;
}
~Lexer() {
DEBUG_ARTI("Destruct Lexer\n");
}
void advance() {
if (this->current_char == '\n')
{
this->lineno += 1;
this->column = 0;
}
this->pos++;
if (this->pos > strlen(this->text) - 1)
this->current_char = -1;
else
{
this->current_char = this->text[this->pos];
this->column++;
}
}
void skip_whitespace()
{
while (this->current_char != -1 && isspace(this->current_char))
this->advance();
}
void skip_comment(const char * endTokens)
{
while (strncmp(this->text + this->pos, endTokens, strlen(endTokens)) != 0)
this->advance();
for (int i=0; i<strlen(endTokens); i++)
this->advance();
}
void number()
{
current_token.lineno = this->lineno;
current_token.column = this->column;
strcpy(current_token.type, "");
strcpy(current_token.value, "");
char result[charLength] = "";
while (this->current_char != -1 && isdigit(this->current_char))
{
result[strlen(result)] = this->current_char;
this->advance();
}
if (this->current_char == '.')
{
result[strlen(result)] = this->current_char;
this->advance();
while (this->current_char != -1 && isdigit(this->current_char))
{
result[strlen(result)] = this->current_char;
this->advance();
}
result[strlen(result)] = '\0';
strcpy(current_token.type, "REAL_CONST");
strcpy(current_token.value, result);
}
else
{
result[strlen(result)] = '\0';
strcpy(current_token.type, "INTEGER_CONST");
strcpy(current_token.value, result);
}
}
void id()
{
current_token.lineno = this->lineno;
current_token.column = this->column;
strcpy(current_token.type, "");
strcpy(current_token.value, "");
char result[charLength] = "";
while (this->current_char != -1 && (isalnum(this->current_char) || this->current_char == '_'))
{
result[strlen(result)] = this->current_char;
this->advance();
}
result[strlen(result)] = '\0';
char resultUpper[charLength];
strcpy(resultUpper, result);
strupr(resultUpper);
if (definitionJson["TOKENS"].containsKey(resultUpper))
{
strcpy(current_token.type, definitionJson["TOKENS"][resultUpper]);
strcpy(current_token.value, resultUpper);
}
else
{
strcpy(current_token.type, "ID");
strcpy(current_token.value, result);
}
}
void get_next_token()
{
current_token.lineno = this->lineno;
current_token.column = this->column;
strcpy(current_token.type, "");
strcpy(current_token.value, "");
if (errorOccurred) return;
while (this->current_char != -1 && this->pos <= strlen(this->text) - 1 && !errorOccurred)
{
if (isspace(this->current_char)) {
this->skip_whitespace();
continue;
}
if (strncmp(this->text + this->pos, "/*", 2) == 0)
{
this->advance();
skip_comment("*/");
continue;
}
if (strncmp(this->text + this->pos, "//", 2) == 0)
{
this->advance();
skip_comment("\n");
continue;
}
if (isalpha(this->current_char))
{
this->id();
return;
}
if (isdigit(this->current_char) || (this->current_char == '.' && isdigit(this->text[this->pos+1])))
{
this->number();
return;
}
// findLongestMatchingToken
char token_type[charLength] = "";
char token_value[charLength] = "";
uint8_t longestTokenLength = 0;
for (JsonPair tokenPair: definitionJson["TOKENS"].as<JsonObject>()) {
const char * value = tokenPair.value();
char currentValue[charLength+1];
strncpy(currentValue, this->text + this->pos, charLength);
currentValue[strlen(value)] = '\0';
if (strcmp(value, currentValue) == 0 && strlen(value) > longestTokenLength) {
strcpy(token_type, tokenPair.key().c_str());
strcpy(token_value, value);
longestTokenLength = strlen(value);
}
}
if (strcmp(token_type, "") != 0 && strcmp(token_value, "") != 0)
{
strcpy(current_token.type, token_type);
strcpy(current_token.value, token_value);
for (int i=0; i<strlen(token_value); i++)
this->advance();
return;
}
else {
ERROR_ARTI("Lexer error on %c line %u col %u\n", this->current_char, this->lineno, this->column);
errorOccurred = true;
}
}
} //get_next_token
void eat(const char * token_type) {
// DEBUG_ARTI("try to eat %s %s\n", lexer->current_token.type, token_type);
if (strcmp(current_token.type, token_type) == 0) {
get_next_token();
// DEBUG_ARTI("eating %s -> %s %s\n", token_type, lexer->current_token.type, lexer->current_token.value);
}
else {
ERROR_ARTI("Lexer Error: Unexpected token %s %s\n", current_token.type, current_token.value);
errorOccurred = true;
}
}
void push_position() {
if (positions_index < nrOfPositions) {
positions[positions_index].pos = this->pos;
positions[positions_index].current_char = this->current_char;
positions[positions_index].lineno = this->lineno;
positions[positions_index].column = this->column;
strcpy(positions[positions_index].type, current_token.type);
strcpy(positions[positions_index].value, current_token.value);
positions_index++;
}
else
ERROR_ARTI("not enough positions %u\n", nrOfPositions);
}
void pop_position() {
if (positions_index > 0) {
positions_index--;
this->pos = positions[positions_index].pos;
this->current_char = positions[positions_index].current_char;
this->lineno = positions[positions_index].lineno;
this->column = positions[positions_index].column;
strcpy(current_token.type, positions[positions_index].type);
strcpy(current_token.value, positions[positions_index].value);
}
else
ERROR_ARTI("no positions saved\n");
}
}; //Lexer
#define ResultFail 0
#define ResultStop 2
#define ResultContinue 1
class ScopedSymbolTable; //forward declaration
class Symbol {
private:
public:
uint8_t symbol_type;
char name[charLength];
uint8_t type;
uint8_t scope_level;
uint8_t scope_index;
ScopedSymbolTable* scope = nullptr;
ScopedSymbolTable* function_scope = nullptr; //used to find the formal parameters in the scope of a function node
JsonVariant block;
Symbol(uint8_t symbol_type, const char * name, uint8_t type = 9) {
this->symbol_type = symbol_type;
strcpy(this->name, name);
this->type = type;
this->scope_level = 0;
}
~Symbol() {
MEMORY_ARTI("Destruct Symbol %s (%u)\n", name, FREE_SIZE);
}
}; //Symbol
#define nrOfSymbolsPerScope 30
#define nrOfChildScope 10 //add checks
class ScopedSymbolTable {
private:
public:
Symbol* symbols[nrOfSymbolsPerScope];
uint8_t symbolsIndex = 0;
uint8_t nrOfFormals = 0;
char scope_name[charLength];
uint8_t scope_level;
ScopedSymbolTable *enclosing_scope;
ScopedSymbolTable *child_scopes[nrOfChildScope];
uint8_t child_scopesIndex = 0;
ScopedSymbolTable(const char * scope_name, int scope_level, ScopedSymbolTable *enclosing_scope = nullptr) {
strcpy(this->scope_name, scope_name);
this->scope_level = scope_level;
this->enclosing_scope = enclosing_scope;
}
~ScopedSymbolTable() {
for (uint8_t i=0; i<child_scopesIndex; i++) {
delete child_scopes[i]; child_scopes[i] = nullptr;
}
for (uint8_t i=0; i<symbolsIndex; i++) {
delete symbols[i]; symbols[i] = nullptr;
}
MEMORY_ARTI("Destruct ScopedSymbolTable %s (%u)\n", scope_name, FREE_SIZE);
}
void init_builtins() {
// this->insert(BuiltinTypeSymbol('INTEGER'));
// this->insert(BuiltinTypeSymbol('REAL'));
}
void insert(Symbol* symbol)
{
#ifdef _SHOULD_LOG_SCOPE
ANDBG_ARTI("Log scope Insert %s\n", symbol->name.c_str());
#endif
symbol->scope_level = this->scope_level;
symbol->scope = this;
symbol->scope_index = symbolsIndex;
if (symbolsIndex < nrOfSymbolsPerScope)
this->symbols[symbolsIndex++] = symbol;
else
ERROR_ARTI("ScopedSymbolTable %s symbols full (%d)", scope_name, nrOfSymbolsPerScope);
}
Symbol* lookup(const char * name, bool current_scope_only=false)
{
for (uint8_t i=0; i<symbolsIndex; i++) {
if (strcmp(symbols[i]->name, name) == 0)
return symbols[i];
}
if (current_scope_only)
return nullptr;
// # recursively go up the chain and lookup the name;
if (this->enclosing_scope != nullptr)
return this->enclosing_scope->lookup(name);
return nullptr;
} //lookup
}; //ScopedSymbolTable
#define nrOfVariables 20
class ActivationRecord
{
private:
public:
char name[charLength];
char type[charLength];
int nesting_level;
// char charMembers[charLength][nrOfVariables];
float floatMembers[nrOfVariables];
char lastSet[charLength];
uint8_t lastSetIndex;
ActivationRecord(const char * name, const char * type, int nesting_level)
{
strcpy(this->name, name);
strcpy(this->type, type);
this->nesting_level = nesting_level;
}
~ActivationRecord()
{
RUNLOG_ARTI("Destruct activation record %s\n", name);
}
// void set(uint8_t index, const char * value)
// {
// lastSetIndex = index;
// strcpy(charMembers[index], value);
// }
void set(uint8_t index, float value)
{
lastSetIndex = index;
floatMembers[index] = value;
}
// const char * getChar(uint8_t index)
// {
// return charMembers[index];
// }
float getFloat(uint8_t index)
{
return floatMembers[index];
}
}; //ActivationRecord
#define nrOfRecords 20
class CallStack {
public:
ActivationRecord* records[nrOfRecords];
uint8_t recordsCounter = 0;
CallStack() {
}
~CallStack()
{
RUNLOG_ARTI("Destruct callstack\n");
}
void push(ActivationRecord* ar)
{
if (recordsCounter < nrOfRecords)
{
// RUNLOG_ARTI("%s\n", "Push ", ar->name);
this->records[recordsCounter++] = ar;
}
else
{
errorOccurred = true;
ERROR_ARTI("no space left in callstack\n");
}
}
ActivationRecord* pop()
{
if (recordsCounter > 0)
{
// RUNLOG_ARTI("%s\n", "Pop ", this->peek()->name);
return this->records[recordsCounter--];
}
else
{
ERROR_ARTI("no ar left on callstack\n");
return nullptr;
}
}
ActivationRecord* peek()
{
return this->records[recordsCounter-1];
}
}; //CallStack
class ValueStack
{
private:
public:
// char charStack[arrayLength][charLength]; //currently only floatStack used.
float floatStack[arrayLength];
uint8_t stack_index = 0;
ValueStack()
{
}
~ValueStack()
{
RUNLOG_ARTI("Destruct valueStack\n");
}
// void push(const char * value) {
// if (stack_index >= arrayLength)
// ERROR_ARTI("Push charStack full %u of %u\n", stack_index, arrayLength);
// else if (value == nullptr) {
// strcpy(charStack[stack_index++], "empty");
// ERROR_ARTI("Push null pointer on float stack\n");
// }
// else
// // RUNLOG_ARTI("calc push %s %s\n", key, value);
// strcpy(charStack[stack_index++], value);
// }
void push(float value)
{
if (stack_index >= arrayLength)
{
ERROR_ARTI("Push floatStack full (check functions with result assigned) %u\n", arrayLength);
errorOccurred = true;
}
else if (value == floatNull)
ERROR_ARTI("Push null value on float stack\n");
else
// RUNLOG_ARTI("calc push %s %s\n", key, value);
floatStack[stack_index++] = value;
}
// const char * peekChar() {
// // RUNLOG_ARTI("Calc Peek %s\n", charStack[stack_index-1]);
// return charStack[stack_index-1];
// }
float peekFloat()
{
// RUNLOG_ARTI("Calc Peek %s\n", floatStack[stack_index-1]);
return floatStack[stack_index-1];
}
// const char * popChar() {
// if (stack_index>0) {
// stack_index--;
// return charStack[stack_index];
// }
// else {
// ERROR_ARTI("Pop value stack empty\n");
// errorOccurred = true;
// // RUNLOG_ARTI("Calc Pop %s\n", charStack[stack_index]);
// return "novalue";
// }
// }
float popFloat()
{
if (stack_index>0)
{
stack_index--;
return floatStack[stack_index];
}
else
{
ERROR_ARTI("Pop floatStack empty\n");
// RUNLOG_ARTI("Calc Pop %s\n", floatStack[stack_index]);
errorOccurred = true;
return -1;
}
}
}; //ValueStack
#define programTextSize 5000
class ARTI {
private:
Lexer *lexer = nullptr;
PSRAMDynamicJsonDocument *definitionJsonDoc = nullptr;
PSRAMDynamicJsonDocument *parseTreeJsonDoc = nullptr;
JsonObject definitionJson;
JsonVariant parseTreeJson;
ScopedSymbolTable *global_scope = nullptr;
CallStack *callStack = nullptr;
ValueStack *valueStack = nullptr;
uint8_t stages = 5; //for debugging: 0:parseFile, 1:Lexer, 2:parse, 3:optimize, 4:analyze, 5:interpret should be 5 if no debugging
char logFileName[fileNameLength];
uint32_t startMillis;
public:
ARTI()
{
// MEMORY_ARTI("new Arti < %u\n", FREE_SIZE); //logfile not open here
}
~ARTI()
{
MEMORY_ARTI("Destruct ARTI\n");
}
//defined in arti_definition.h e.g. arti_wled.h!
float arti_external_function(uint8_t function, float par1 = floatNull, float par2 = floatNull, float par3 = floatNull, float par4 = floatNull, float par5 = floatNull);
float arti_get_external_variable(uint8_t variable, float par1 = floatNull, float par2 = floatNull, float par3 = floatNull);
void arti_set_external_variable(float value, uint8_t variable, float par1 = floatNull, float par2 = floatNull, float par3 = floatNull);
bool loop();
uint8_t parse(JsonVariant parseTree, const char * node_name, char operatorx, JsonVariant expression, uint8_t depth = 0)
{
if (depth > 50)
{
ERROR_ARTI("Error: Parse recursion level too deep at %s (%u)\n", parseTree.as<std::string>().c_str(), depth);
errorOccurred = true;
}
if (errorOccurred) return ResultFail;
uint8_t result = ResultContinue;
uint8_t resultChild = ResultContinue;
if (expression.is<JsonArray>()) //should always be the case
{
for (JsonVariant expressionElement: expression.as<JsonArray>()) //e.g. ["PROGRAM","ID","block"]
{
const char * nextNode_name = node_name; //e.g. "program":
JsonVariant nextExpression = expressionElement; // e.g. block
JsonVariant nextParseTree = parseTree;
JsonVariant nodeExpression = lexer->definitionJson[expressionElement.as<const char *>()];
if (!nodeExpression.isNull()) //is expressionElement a Node e.g. "block" : ["LCURL",{"*": ["statement"]},"RCURL"]
{
nextNode_name = expressionElement; //e.g. block
nextExpression = nodeExpression; // e.g. ["LCURL",{"*": ["statement"]},"RCURL"]
// DEBUG_ARTI("%s %s %u\n", spaces+50-depth, nextNode_name, depth); //, parseTree.as<std::string>().c_str()
if (parseTree.is<JsonArray>())
{
parseTree[parseTree.size()][nextNode_name]["connect"] = "array";
nextParseTree = parseTree[parseTree.size()-1]; //nextparsetree is last element in the array (which is always an object)
}
else //no list, create object
{
if (parseTree[node_name].isNull()) //no object yet
parseTree[node_name]["connect"] = "object"; //make the connection, new object item
nextParseTree = parseTree[node_name];
}
}
if (operatorx == '|')
lexer->push_position();
if (nextExpression.is<JsonObject>()) // e.g. {"?":["LPAREN","formals*","RPAREN"]}
{
JsonObject::iterator objectIterator = nextExpression.as<JsonObject>().begin();
char objectOperator = objectIterator->key().c_str()[0];
JsonVariant objectElement = objectIterator->value();
if (objectElement.is<JsonArray>())
{
if (objectOperator == '*' || objectOperator == '+')
{
nextParseTree[nextNode_name]["*"][0] = "multiple"; // * is another object in the list of objects
nextParseTree = nextParseTree[nextNode_name]["*"];
}
//and: see 'is array'
if (objectOperator == '|')
{
resultChild = parse(nextParseTree, nextNode_name, objectOperator, objectElement, depth + 1);
if (resultChild != ResultFail) resultChild = ResultContinue;
}
else
{
uint8_t resultChild2 = ResultContinue;
uint8_t counter = 0;
while (resultChild2 == ResultContinue)
{
resultChild2 = parse(nextParseTree, nextNode_name, objectOperator, objectElement, depth + 1); //no assign to result as optional
if (objectOperator == '?') { //zero or one iteration, also continue if parse not continue
resultChild2 = ResultContinue;
break;
}
else if (objectOperator == '+') { //one or more iterations, stop if first parse not continue
if (counter == 0) {
if (resultChild2 != ResultContinue)
break;
}
else
{
if (resultChild2 != ResultContinue)
{
resultChild2 = ResultContinue; //always continue
break;
}
}
}
else if (objectOperator == '*') //zero or more iterations, stop if parse not continue
{
if (resultChild2 != ResultContinue)
{
resultChild2 = ResultContinue; //always continue
break;
}
}
else
{
ERROR_ARTI("%s Programming error: undefined %c %s\n", spaces+50-depth, objectOperator, objectElement.as<std::string>().c_str());
resultChild2 = ResultFail;
}
counter++;
} //while
resultChild = resultChild2;
} //not or
} // element is array
else
ERROR_ARTI("%s Definition error: should be an array %s %c %s\n", spaces+50-depth, stringOrEmpty(nextNode_name), operatorx, objectElement.as<std::string>().c_str());
}
else if (nextExpression.is<JsonArray>()) // e.g. ["LPAREN", "expr*", "RPAREN"]
{
resultChild = parse(nextParseTree, nextNode_name, '&', nextExpression, depth + 1); // every array element starts with '&' (operatorx is for result of all elements of array)
}
else if (lexer->definitionJson["TOKENS"].containsKey(nextExpression.as<const char *>())) // token e.g. "ID"
{
const char * token_type = nextExpression;
if (strcmp(lexer->current_token.type, token_type) == 0)
{
DEBUG_ARTI("%s %s %s", spaces+50-depth, lexer->current_token.type, lexer->current_token.value);
//only add 'semantic tokens'
if (strcmp(lexer->current_token.type, "ID") != 0 && strcmp(lexer->current_token.type, "INTEGER_CONST") != 0 && strcmp(lexer->current_token.type, "REAL_CONST") != 0 &&
strcmp(lexer->current_token.type, "INTEGER") != 0 && strcmp(lexer->current_token.type, "REAL") != 0 &&
strcmp(lexer->current_token.value, "+") != 0 && strcmp(lexer->current_token.value, "-") != 0 && strcmp(lexer->current_token.value, "*") != 0 && strcmp(lexer->current_token.value, "/") != 0 && strcmp(lexer->current_token.value, "%") != 0 &&
strcmp(lexer->current_token.value, "+=") != 0 && strcmp(lexer->current_token.value, "-=") != 0 && strcmp(lexer->current_token.value, "*=") != 0 && strcmp(lexer->current_token.value, "/=") != 0 &&
strcmp(lexer->current_token.value, "<<") != 0 && strcmp(lexer->current_token.value, ">>") != 0 &&
strcmp(lexer->current_token.value, "==") != 0 && strcmp(lexer->current_token.value, "!=") != 0 &&
strcmp(lexer->current_token.value, "&&") != 0 && strcmp(lexer->current_token.value, "||") != 0 &&
strcmp(lexer->current_token.value, ">") != 0 && strcmp(lexer->current_token.value, ">=") != 0 && strcmp(lexer->current_token.value, "<") != 0 && strcmp(lexer->current_token.value, "<=") != 0 &&
strcmp(lexer->current_token.value, "++") != 0 && strcmp(lexer->current_token.value, "--") != 0
)
{}
else
{
if (nextParseTree.is<JsonArray>())
nextParseTree.as<JsonArray>()[nextParseTree.size()][lexer->current_token.type] = lexer->current_token.value; //add in last element of array
else
nextParseTree[nextNode_name][lexer->current_token.type] = lexer->current_token.value;
}
lexer->eat(token_type);
DEBUG_ARTI(" -> [%s %s] %d\n", lexer->current_token.type, lexer->current_token.value, depth);
resultChild = ResultContinue;
}
else //deadend
resultChild = ResultFail;
} // if token
else //expressionElement is not a node, not a token, not an array and not an object
{
if (lexer->definitionJson.containsKey(nextExpression.as<const char *>()))
ERROR_ARTI("%s Programming error: %s not a node, token, array or object in %s\n", spaces+50-depth, nextExpression.as<std::string>().c_str(), stringOrEmpty(nextNode_name));
else
ERROR_ARTI("%s Definition error: \"%s\": \"%s\" node should be embedded in array\n", spaces+50-depth, stringOrEmpty(nextNode_name), nextExpression.as<std::string>().c_str());
} //nextExpression is not a token
if (!nodeExpression.isNull()) //if node
{
if (nextParseTree.containsKey("connect"))
nextParseTree.remove("connect"); //remove connector
// for values which are not parsed deeper. e.g. : {"formal": {"ID": "z"}}
for (JsonPair parseTreePair : nextParseTree.as<JsonObject>())
{
JsonVariant value = parseTreePair.value();
if (value.containsKey("connect"))
value.remove("connect"); //remove connector
}
if (resultChild == ResultFail) { //remove result of parse
nextParseTree.remove(nextNode_name); //remove the failed stuff
// DEBUG_ARTI("%s fail %s\n", spaces+50-depth, nextNode_name);
}
else //success
{
DEBUG_ARTI("%s found %s\n", spaces+50-depth, nextNode_name);//, nextParseTree.as<std::string>().c_str());
//parseTree optimization moved to optimize function
// if (nextParseTree.is<JsonObject>())
// {
// // optimize(nextParseTree, depth);
// JsonObject innerObject = nextParseTree[nextNode_name].as<JsonObject>();
// JsonObject::iterator begin = innerObject.begin();
// if (fnextParseTree.size() == 1 && nextParseTree[nextNode_name].size() == 1 && lexer->definitionJson.containsKey(nextNode_name) && lexer->definitionJson.containsKey(nextNode_name) && lexer->definitionJson.containsKey(begin->key()))
// {
// // JsonObject nextParseTreeObject = nextParseTree.as<JsonObject>();
// DEBUG_ARTI("%s found %s:%s\n", spaces+50-depth, node_name, parseTree.as<std::string>().c_str());
// DEBUG_ARTI("%s found replace %s by %s %s\n", spaces+50-depth, nextNode_name, begin->key().c_str(), nextParseTree.as<std::string>().c_str());
// DEBUG_ARTI("%s expression %s\n", spaces+50-depth, expression.as<std::string>().c_str());
// DEBUG_ARTI("%s found %s\n", spaces+50-depth, nextParseTree[nextNode_name].as<std::string>().c_str());
// nextParseTree.remove(nextNode_name);
// // char temp[charLength];
// // strcpy(temp, nextNode_name);
// // strcat(temp, "-");
// // strcat(temp, begin->key().c_str());
// nextParseTree[begin->key()] = begin->value();
// DEBUG_ARTI("%s found %s:%s\n", spaces+50-depth, node_name, parseTree.as<std::string>().c_str());
// }
// }
// else
// DEBUG_ARTI("%s no jsonobject??? %s\n", spaces+50-depth, parseTree.as<std::string>().c_str());
}
} // if node
//determine result of expressionElement
if (operatorx == '|') {
if (resultChild == ResultFail) {//if fail, go back and try another
// result = ResultContinue;
lexer->pop_position();
}
else {
result = ResultStop; //Stop or continue is enough for an or
lexer->positions_index--;
}
}
else {
if (resultChild != ResultContinue) //for and, ?, + and *; each result should continue
result = ResultFail;
}
if (result != ResultContinue) //if no reason to continue then stop
break;
} //for expressionElement
if (operatorx == '|')
{
if (result != ResultStop) //still looking but nothing to look for
result = ResultFail;
}
}
else { //should never happen
ERROR_ARTI("%s Programming error: no array %s %c %s\n", spaces+50-depth, stringOrEmpty(node_name), operatorx, expression.as<std::string>().c_str());
}
return result;
} //parse
bool analyze(JsonVariant parseTree, const char * treeElement = nullptr, ScopedSymbolTable* current_scope = nullptr, uint8_t depth = 0)
{
// ANDBG_ARTI("%s Depth %u %s\n", spaces+50-depth, depth, parseTree.as<std::string>().c_str());
if (depth > 24) //otherwise stack canary errors on Arduino (value determined after testing, should be revisited)
{
ERROR_ARTI("Error: Analyze recursion level too deep at %s (%u)\n", parseTree.as<std::string>().c_str(), depth);
errorOccurred = true;
}
if (errorOccurred) return false;
if (parseTree.is<JsonObject>())
{
for (JsonPair parseTreePair : parseTree.as<JsonObject>())
{
const char * key = parseTreePair.key().c_str();
JsonVariant value = parseTreePair.value();
if (treeElement == nullptr || strcmp(treeElement, key) == 0 ) //in case there are more elements in the object and you want to analyze only one
{
bool visitedAlready = false;
if (strcmp(key, "*") == 0 ) // multiple
{
}
else if (strcmp(key, "token") == 0) // do nothing with added tokens
visitedAlready = true;
else if (this->definitionJson["TOKENS"].containsKey(key)) // if token
{
const char * valueStr = value;
if (strcmp(key, "INTEGER_CONST") == 0)
parseTree["token"] = F_integerConstant;
else if (strcmp(key, "REAL_CONST") == 0)
parseTree["token"] = F_realConstant;
else if (strcmp(valueStr, "+") == 0)
parseTree["token"] = F_plus;
else if (strcmp(valueStr, "-") == 0)
parseTree["token"] = F_minus;
else if (strcmp(valueStr, "*") == 0)
parseTree["token"] = F_multiplication;
else if (strcmp(valueStr, "/") == 0)
parseTree["token"] = F_division;
else if (strcmp(valueStr, "%") == 0)
parseTree["token"] = F_modulo;
else if (strcmp(valueStr, "<<") == 0)
parseTree["token"] = F_bitShiftLeft;
else if (strcmp(valueStr, ">>") == 0)
parseTree["token"] = F_bitShiftRight;
else if (strcmp(valueStr, "==") == 0)
parseTree["token"] = F_equal;
else if (strcmp(valueStr, "!=") == 0)
parseTree["token"] = F_notEqual;
else if (strcmp(valueStr, ">") == 0)
parseTree["token"] = F_greaterThen;
else if (strcmp(valueStr, ">=") == 0)
parseTree["token"] = F_greaterThenOrEqual;
else if (strcmp(valueStr, "<") == 0)
parseTree["token"] = F_lessThen;
else if (strcmp(valueStr, "<=") == 0)
parseTree["token"] = F_lessThenOrEqual;
else if (strcmp(valueStr, "&&") == 0)
parseTree["token"] = F_and;
else if (strcmp(valueStr, "||") == 0)
parseTree["token"] = F_or;
else
ERROR_ARTI("%s Programming error: token not defined as operator %s %s (%u)\n", spaces+50-depth, key, value.as<std::string>().c_str(), depth);
visitedAlready = true;
}
else //if key is node_name
{
uint8_t node = stringToNode(key);
switch (node)
{
case F_Program:
{
const char * program_name = value["ID"];
global_scope = new ScopedSymbolTable(program_name, 1, nullptr); //current_scope
ANDBG_ARTI("%s Program %s %u %u\n", spaces+50-depth, global_scope->scope_name, global_scope->scope_level, global_scope->symbolsIndex);
if (value["ID"].isNull()) {
ERROR_ARTI("program name null\n");
errorOccurred = true;
}
if (value["block"].isNull()) {
ERROR_ARTI("%s Program %s: no block in parseTree\n", spaces+50-depth, program_name);
errorOccurred = true;
}
else {
analyze(value["block"], nullptr, global_scope, depth + 1);
}
#ifdef ARTI_DEBUG
for (uint8_t i=0; i<global_scope->symbolsIndex; i++) {
Symbol* symbol = global_scope->symbols[i];
ANDBG_ARTI("%s %u %s %s.%s of %u (%u)\n", spaces+50-depth, i, nodeToString(symbol->symbol_type), global_scope->scope_name, symbol->name, symbol->type, symbol->scope_level);
}
#endif
visitedAlready = true;
break;
}
case F_Function:
{
//find the function name (so we must know this is a function...)
const char * function_name = value["ID"];
Symbol* function_symbol = new Symbol(node, function_name);
current_scope->insert(function_symbol);
ANDBG_ARTI("%s Function %s.%s\n", spaces+50-depth, current_scope->scope_name, function_name);
ScopedSymbolTable* function_scope = new ScopedSymbolTable(function_name, current_scope->scope_level + 1, current_scope);
if (current_scope->child_scopesIndex < nrOfChildScope)
current_scope->child_scopes[current_scope->child_scopesIndex++] = function_scope;
else
ERROR_ARTI("ScopedSymbolTable %s childs full (%d)", current_scope->scope_name, nrOfChildScope);
function_symbol->function_scope = function_scope;
if (value.containsKey("formals"))
analyze(value["formals"], nullptr, function_scope, depth + 1);
function_scope->nrOfFormals = function_scope->symbolsIndex;
if (value["block"].isNull())
ERROR_ARTI("%s Function %s: no block in parseTree\n", spaces+50-depth, function_name);
else
analyze(value["block"], nullptr, function_scope, depth + 1);
#ifdef ARTI_DEBUG
for (uint8_t i=0; i<function_scope->symbolsIndex; i++) {
Symbol* symbol = function_scope->symbols[i];
ANDBG_ARTI("%s %u %s %s.%s of %u (%u)\n", spaces+50-depth, i, nodeToString(symbol->symbol_type), function_scope->scope_name, symbol->name, symbol->type, symbol->scope_level);
}
#endif
visitedAlready = true;
break;
}
case F_VarDef:
case F_Formal:
case F_Assign:
case F_VarRef:
{
JsonObject variable_value;
if (node == F_Assign)
variable_value = value["varref"];
else
variable_value = value;
const char * variable_name;
variable_name = variable_value["ID"];
if (variable_value.containsKey("indices"))
analyze(variable_value, "indices", current_scope, depth + 1);
//check if external variable
bool externalFound = false;
uint8_t index = 0;
for (JsonPair externalsPair: definitionJson["EXTERNALS"].as<JsonObject>()) {
if (strcmp(variable_name, externalsPair.key().c_str()) == 0) {
variable_value["external"] = index; //add external index to parseTree
ANDBG_ARTI("%s Ext Variable found %s (%u) %s\n", spaces+50-depth, variable_name, depth, key);
externalFound = true;
}
index++;
}
if (!externalFound)
{
Symbol* var_symbol = current_scope->lookup(variable_name); //lookup here and parent scopes
if (node == F_VarRef)
{
if (var_symbol == nullptr) {
WARNING_ARTI("%s VarRef %s ID not found in scope of %s\n", spaces+50-depth, variable_name, current_scope->scope_name);
//only warning: value 0 in interpreter (div 0 is captured)
} else {
ANDBG_ARTI("%s VarRef found %s.%s (%u)\n", spaces+50-depth, var_symbol->scope->scope_name, variable_name, depth);
}
}
else //assign and var/formal
{
//if variable not already defined, then add
if (node != F_Assign || var_symbol == nullptr) //only assign needs to check if not exists
{
char param_type[charLength];
if (variable_value.containsKey("type"))
serializeJson(variable_value["type"], param_type); //current_scope.lookup(param.type_node.value); //need string, lookup also used to find types...
else
strcpy(param_type, "notype");
var_symbol = new Symbol(node, variable_name, 9); // no type support yet
if (node == F_Assign)
global_scope->insert(var_symbol); // assigned variables are global scope
else
current_scope->insert(var_symbol);
ANDBG_ARTI("%s %s %s.%s of %s\n", spaces+50-depth, key, var_symbol->scope->scope_name, variable_name, param_type);
}
else if (node != F_Assign && var_symbol != nullptr)
ERROR_ARTI("%s %s Duplicate ID %s.%s\n", spaces+50-depth, key, var_symbol->scope->scope_name, variable_name);
}
if (var_symbol != nullptr)
{
variable_value["level"] = var_symbol->scope_level;
variable_value["index"] = var_symbol->scope_index;;
}
}
if (node == F_Assign)
{
ANDBG_ARTI("%s %s %s = (%u)\n", spaces+50-depth, key, variable_name, depth);
if (value.containsKey("assignoperator"))
{
JsonObject asop = value["assignoperator"];
JsonObject::iterator asopBegin = asop.begin();
ANDBG_ARTI("%s %s\n", spaces+50-depth, asopBegin->value().as<std::string>().c_str());
if (strcmp(asopBegin->value(), "+=") == 0)
value["assignoperator"] = F_plus;
else if (strcmp(asopBegin->value(), "-=") == 0)
value["assignoperator"] = F_minus;
else if (strcmp(asopBegin->value(), "*=") == 0)
value["assignoperator"] = F_multiplication;
else if (strcmp(asopBegin->value(), "/=") == 0)
value["assignoperator"] = F_division;
else if (strcmp(asopBegin->value(), "++") == 0)
value["assignoperator"] = F_plusplus;
else if (strcmp(asopBegin->value(), "--") == 0)
value["assignoperator"] = F_minmin;
}
if (value.containsKey("expr"))
analyze(value, "expr", current_scope, depth + 1);
else if (value["assignoperator"].as<uint8_t>() != F_plusplus && value["assignoperator"].as<uint8_t>() != F_minmin)
{
ERROR_ARTI("%s %s %s: Assign without expression\n", spaces+50-depth, key, variable_name);
errorOccurred = true;
}
}
visitedAlready = true;
break;
}
case F_Call:
{
const char * function_name = value["ID"];
//check if external function
bool externalFound = false;
uint8_t index = 0;
for (JsonPair externalsPair: definitionJson["EXTERNALS"].as<JsonObject>())
{
if (strcmp(function_name, externalsPair.key().c_str()) == 0)
{
ANDBG_ARTI("%s Ext Function found %s (%u)\n", spaces+50-depth, function_name, depth);
value["external"] = index; //add external index to parseTree
externalFound = true;
}
index++;
}
if (!externalFound)
{
Symbol* function_symbol = current_scope->lookup(function_name); //lookup here and parent scopes
if (function_symbol != nullptr)
analyze(function_symbol->block, nullptr, current_scope, depth + 1);
else
ERROR_ARTI("%s Function %s not found in scope of %s\n", spaces+50-depth, function_name, current_scope->scope_name);
} //external functions
if (value.containsKey("actuals"))
analyze(value["actuals"], nullptr, current_scope, depth + 1);
visitedAlready = true;
break;
} // case
default: //visitedalready false => recursive call
break;
} //switch
} // is node_name
if (!visitedAlready && value.size() > 0) // if size == 0 then injected key/value like operator
analyze(value, nullptr, current_scope, depth + 1);
} // key values
} ///for elements in object
}
else if (parseTree.is<JsonArray>())
{
for (JsonVariant newParseTree: parseTree.as<JsonArray>())
analyze(newParseTree, nullptr, current_scope, depth + 1);
}
else //not array
{
// string element = parseTree;
//for some weird reason this causes a crash on esp32
// ERROR_ARTI("%s Error: parseTree should be array or object %s (%u)\n", spaces+50-depth, parseTree.as<std::string>().c_str(), depth);
}
return !errorOccurred;
} //analyze
//https://dev.to/lefebvre/compilers-106---optimizer--ig8
bool optimize(JsonVariant parseTree, uint8_t depth = 0)
{
// DEBUG_ARTI("%s optimize %s (%u)\n", spaces+50-depth, parseTree.as<std::string>().c_str(), depth);
if (parseTree.is<JsonObject>())
{
//make the parsetree as small as possible to let the interpreter run as fast as possible:
for (JsonPair parseTreePair : parseTree.as<JsonObject>())
{
const char * key = parseTreePair.key().c_str();
JsonVariant value = parseTreePair.value();
bool visitedAlready = false;
// object:
// if key is node and value is object then optimize object after that if object empty then remove key
// else key is !ID and value is string then remove key
// else key is * and value is array then optimize array after that if array empty then remove key *
// array
// if element is multiple then remove
if (strcmp(key, "*") == 0 ) // multiple
{
optimize(value, depth + 1);
if (value.size() == 0)
{
parseTree.remove(key);
// DEBUG_ARTI("%s optimize: remove empty %s %s (%u)\n", spaces+50-depth, key, value.as<std::string>().c_str(), depth);
}
visitedAlready = true;
}
else if (this->definitionJson["TOKENS"].containsKey(key)) // if key is token (moved to parse)
{
visitedAlready = true;
}
else if (value.is<JsonObject>()) //if key is node_name
{
optimize(value, depth + 1);
if (value.size() == 0)
{
// DEBUG_ARTI("%s optimize: remove key %s with empty object (%u)\n", spaces+50-depth, key, depth);
parseTree.remove(key);
}
else if (value.size() == 1) //try to shrink, moved to below
{
//- check if a node is not used in analyzer / interpreter and has only one element: go to the parent and replace itself with its child (shrink)
// DEBUG_ARTI("%s node try to shrink %s : %s (%u)\n", spaces+50-depth, key, value.as<std::string>().c_str(), value.size());
JsonObject::iterator objectIterator = value.as<JsonObject>().begin();
if (definitionJson.containsKey(objectIterator->key().c_str())) // if value key is a node
{
// if (objectIterator->value().is<JsonObject>() && objectIterator->value().size() == 1) //
// {
// // JsonObject::iterator objectIterator2 = objectIterator->value().as<JsonObject>().begin();
// // if (objectIterator2->value().is<JsonObject>() ) //&& objectIterator.size() == 1
// {
// DEBUG_ARTI("%s node to shrink %s : %s from %s\n", spaces+50-depth, key, value.as<std::string>().c_str(), parseTree.as<std::string>().c_str());
// DEBUG_ARTI("%s node to shrink %s : %s\n", spaces+50-depth, objectIterator->key().c_str(), objectIterator->value().as<std::string>().c_str());
// // DEBUG_ARTI("%s node to shrink %s : %s\n", spaces+50-depth, objectIterator2->key().c_str(), objectIterator2->value().as<std::string>().c_str());
// DEBUG_ARTI("%s node to shrink replace %s\n", spaces+50-depth, parseTree[key].as<std::string>().c_str());
// DEBUG_ARTI("%s node to shrink by %s\n", spaces+50-depth, objectIterator->value().as<std::string>().c_str());
// // parseTree[key][objectIterator->key().c_str()] = objectIterator2->value();
// parseTree[key] = objectIterator->value();
// }
// // else
// // {
// // DEBUG_ARTI("%s node to shrink2 %s : %s\n", spaces+50-depth, objectIterator2->key().c_str(), objectIterator2->value().as<std::string>().c_str());
// // }
// }
// else
// DEBUG_ARTI("%s value should be an object %s in %s : %s from %s\n", spaces+50-depth, objectIterator->key().c_str(), key, objectIterator->value().as<std::string>().c_str(), value.as<std::string>().c_str());
if (stringToNode(objectIterator->key().c_str()) == F_NoNode) // if key not a node
// if (objectIterator->value().size() == 1)
{
DEBUG_ARTI("%s node to shrink %s in %s : %s from %s\n", spaces+50-depth, objectIterator->key().c_str(), key, value.as<std::string>().c_str(), parseTree.as<std::string>().c_str());
// DEBUG_ARTI("%s node to shrink %s in %s = %s from %s\n", spaces+50-depth, objectIterator->key().c_str(), key, objectIterator->value().as<std::string>().c_str(), parseTree[key].as<std::string>().c_str());
parseTree[key] = objectIterator->value();
// parseTree[key]["old"] = objectIterator->key();
// parseTreePair.key() = objectIterator->key();
// parseTreePair._key = objectIterator->key();
// parseTree[objectIterator->key()] = objectIterator->value();
// parseTree.remove(key);
// parseTree[key][objectIterator2->key().c_str()] = objectIterator2->value();
}
}
} //shrink
visitedAlready = true;
}
else
ERROR_ARTI("%s Programming Error: key no node and no token %s %s (%u)\n", spaces+50-depth, key, value.as<std::string>().c_str(), depth);
if (!visitedAlready && value.size() > 0) // if size == 0 then injected key/value like operator
optimize(value, depth + 1);
} ///for elements in object
//shrink
for (JsonPair parseTreePair : parseTree.as<JsonObject>())
{
const char * key = parseTreePair.key().c_str();
JsonVariant value = parseTreePair.value();
if (false && value.is<JsonObject>() && parseTree.size() == 1 && value.size() == 1 && definitionJson.containsKey(key)) //if key is node_name
{
JsonObject::iterator objectIterator = value.as<JsonObject>().begin();
// DEBUG_ARTI("%s try replace %s by %s %s\n", spaces+50-depth, key, objectIterator->key().c_str(), parseTree.as<std::string>().c_str());
if (strcmp(objectIterator->key().c_str(), "ID") != 0) //&& definitionJson.containsKey(objectIterator->key())???
{
// DEBUG_ARTI("%s found %s:%s\n", spaces+50-depth, node_name, parseTree.as<std::string>().c_str());
// DEBUG_ARTI("%s found replace %s by %s %s\n", spaces+50-depth, key, objectIterator->key().c_str(), parseTree.as<std::string>().c_str());
// DEBUG_ARTI("%s found %s\n", spaces+50-depth, value.as<std::string>().c_str());
// DEBUG_ARTI("%s found %s\n", spaces+50-depth, parseTree.as<std::string>().c_str());
DEBUG_ARTI("%s replace %s by %s %s\n", spaces+50-depth, key, objectIterator->key().c_str(), parseTree.as<std::string>().c_str());
parseTree.remove(key);
// parseTree[key] = value;
parseTree[objectIterator->key()] = objectIterator->value();
// DEBUG_ARTI("%s found %s:%s\n", spaces+50-depth, node_name, parseTree.as<std::string>().c_str());
}
// else
// {
// DEBUG_ARTI("%s not shrinkable %s %s\n", spaces+50-depth, key, value.as<std::string>().c_str());
// if (depth > 12) {
// // parseTree.remove(key);
// char temp[charLength];
// strcpy(temp, key);
// strcat(temp, "-");
// // strcat(temp, objectIterator->key().c_str());
// // parseTree[temp] = value;
// }
// }
if (false && definitionJson.containsKey(objectIterator->key().c_str())) // if value key is a node
{
if (stringToNode(objectIterator->key().c_str()) == F_NoNode) // if key not a node
// if (objectIterator->value().size() == 1)
{
DEBUG_ARTI("%s node to shrink %s in %s : %s from %s\n", spaces+50-depth, objectIterator->key().c_str(), key, value.as<std::string>().c_str(), parseTree.as<std::string>().c_str());
// DEBUG_ARTI("%s node to shrink %s in %s = %s from %s\n", spaces+50-depth, objectIterator->key().c_str(), key, objectIterator->value().as<std::string>().c_str(), parseTree[key].as<std::string>().c_str());
parseTree[key] = objectIterator->value();
// parseTree[key]["old"] = objectIterator->key();
// parseTreePair.key() = objectIterator->key();
// parseTreePair._key = objectIterator->key();
// parseTree[objectIterator->key()] = objectIterator->value();
// parseTree.remove(key);
// parseTree[key][objectIterator2->key().c_str()] = objectIterator2->value();
}
}
} //value is jsonObject
} // for
}
else if (parseTree.is<JsonArray>())
{
JsonArray parseTreeArray = parseTree.as<JsonArray>();
uint8_t arrayIndex = 0;
for (JsonArray::iterator it = parseTreeArray.begin(); it != parseTreeArray.end(); ++it)
{
JsonVariant element = *it;
if (element == "multiple")
{
// DEBUG_ARTI("%s optimize: remove array element 'multiple' of array (%u)\n", spaces+50-depth, arrayIndex);
parseTreeArray.remove(it);
}
else if (it->size() == 0) //remove {} elements (added by * arrays, don't know where added)
{
// DEBUG_ARTI("%s optimize: remove array element {} of %s array (%u)\n", spaces+50-depth, element.as<std::string>().c_str(), arrayIndex);
parseTreeArray.remove(it);
}
else
optimize(*it, depth + 1);
arrayIndex++;
}
}
else //not array
{
// string element = parseTree;
//for some weird reason this causes a crash on esp32
ERROR_ARTI("%s Error: parseTree should be array or object %s (%u)\n", spaces+50-depth, parseTree.as<std::string>().c_str(), depth);
}
// DEBUG_ARTI("%s optimized %s (%u)\n", spaces+50-depth, parseTree.as<std::string>().c_str(), depth);
return !errorOccurred;
} //optimize
// bool visit_ID(JsonVariant parseTree, const char * treeElement = nullptr, ScopedSymbolTable* current_scope = nullptr, uint8_t depth = 0)
bool interpret(JsonVariant parseTree, const char * treeElement = nullptr, ScopedSymbolTable* current_scope = nullptr, uint8_t depth = 0)
{
// RUNLOG_ARTI("%s Interpret %s %s (%u)\n", spaces+50-depth, stringOrEmpty(treeElement), parseTree.as<std::string>().c_str(), depth);
if (depth >= 50)
{
ERROR_ARTI("Error: Interpret recursion level too deep at %s (%u)\n", parseTree.as<std::string>().c_str(), depth);
errorOccurred = true;
}
if (errorOccurred) return false;
if (parseTree.is<JsonObject>())
{
for (JsonPair parseTreePair : parseTree.as<JsonObject>())
{
const char * key = parseTreePair.key().c_str();
JsonVariant value = parseTreePair.value();
if (treeElement == nullptr || strcmp(treeElement, key) == 0)
{
// RUNLOG_ARTI("%s Interpret object element %s %s\n", spaces+50-depth, key, value.as<std::string>().c_str());
bool visitedAlready = false;
if (strcmp(key, "*") == 0)
{
// do the recursive call below
}
else if (strcmp(key, "token") == 0 || strcmp(key, "variable") == 0) //variable decls done in analyze (see pas)
visitedAlready = true;
else if (parseTree.containsKey("token")) //key is token
{
// RUNLOG_ARTI("%s Token %s %s %s\n", spaces+50-depth, key, valueStr, parseTree.as<std::string>().c_str());
const char * valueStr = value;
switch (parseTree["token"].as<uint8_t>())
{
case F_integerConstant:
case F_realConstant:
valueStack->push(atof(valueStr)); //push value
#if ARTI_PLATFORM != ARTI_ARDUINO //for some weird reason this causes a crash on esp32
RUNLOG_ARTI("%s %s %s (Push %u)\n", spaces+50-depth, key, valueStr, valueStack->stack_index);
#endif
break;
default:
valueStack->push(parseTree["token"].as<uint8_t>()); // push Operator index
#if ARTI_PLATFORM != ARTI_ARDUINO //for some weird reason this causes a crash on esp32
RUNLOG_ARTI("%s %s %s (Push %u)\n", spaces+50-depth, key, valueStr, valueStack->stack_index);
#endif
}
visitedAlready = true;
}
else //if key is node_name
{
uint8_t node = stringToNode(key);
// RUNLOG_ARTI("%s Node %s\n", spaces+50-depth, key);
switch (node)
{
case F_Program:
{
const char * program_name = value["ID"];
RUNLOG_ARTI("%s program %s\n", spaces+50-depth, program_name);
ActivationRecord* ar = new ActivationRecord(program_name, "PROGRAM", 1);
this->callStack->push(ar);
interpret(value["block"], nullptr, global_scope, depth + 1);
// do not delete main stack and program ar as used in subsequent calls
// this->callStack->pop();
// delete ar; ar = nullptr;
visitedAlready = true;
break;
}
case F_Function:
{
const char * function_name = value["ID"];
Symbol* function_symbol = current_scope->lookup(function_name);
RUNLOG_ARTI("%s Save block of %s\n", spaces+50-depth, function_name);
if (function_symbol != nullptr)
function_symbol->block = value["block"];
else
ERROR_ARTI("%s Function %s: not found\n", spaces+50-depth, function_name);
visitedAlready = true;
break;
}
case F_Call:
{
const char * function_name = value["ID"];
//check if external function
if (value.containsKey("external")) {
uint8_t oldIndex = valueStack->stack_index;
if (value.containsKey("actuals"))
interpret(value["actuals"], nullptr, current_scope, depth + 1);
float returnValue = floatNull;
returnValue = arti_external_function(value["external"], valueStack->floatStack[oldIndex]
, (valueStack->stack_index - oldIndex>1)?valueStack->floatStack[oldIndex+1]:floatNull
, (valueStack->stack_index - oldIndex>2)?valueStack->floatStack[oldIndex+2]:floatNull
, (valueStack->stack_index - oldIndex>3)?valueStack->floatStack[oldIndex+3]:floatNull
, (valueStack->stack_index - oldIndex>4)?valueStack->floatStack[oldIndex+4]:floatNull);
#if ARTI_PLATFORM != ARTI_ARDUINO // because arduino runs the code instead of showing the code
uint8_t lastIndex = oldIndex;
RUNLOG_ARTI("%s Call %s(", spaces+50-depth, function_name);
char sep[3] = "";
for (int i = oldIndex; i< valueStack->stack_index; i++) {
RUNLOG_ARTI("%s%f", sep, valueStack->floatStack[i]);
strcpy(sep, ", ");
}
if ( returnValue != floatNull)
RUNLOG_ARTI(") = %f (Pop %u, Push %u)\n", returnValue, oldIndex, oldIndex + 1);
else
RUNLOG_ARTI(") (Pop %u)\n", oldIndex);
#endif
valueStack->stack_index = oldIndex;
if (returnValue != floatNull)
valueStack->push(returnValue);
}
else { //not an external function
Symbol* function_symbol = current_scope->lookup(function_name);
if (function_symbol != nullptr) //calling undefined function: pre-defined functions e.g. print
{
ActivationRecord* ar = new ActivationRecord(function_name, "Function", function_symbol->scope_level + 1);
RUNLOG_ARTI("%s %s %s\n", spaces+50-depth, key, function_name);
uint8_t oldIndex = valueStack->stack_index;
uint8_t lastIndex = valueStack->stack_index;
if (value.containsKey("actuals"))
interpret(value["actuals"], nullptr, current_scope, depth + 1);
for (uint8_t i=0; i<function_symbol->function_scope->nrOfFormals; i++)
{
//determine type, for now assume float
float result = valueStack->floatStack[lastIndex++];
ar->set(function_symbol->function_scope->symbols[i]->scope_index, result);
RUNLOG_ARTI("%s Actual %s.%s = %f (pop %u)\n", spaces+50-depth, function_name, function_symbol->function_scope->symbols[i]->name, result, valueStack->stack_index);
}
valueStack->stack_index = oldIndex;
this->callStack->push(ar);
interpret(function_symbol->block, nullptr, function_symbol->function_scope, depth + 1);
this->callStack->pop();
delete ar; ar = nullptr;
//tbd if syntax supports returnvalue
// char callResult[charLength] = "CallResult tbd of ";
// strcat(callResult, function_name);
// valueStack->push(callResult);
} //function_symbol != nullptr
else {
RUNLOG_ARTI("%s %s not found %s\n", spaces+50-depth, key, function_name);
}
} //external functions
visitedAlready = true;
break;
}
case F_VarRef:
case F_Assign: //get or set a variable
{
const char * variable_name;
uint8_t variable_level;
uint8_t variable_index;
uint8_t variable_external;
JsonObject variable_indices;
JsonObject variable_value;
float resultValue = floatNull;
if (node == F_Assign)
{
variable_value = value["varref"];
if (value.containsKey("expr")) //value assignment
{
interpret(value, "expr", current_scope, depth + 1); //value pushed
resultValue = valueStack->popFloat(); //result of interpret expr (but not for -- and ++ !!!!)
}
}
else
variable_value = value;
variable_name = variable_value["ID"];
variable_level = variable_value["level"];
variable_index = variable_value["index"];
variable_external = variable_value["external"];
variable_indices = variable_value["indices"];
uint8_t oldIndex = valueStack->stack_index;
//array indices
char indices[charLength]; //used in RUNLOG_ARTI only
strcpy(indices, "");
if (!variable_indices.isNull())
{
strcat(indices, "[");
interpret(variable_value, "indices", current_scope, depth + 1); //values of indices pushed
char sep[3] = "";
for (uint8_t i = oldIndex; i< valueStack->stack_index; i++) {
strcat(indices, sep);
char itoaChar[charLength];
// itoa(valueStack->floatStack[i], itoaChar, 10);
snprintf(itoaChar, sizeof(itoaChar), "%f", valueStack->floatStack[i]);
strcat(indices, itoaChar);
strcpy(sep, ",");
}
strcat(indices, "]");
}
//check if external variable
if (variable_value.containsKey("external")) //added by Analyze
{
if (node == F_VarRef) { //get the value
resultValue = arti_get_external_variable(variable_external, (valueStack->stack_index - oldIndex>0)?valueStack->floatStack[oldIndex]:floatNull, (valueStack->stack_index - oldIndex>1)?valueStack->floatStack[oldIndex+1]:floatNull);
valueStack->stack_index = oldIndex;
if (resultValue != floatNull)
{
valueStack->push(resultValue);
RUNLOG_ARTI("%s %s ext.%s = %f (push %u)\n", spaces+50-depth, key, variable_name, resultValue, valueStack->stack_index); //key is variable_declaration name is ID
}
else
ERROR_ARTI("%s Error: %s ext.%s no value\n", spaces+50-depth, key, variable_name);
}
else //assign: set the external value...
{
arti_set_external_variable(resultValue, variable_external, (valueStack->stack_index - oldIndex>0)?valueStack->floatStack[oldIndex]:floatNull, (valueStack->stack_index - oldIndex>1)?valueStack->floatStack[oldIndex+1]:floatNull);
valueStack->stack_index = oldIndex;
RUNLOG_ARTI("%s %s set ext.%s%s = %f (Pop %u)\n", spaces+50-depth, key, variable_name, indices, resultValue, oldIndex);
}
}
else //not external, get er set the variable
{
// Symbol* variable_symbol = current_scope->lookup(variable_name);
ActivationRecord* ar;
//check already defined in this scope
// RUNLOG_ARTI("%s levels %u-%u\n", spaces+50-depth, variable_level, variable_index );
if (variable_level != 0) { //var already exist
//calculate the index in the call stack to find the right ar
uint8_t index = this->callStack->recordsCounter - 1 - (this->callStack->peek()->nesting_level - variable_level);
// RUNLOG_ARTI("%s %s %s.%s = %s (push) %s %d-%d = %d (%d)\n", spaces+50-depth, key, ar->name, variable_name, varValue, variable_symbol->name, this->callStack->peek()->nesting_level,variable_symbol->scope_level, index, this->callStack->recordsCounter); //key is variable_declaration name is ID
ar = this->callStack->records[index];
}
else //var created here
ar = this->callStack->peek();
if (ar != nullptr) // variable found
{
if (node == F_VarRef) //get the value
{
//determine type, for now assume float
float varValue = ar->getFloat(variable_index);
valueStack->push(varValue);
#if ARTI_PLATFORM != ARTI_ARDUINO //for some weird reason this causes a crash on esp32
RUNLOG_ARTI("%s %s %s.%s = %f (push %u) %u-%u\n", spaces+50-depth, key, ar->name, variable_name, varValue, valueStack->stack_index, variable_level, variable_index); //key is variable_declaration name is ID
#endif
}
else { //assign: set the value
if (value.containsKey("assignoperator"))
{
switch (value["assignoperator"].as<uint8_t>())
{
case F_plus:
ar->set(variable_index, ar->getFloat(variable_index) + resultValue);
break;
case F_minus:
ar->set(variable_index, ar->getFloat(variable_index) - resultValue);
break;
case F_multiplication:
ar->set(variable_index, ar->getFloat(variable_index) * resultValue);
break;
case F_division:
{
if (resultValue == 0) // divisor
{
resultValue = 1;
ERROR_ARTI("%s /= division by 0 not possible, divisor ignored for %f\n", spaces+50-depth, ar->getFloat(variable_index));
}
ar->set(variable_index, ar->getFloat(variable_index) / resultValue);
break;
}
case F_plusplus:
ar->set(variable_index, ar->getFloat(variable_index) + 1);
break;
case F_minmin:
ar->set(variable_index, ar->getFloat(variable_index) - 1);
break;
}
RUNLOG_ARTI("%s %s.%s%s %s= %f (pop %u) %u-%u\n", spaces+50-depth, ar->name, variable_name, indices, tokenToString(value["assignoperator"]), ar->getFloat(variable_index), valueStack->stack_index, variable_level, variable_index);
}
else
{
ar->set(variable_index, resultValue);
RUNLOG_ARTI("%s %s.%s%s := %f (pop %u) %u-%u\n", spaces+50-depth, ar->name, variable_name, indices, ar->getFloat(variable_index), valueStack->stack_index, variable_level, variable_index);
}
valueStack->stack_index = oldIndex;
}
} //ar != nullptr
else { //unknown variable
ERROR_ARTI("%s %s %s unknown\n", spaces+50-depth, key, variable_name);
valueStack->push(floatNull);
}
} // ! founnd
visitedAlready = true;
break;
}
case F_Expr:
case F_Term:
{
uint8_t oldIndex = valueStack->stack_index;
// RUNLOG_ARTI("%s before expr term interpret %s %s\n", spaces+50-depth, key, value.as<std::string>().c_str());
interpret(value, nullptr, current_scope, depth + 1); //pushes results
// RUNLOG_ARTI("%s %s interpret > (%u - %u = %u)\n", spaces+50-depth, key, valueStack->stack_index, oldIndex, valueStack->stack_index - oldIndex);
// always 3, 5, 7 ... values
if (valueStack->stack_index - oldIndex >= 3)
{
float left = valueStack->floatStack[oldIndex];
for (int i = 3; i <= valueStack->stack_index - oldIndex; i += 2)
{
uint8_t operatorx = valueStack->floatStack[oldIndex + i - 2];
float right = valueStack->floatStack[oldIndex + i - 1];
float evaluation = 0;
switch (operatorx) {
case F_plus:
evaluation = left + right;
break;
case F_minus:
evaluation = left - right;
break;
case F_multiplication:
evaluation = left * right;
break;
case F_division: {
if (right == 0)
{
right = 1;
ERROR_ARTI("%s division by 0 not possible, divisor ignored for %f\n", spaces+50-depth, left);
}
evaluation = left / right;
break;
}
case F_modulo: {
if (right == 0) {
evaluation = left;
ERROR_ARTI("%s mod 0 not possible, mod ignored %f\n", spaces+50-depth, left);
}
else
evaluation = fmod(left, right);
break;
}
case F_bitShiftLeft:
evaluation = (int)left << (int)right; //only works on integers
break;
case F_bitShiftRight:
evaluation = (int)left >> (int)right; //only works on integers
break;
case F_equal:
evaluation = left == right;
break;
case F_notEqual:
evaluation = left != right;
break;
case F_lessThen:
evaluation = left < right;
break;
case F_lessThenOrEqual:
evaluation = left <= right;
break;
case F_greaterThen:
evaluation = left > right;
break;
case F_greaterThenOrEqual:
evaluation = left >= right;
break;
case F_and:
evaluation = left && right;
break;
case F_or:
evaluation = left || right;
break;
default:
ERROR_ARTI("%s Programming error: unknown operator %u\n", spaces+50-depth, operatorx);
}
RUNLOG_ARTI("%s %f %s %f = %f (pop %u, push %u)\n", spaces+50-depth, left, tokenToString(operatorx), right, evaluation, valueStack->stack_index - i, valueStack->stack_index - i + 1);
left = evaluation;
}
valueStack->stack_index = oldIndex;
valueStack->push(left);
}
else if (valueStack->stack_index - oldIndex == 2) // unary: operator and 1 operand
{
uint8_t operatorx = valueStack->floatStack[oldIndex];
if (operatorx == F_minus)
{
valueStack->stack_index = oldIndex;
valueStack->push(-valueStack->floatStack[oldIndex + 1]);
RUNLOG_ARTI("%s unary - %f (push %u)\n", spaces+50-depth, valueStack->floatStack[oldIndex + 1], valueStack->stack_index );
}
else {
RUNLOG_ARTI("%s unary operator not supported %u %s\n", spaces+50-depth, operatorx, tokenToString(operatorx));
}
}
visitedAlready = true;
break;
}
case F_For:
{
RUNLOG_ARTI("%s For (%u)\n", spaces+50-depth, valueStack->stack_index);
interpret(value, "assign", current_scope, depth + 1); //creates the assignment
ActivationRecord* ar = this->callStack->peek();
bool continuex = true;
uint16_t counter = 0;
while (continuex && counter < 2000) //to avoid endless loops
{
RUNLOG_ARTI("%s iteration\n", spaces+50-depth);
RUNLOG_ARTI("%s check to condition\n", spaces+50-depth);
interpret(value, "expr", current_scope, depth + 1); //pushes result of to
float conditionResult = valueStack->popFloat();
RUNLOG_ARTI("%s conditionResult (pop %u)\n", spaces+50-depth, valueStack->stack_index);
if (conditionResult == 1) { //conditionResult is true
RUNLOG_ARTI("%s 1 => run block\n", spaces+50-depth);
interpret(value["block"], nullptr, current_scope, depth + 1);
RUNLOG_ARTI("%s assign next value\n", spaces+50-depth);
interpret(value["increment"], nullptr, current_scope, depth + 1); //pushes increment result
// MEMORY_ARTI("%s Iteration %u %u\n", spaces+50-depth, counter, FREE_SIZE);
}
else
{
if (conditionResult == 0) { //conditionResult is false
RUNLOG_ARTI("%s 0 => end of For\n", spaces+50-depth);
continuex = false;
}
else // conditionResult is a value (e.g. in pascal)
{
//get the variable from assignment
float varValue = ar->getFloat(ar->lastSetIndex);
float evaluation = varValue <= conditionResult;
RUNLOG_ARTI("%s %s.(%u) %f <= %f = %f\n", spaces+50-depth, ar->name, ar->lastSetIndex, varValue, conditionResult, evaluation);
if (evaluation == 1)
{
RUNLOG_ARTI("%s 1 => run block\n", spaces+50-depth);
interpret(value["block"], nullptr, current_scope, depth + 1);
//increment
ar->set(ar->lastSetIndex, varValue + 1);
}
else
{
RUNLOG_ARTI("%s 0 => end of For\n", spaces+50-depth);
continuex = false;
}
}
}
counter++;
};
if (continuex)
ERROR_ARTI("%s too many iterations in for loop %u\n", spaces+50-depth, counter);
visitedAlready = true;
break;
} // case
case F_If:
{
RUNLOG_ARTI("%s If (stack %u)\n", spaces+50-depth, valueStack->stack_index);
RUNLOG_ARTI("%s condition\n", spaces+50-depth);
if (value.containsKey("expr"))
interpret(value, "expr", current_scope, depth + 1);
// else if (value.containsKey("varref"))
// interpret(value, "varref", current_scope, depth + 1);
float conditionResult = valueStack->popFloat();
RUNLOG_ARTI("%s (pop %u)\n", spaces+50-depth, valueStack->stack_index);
if (conditionResult == 1) //conditionResult is true
interpret(value, "block", current_scope, depth + 1);
else
interpret(value, "elseBlock", current_scope, depth + 1);
visitedAlready = true;
break;
} // case
case F_Cex:
{
RUNLOG_ARTI("%s Cex (stack %u)\n", spaces+50-depth, valueStack->stack_index);
RUNLOG_ARTI("%s condition\n", spaces+50-depth);
interpret(value, "expr", current_scope, depth + 1);
float conditionResult = valueStack->popFloat();
RUNLOG_ARTI("%s (pop %u)\n", spaces+50-depth, valueStack->stack_index);
if (conditionResult == 1) //conditionResult is true
interpret(value, "trueExpr", current_scope, depth + 1);
else
interpret(value, "falseExpr", current_scope, depth + 1);
visitedAlready = true;
break;
} // case
default: //visitedalready false => recursive call
break;
}
} // is key is node_name
if (!visitedAlready && value.size() > 0) // if size == 0 then injected key/value like operator
interpret(value, nullptr, current_scope, depth + 1);
} // if treeelement
// RUNLOG_ARTI("%s before end for %u\n", spaces+50-depth, depth);
} // for (JsonPair)
}
else if (parseTree.is<JsonArray>())
{
for (JsonVariant newParseTree: parseTree.as<JsonArray>())
{
// RUNLOG_ARTI("%s\n", spaces+50-depth, "Array ", parseTree[i], " ";
interpret(newParseTree, nullptr, current_scope, depth + 1);
}
}
else { //not array
ERROR_ARTI("%s Error: parseTree should be array or object %s (%u)\n", spaces+50-depth, parseTree.as<std::string>().c_str(), depth);
}
return !errorOccurred;
} //interpret
void closeLog()
{
//non arduino stops log here
#if ARTI_PLATFORM == ARTI_ARDUINO
if (logToFile)
{
logFile.close();
logToFile = false;
}
#else
if (logToFile)
{
fclose(logFile);
logToFile = false;
}
#endif
}
bool setup(const char *definitionName, const char *programName)
{
errorOccurred = false;
frameCounter = 0;
logToFile = true;
//open logFile
if (logToFile)
{
#if ARTI_PLATFORM == ARTI_ARDUINO
strcpy(logFileName, "/");
#endif
strcpy(logFileName, programName);
strcat(logFileName, ".log");
#if ARTI_PLATFORM == ARTI_ARDUINO
logFile = WLED_FS.open(logFileName,"w");
#else
logFile = fopen (logFileName,"w");
#endif
}
MEMORY_ARTI("setup %u bytes free\n", FREE_SIZE);
if (stages < 1) {close(); return true;}
bool loadParseTreeFile = false;
#if ARTI_PLATFORM == ARTI_ARDUINO
File definitionFile;
definitionFile = WLED_FS.open(definitionName, "r");
#else
std::fstream definitionFile;
definitionFile.open(definitionName, std::ios::in);
#endif
MEMORY_ARTI("open %s %u ✓\n", definitionName, FREE_SIZE);
if (!definitionFile)
{
ERROR_ARTI("Definition file %s not found. Press Download wled json\n", definitionName);
return false;
}
//open definitionFile
#if ARTI_PLATFORM == ARTI_ARDUINO
definitionJsonDoc = new PSRAMDynamicJsonDocument(8192); //currently 5335
#else
definitionJsonDoc = new PSRAMDynamicJsonDocument(16384); //currently 9521
#endif
// mandatory tokens:
// "ID": "ID",
// "INTEGER_CONST": "INTEGER_CONST",
// "REAL_CONST": "REAL_CONST",
MEMORY_ARTI("definitionTree %u => %u ✓\n", (unsigned int)definitionJsonDoc->capacity(), FREE_SIZE); //unsigned int needed when running embedded to suppress warnings
DeserializationError err = deserializeJson(*definitionJsonDoc, definitionFile);
if (err)
{
ERROR_ARTI("deserializeJson() of definition failed with code %s\n", err.c_str());
return false;
}
definitionFile.close();
definitionJson = definitionJsonDoc->as<JsonObject>();
JsonObject::iterator objectIterator = definitionJson.begin();
JsonObject metaData = objectIterator->value();
const char * version = metaData["version"];
if (strcmp(version, "v033") != 0)
{
ERROR_ARTI("Version of definition.json file (%s) should be v033.\nPress Download wled json\n", version);
return false;
}
const char * startNode = metaData["start"];
if (startNode == nullptr)
{
ERROR_ARTI("Setup Error: No start node found in definition file %s\n", definitionName);
return false;
}
#if ARTI_PLATFORM == ARTI_ARDUINO
File programFile;
programFile = WLED_FS.open(programName, "r");
#else
std::fstream programFile;
programFile.open(programName, std::ios::in);
#endif
MEMORY_ARTI("open %s %u ✓\n", programName, FREE_SIZE);
if (!programFile)
{
ERROR_ARTI("Program file %s not found\n", programName);
return false;
}
//open programFile
char * programText;
uint16_t programFileSize;
#if ARTI_PLATFORM == ARTI_ARDUINO
programFileSize = programFile.size();
programText = (char *)malloc(programFileSize+1);
programFile.read((byte *)programText, programFileSize);
programText[programFileSize] = '\0';
#else
programText = (char *)malloc(programTextSize);
programFile.read(programText, programTextSize);
DEBUG_ARTI("programFile size %lu bytes\n", programFile.gcount());
programText[programFile.gcount()] = '\0';
programFileSize = strlen(programText);
#endif
programFile.close();
char parseTreeName[fileNameLength];
strcpy(parseTreeName, programName);
// if (loadParseTreeFile)
// strcpy(parseTreeName, "Gen");
strcat(parseTreeName, ".json");
#if ARTI_PLATFORM == ARTI_ARDUINO
parseTreeJsonDoc = new PSRAMDynamicJsonDocument(32768); //less memory on arduino: 32 vs 64 bit?
#else
parseTreeJsonDoc = new PSRAMDynamicJsonDocument(65536);
#endif
MEMORY_ARTI("parseTree %u => %u ✓\n", (unsigned int)parseTreeJsonDoc->capacity(), FREE_SIZE);
//parse
#ifdef ARTI_DEBUG // only read write file if debug is on
#if ARTI_PLATFORM == ARTI_ARDUINO
File parseTreeFile;
parseTreeFile = WLED_FS.open(parseTreeName, loadParseTreeFile?"r":"w");
#else
std::fstream parseTreeFile;
parseTreeFile.open(parseTreeName, loadParseTreeFile?std::ios::in:std::ios::out);
#endif
#endif
if (stages < 1) {close(); return true;}
if (!loadParseTreeFile)
{
parseTreeJson = parseTreeJsonDoc->as<JsonVariant>();
lexer = new Lexer(programText, definitionJson);
lexer->get_next_token();
if (stages < 2) {close(); return true;}
uint8_t result = parse(parseTreeJson, startNode, '&', lexer->definitionJson[startNode], 0);
if (this->lexer->pos != strlen(this->lexer->text))
{
ERROR_ARTI("Node %s Program not entirely parsed (%u,%u) %u of %u\n", startNode, this->lexer->lineno, this->lexer->column, this->lexer->pos, (unsigned int)strlen(this->lexer->text));
return false;
}
else if (result == ResultFail)
{
ERROR_ARTI("Node %s Program parsing failed (%u,%u) %u of %u\n", startNode, this->lexer->lineno, this->lexer->column, this->lexer->pos, (unsigned int)strlen(this->lexer->text));
return false;
}
else
{
DEBUG_ARTI("Node %s Parsed until (%u,%u) %u of %u\n", startNode, this->lexer->lineno, this->lexer->column, this->lexer->pos, (unsigned int)strlen(this->lexer->text));
MEMORY_ARTI("parse %u ✓\n", FREE_SIZE);
}
MEMORY_ARTI("definitionTree %u / %u%% (%u %u %u)\n", (unsigned int)definitionJsonDoc->memoryUsage(), 100 * definitionJsonDoc->memoryUsage() / definitionJsonDoc->capacity(), (unsigned int)definitionJsonDoc->size(), definitionJsonDoc->overflowed(), (unsigned int)definitionJsonDoc->nesting());
MEMORY_ARTI("parseTree %u / %u%% (%u %u %u)\n", (unsigned int)parseTreeJsonDoc->memoryUsage(), 100 * parseTreeJsonDoc->memoryUsage() / parseTreeJsonDoc->capacity(), (unsigned int)parseTreeJsonDoc->size(), parseTreeJsonDoc->overflowed(), (unsigned int)parseTreeJsonDoc->nesting());
size_t memBefore = parseTreeJsonDoc->memoryUsage();
parseTreeJsonDoc->garbageCollect();
MEMORY_ARTI("garbageCollect %u / %u%% -> %u / %u%%\n", (unsigned int)memBefore, 100 * memBefore / parseTreeJsonDoc->capacity(), (unsigned int)parseTreeJsonDoc->memoryUsage(), 100 * parseTreeJsonDoc->memoryUsage() / parseTreeJsonDoc->capacity());
delete lexer; lexer = nullptr;
}
else
{
// read parseTree
#ifdef ARTI_DEBUG // only write file if debug is on
DeserializationError err = deserializeJson(*parseTreeJsonDoc, parseTreeFile);
if (err)
{
ERROR_ARTI("deserializeJson() of parseTree failed with code %s\n", err.c_str());
return false;
}
#endif
}
#if ARTI_PLATFORM == ARTI_ARDUINO //not on windows as cause crash???
free(programText);
#endif
if (stages >= 3)
{
DEBUG_ARTI("\nOptimizer\n");
if (!optimize(parseTreeJson))
{
ERROR_ARTI("Optimize failed\n");
return false;
}
else
MEMORY_ARTI("optimize %u ✓\n", FREE_SIZE);
size_t memBefore = parseTreeJsonDoc->memoryUsage();
parseTreeJsonDoc->garbageCollect();
MEMORY_ARTI("garbageCollect %u / %u%% -> %u / %u%%\n", (unsigned int)memBefore, 100 * memBefore / parseTreeJsonDoc->capacity(), (unsigned int)parseTreeJsonDoc->memoryUsage(), 100 * parseTreeJsonDoc->memoryUsage() / parseTreeJsonDoc->capacity());
if (stages >= 4)
{
ANDBG_ARTI("\nAnalyzer\n");
if (!analyze(parseTreeJson))
{
ERROR_ARTI("Analyze failed\n");
errorOccurred = true;
}
else
MEMORY_ARTI("analyze %u ✓\n", FREE_SIZE);
}
}
size_t memBefore = parseTreeJsonDoc->memoryUsage();
parseTreeJsonDoc->garbageCollect();
MEMORY_ARTI("garbageCollect %u / %u%% -> %u / %u%%\n", (unsigned int)memBefore, 100 * memBefore / parseTreeJsonDoc->capacity(), (unsigned int)parseTreeJsonDoc->memoryUsage(), 100 * parseTreeJsonDoc->memoryUsage() / parseTreeJsonDoc->capacity());
#ifdef ARTI_DEBUG // only write parseTree file if debug is on
if (!loadParseTreeFile)
serializeJsonPretty(*parseTreeJsonDoc, parseTreeFile);
parseTreeFile.close();
#endif
if (stages < 5 || errorOccurred) {close(); return !errorOccurred;}
//interpret main
callStack = new CallStack();
valueStack = new ValueStack();
if (global_scope != nullptr) //due to undefined functions??? wip
{
RUNLOG_ARTI("\ninterpret %s %u %u\n", global_scope->scope_name, global_scope->scope_level, global_scope->symbolsIndex);
if (!interpret(parseTreeJson))
{
ERROR_ARTI("Interpret main failed\n");
return false;
}
}
else
{
ERROR_ARTI("\nInterpret global scope is nullptr\n");
return false;
}
MEMORY_ARTI("Interpret main %u ✓\n", FREE_SIZE);
return !errorOccurred;
} // setup
void close() {
MEMORY_ARTI("closing Arti %u\n", FREE_SIZE);
if (callStack != nullptr) {delete callStack; callStack = nullptr;}
if (valueStack != nullptr) {delete valueStack; valueStack = nullptr;}
if (global_scope != nullptr) {delete global_scope; global_scope = nullptr;}
if (definitionJsonDoc != nullptr) {
MEMORY_ARTI("definitionJson %u / %u%% (%u %u %u)\n", (unsigned int)definitionJsonDoc->memoryUsage(), 100 * definitionJsonDoc->memoryUsage() / definitionJsonDoc->capacity(), (unsigned int)definitionJsonDoc->size(), definitionJsonDoc->overflowed(), (unsigned int)definitionJsonDoc->nesting());
delete definitionJsonDoc; definitionJsonDoc = nullptr;
}
if (parseTreeJsonDoc != nullptr) {
MEMORY_ARTI("parseTree %u / %0u%% (%u %u %u)\n", (unsigned int)parseTreeJsonDoc->memoryUsage(), 100 * parseTreeJsonDoc->memoryUsage() / parseTreeJsonDoc->capacity(), (unsigned int)parseTreeJsonDoc->size(), parseTreeJsonDoc->overflowed(), (unsigned int)parseTreeJsonDoc->nesting());
delete parseTreeJsonDoc; parseTreeJsonDoc = nullptr;
}
MEMORY_ARTI("closed Arti %u ✓\n", FREE_SIZE);
closeLog();
#if ARTI_PLATFORM == ARTI_ARDUINO
WLED_FS.remove(logFileName); //cleanup the /edit folder a bit
#endif
}
}; //ARTI