//+------------------------------------------------------------------+ //| StaticAst.mqh | //| Copyright 2026, Niquel Mendoza. | //| https://www.mql5.com/es/users/nique_372/news | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Niquel Mendoza." #property link "https://www.mql5.com/es/users/nique_372/news" #property strict #ifndef EXPRESSEVALBYLEO_SRC_MATHEVAL_STATIC_AST_MQH #define EXPRESSEVALBYLEO_SRC_MATHEVAL_STATIC_AST_MQH //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "Defines.mqh" //--- // Este AST solo procesa operaciones estaticas (Sin valores dinamicos) @smop(10 * 29 * (10 + 20) % 30) // Esta super optimizado para ello en cambio //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ struct pack(sizeof(int)) CMathExpresionEvalStatic { private: int m_pos; int m_size; ENUM_MATH_EVAL_EXP_ERR_PARSE m_last_err; int m_err_pos; int m_para_stack; //--- bool ParseInternalRaw(const TokenMathExp& tokens[], const int math_op, BitInterpreter &val, int& type); //--- bool RawOp(const TokenMathExp &tokens[], const int type_op, int pos1, int pos2, BitInterpreter &bit, int &type); bool RawOpAply(const TokenMathExp &tokens[], const int type_op, int pos2, BitInterpreter &bit, int &type); bool RawOpSobreBits(const TokenMathExp &tokens[], const int type_op, BitInterpreter& bit, int& type, const BitInterpreter &bit2, const int type_2); public: CMathExpresionEvalStatic(void) {} //~CMathExpresionEvalStatic(void) {} //--- void CleanCache(const TokenMathExp& tokens[]); bool ParsNoRes(const TokenMathExp &tokens[], BitInterpreter& it, int& type); //--- __forceinline ENUM_MATH_EVAL_EXP_ERR_PARSE LastErr() const { return m_last_err; } __forceinline int LastErrPos() const { return m_err_pos; } }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CMathExpresionEvalStatic::CleanCache(const TokenMathExp& tokens[]) { //--- m_size = ArraySize(tokens); m_pos = 0; m_para_stack = 0; m_err_pos = 0; m_last_err = WRONG_VALUE; //--- // ParseNoRes // 10 * 20 - // ParseInternalRaw // 200 - 10 + 2 // ParseInternalRaw // 190 + 2 * // ParsNoRes // 2 * 2 // ParseInternalRaw // 190 + 4 // . // . // 10 * 20 - 10 + 2 * 2 // 200 - 10 + 2 * 2 // 190 + 2 * 2 // 200 - // //--- } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CMathExpresionEvalStatic::RawOp(const TokenMathExp &tokens[], const int type_op, int pos1, int pos2, BitInterpreter &bit, int &type) { //--- // Se asume que pos1, pos2 son numeros //--- const int type_number_1 = tokens[pos1].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; // Tipo de numero 1 const int type_number_2 = tokens[pos2].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; // Tipo de numero 2 //--- if((type_number_1 & type_number_2) == 1) { type = AST_LOGIC_NUMBER_INTEGER; switch(type_op) { case BINARY_NODE_OP_BIT_OR: bit.long_value = tokens[pos1].v.long_value | tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_BIT_XOR: bit.long_value = tokens[pos1].v.long_value ^ tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_BIT_AND: bit.long_value = tokens[pos1].v.long_value & tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_BIT_DEZPLAZAMIENTO_DRC: bit.long_value = tokens[pos1].v.long_value >> tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_BIT_DEZPLAZAMIENTO_IZQ: bit.long_value = tokens[pos1].v.long_value << tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_RESTA: bit.long_value = tokens[pos1].v.long_value - tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_SUMA: bit.long_value = tokens[pos1].v.long_value + tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_MODULO: bit.long_value = tokens[pos1].v.long_value % tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_DIVICION: bit.long_value = tokens[pos1].v.long_value / tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_MUL: bit.long_value = tokens[pos1].v.long_value * tokens[pos2].v.long_value; return true; default: m_last_err = MATH_EVAL_EXP_ERR_PARSE_TIPO_DE_OPERACION_MATH_DESCONOCIDA; m_err_pos = pos2 - 1; return false; } } else { //--- const double left = (type_number_1 == AST_LOGIC_NUMBER_DOUBLE) ? tokens[pos1].v.double_value : (double)tokens[pos1].v.long_value; const double right = (type_number_2 == AST_LOGIC_NUMBER_DOUBLE) ? tokens[pos2].v.double_value : (double)tokens[pos2].v.long_value; //--- type = AST_LOGIC_NUMBER_DOUBLE; switch(type_op) { case BINARY_NODE_OP_RESTA: bit.double_value = left - right; return true; case BINARY_NODE_OP_SUMA: bit.double_value = left + right; return true; case BINARY_NODE_OP_DIVICION: bit.double_value = left / right; return true; case BINARY_NODE_OP_MUL: bit.double_value = left * right; return true; default: m_last_err = MATH_EVAL_EXP_ERR_PARSE_TIPO_DE_OPERACION_INVALIDA_PARA_DBL; m_err_pos = pos2 - 1; return false; } } //--- return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CMathExpresionEvalStatic::RawOpAply(const TokenMathExp &tokens[], const int type_op, int pos2, BitInterpreter &bit, int &type) { //--- // Se asume que pos1, pos2 son numeros //--- const int type_number_1 = type; // Tipo de numero 1 const int type_number_2 = tokens[pos2].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; // Tipo de numero 2 //--- if((type_number_1 & type_number_2) == 1) { type = AST_LOGIC_NUMBER_INTEGER; switch(type_op) { case BINARY_NODE_OP_BIT_OR: bit.long_value |= tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_BIT_XOR: bit.long_value ^= tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_BIT_AND: bit.long_value &= tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_BIT_DEZPLAZAMIENTO_DRC: bit.long_value >>= tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_BIT_DEZPLAZAMIENTO_IZQ: bit.long_value <<= tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_RESTA: bit.long_value -= tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_SUMA: bit.long_value += tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_MODULO: bit.long_value %= tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_DIVICION: bit.long_value /= tokens[pos2].v.long_value; return true; case BINARY_NODE_OP_MUL: bit.long_value *= tokens[pos2].v.long_value; return true; default: m_last_err = MATH_EVAL_EXP_ERR_PARSE_TIPO_DE_OPERACION_MATH_DESCONOCIDA; m_err_pos = pos2 - 1; return false; } } else { //--- const double right = (type_number_2 == AST_LOGIC_NUMBER_DOUBLE) ? tokens[pos2].v.double_value : (double)tokens[pos2].v.long_value; if(type == AST_LOGIC_NUMBER_INTEGER) bit.double_value = (double)bit.long_value; //--- type = AST_LOGIC_NUMBER_DOUBLE; //--- switch(type_op) { case BINARY_NODE_OP_RESTA: bit.double_value -= right; return true; case BINARY_NODE_OP_SUMA: bit.double_value += right; return true; case BINARY_NODE_OP_DIVICION: bit.double_value /= right; return true; case BINARY_NODE_OP_MUL: bit.double_value *= right; return true; default: m_last_err = MATH_EVAL_EXP_ERR_PARSE_TIPO_DE_OPERACION_INVALIDA_PARA_DBL; m_err_pos = pos2 - 1; return false; } } //--- return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CMathExpresionEvalStatic::RawOpSobreBits(const TokenMathExp &tokens[], const int type_op, BitInterpreter& bit, int& type, const BitInterpreter &bit2, const int type_2) { //--- Ambos son enteros if((type & type_2) == 1) { type = AST_LOGIC_NUMBER_INTEGER; switch(type_op) { case BINARY_NODE_OP_BIT_OR: bit.long_value = bit.long_value | bit2.long_value; return true; case BINARY_NODE_OP_BIT_XOR: bit.long_value = bit.long_value ^ bit2.long_value; return true; case BINARY_NODE_OP_BIT_AND: bit.long_value = bit.long_value & bit2.long_value; return true; case BINARY_NODE_OP_BIT_DEZPLAZAMIENTO_DRC: bit.long_value = bit.long_value >> bit2.long_value; return true; case BINARY_NODE_OP_BIT_DEZPLAZAMIENTO_IZQ: bit.long_value = bit.long_value << bit2.long_value; return true; case BINARY_NODE_OP_RESTA: bit.long_value = bit.long_value - bit2.long_value; return true; case BINARY_NODE_OP_SUMA: bit.long_value = bit.long_value + bit2.long_value; return true; case BINARY_NODE_OP_MODULO: bit.long_value = bit.long_value % bit2.long_value; return true; case BINARY_NODE_OP_DIVICION: bit.long_value = bit.long_value / bit2.long_value; return true; case BINARY_NODE_OP_MUL: bit.long_value = bit.long_value * bit2.long_value; return true; default: m_last_err = MATH_EVAL_EXP_ERR_PARSE_TIPO_DE_OPERACION_MATH_DESCONOCIDA; return false; } } else { //--- const double right = (type_2 == AST_LOGIC_NUMBER_DOUBLE) ? bit2.double_value : (double)bit2.long_value; if(type == AST_LOGIC_NUMBER_INTEGER) bit.double_value = (double)bit.long_value; //--- type = AST_LOGIC_NUMBER_DOUBLE; //--- switch(type_op) { case BINARY_NODE_OP_RESTA: bit.double_value -= right; return true; case BINARY_NODE_OP_SUMA: bit.double_value += right; return true; case BINARY_NODE_OP_DIVICION: bit.double_value /= right; return true; case BINARY_NODE_OP_MUL: bit.double_value *= right; return true; default: m_last_err = MATH_EVAL_EXP_ERR_PARSE_TIPO_DE_OPERACION_INVALIDA_PARA_DBL; return false; } } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CMathExpresionEvalStatic::ParsNoRes(const TokenMathExp &tokens[], BitInterpreter& it, int& type) { //--- // La idea con esta funcion es pase normal tal cual //--- Extraccion // Tipo de numero 1 int tkn_type = tokens[m_pos].type & 0xFFFF; if(tkn_type == AST_TOKEN_MATH_T_VALUE_OPERDOR_PARA_INI) { m_para_stack++; m_pos++; // Le sigue un numero //--- if((tokens[m_pos].type & 0xFFFF) != AST_TOKEN_MATH_T_VALUE_NUMBER) { m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_VALOR; m_err_pos = m_pos - 1; // previa a para return false; } } else if(tkn_type != AST_TOKEN_MATH_T_VALUE_NUMBER) { m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_VALOR; m_err_pos = m_pos; // previa a para return false; } const int pos_val_1 = m_pos++; // NOTA: Ahora mismo solo extraemos el tipo //--- // Check de len // neceistmos 2 tokens mas // [m_pos]: tipo op // [m_pos + 1]: valor | o para ini if(m_pos + 1 >= m_size) { // En este caso solo hay un valor asi que lo damos type = tokens[pos_val_1].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; if(type == AST_LOGIC_NUMBER_INTEGER) { it.long_value = tokens[pos_val_1].v.long_value; } else { it.double_value = tokens[pos_val_1].v.double_value; } // Listo retornamos return true; } // Contianuamos //--- // Tipó de op // Aqui esta el tipo de operacion matematica tkn_type = tokens[m_pos].type & 0xFFFF; if(tkn_type != AST_TOKEN_MATH_T_VALUE_OPERDOR_MATEMATICO) { if(m_para_stack == 0) { // Luego de un valor siempre debe de haber un operador matematico m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_OPERADOR_MATEMATICO; m_err_pos = m_pos; return false; } else if(tkn_type == AST_TOKEN_MATH_T_VALUE_OPERDOR_PARA_END) // Para end o operador { m_para_stack--; if((tkn_type = (tokens[++m_pos].type & 0xFFFF)) != AST_TOKEN_MATH_T_VALUE_OPERDOR_MATEMATICO) { m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_OPERADOR_LUEGO_DE_PARA; m_err_pos = m_pos - 1; return false; } // Ahora mismo estamos en el operador } // No hay parantesis nodo abierto.... } const int type_op_1 = tokens[m_pos++].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; //--- // Tipo de valor 2 tkn_type = tokens[m_pos].type & 0xFFFF; if(tkn_type == AST_TOKEN_MATH_T_VALUE_OPERDOR_PARA_INI) { //--- // En este caso lo que haremo sera parar lo que estuvimos haciendo... type = tokens[pos_val_1].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; if(type == AST_LOGIC_NUMBER_INTEGER) { it.long_value = tokens[pos_val_1].v.long_value; } else { it.double_value = tokens[pos_val_1].v.double_value; } //--- //const int locked = m_para_stack; BitInterpreter bit; int bit_type; // if(!ParsNoRes(tokens, bit, bit_type)) return false; //--- if(!RawOpSobreBits(tokens, type_op_1, it, type, bit, bit_type)) return false; //Print("VALUE 1: ", (type == AST_LOGIC_NUMBER_DOUBLE ? it.double_value : it.long_value), " TYPE: ", type); //Print("VALUE 2: ", (bit_type == AST_LOGIC_NUMBER_DOUBLE ? bit.double_value : bit.long_value), " TYPE: ", bit_type); //--- m_para_stack--; // Restamos la idea es acabar en ) // Asi quye ahora mismo pos apunta a ello m_pos=) if(m_pos + 2 < m_size) { // Necesitaremos dos espacios mas.. // Ahora misto esmtaos en = [)][Opeador][( Or Valor] const int t = (tokens[++m_pos].type & 0xFFFF); if(t == AST_TOKEN_MATH_T_VALUE_OPERDOR_PARA_END) { // Para end no hay nada mas que hacer return true; } else if(t != AST_TOKEN_MATH_T_VALUE_OPERDOR_MATEMATICO) { // Error luego de un ) tiene que haber un operador matematico (no un numoero valor) m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_OPERADOR_LUEGO_DE_PARA; m_err_pos = m_pos - 1; return false; } // Continuamos el parseo agregando al valor actual // pos++ para el siguiente valor seira [( or valor] return ParseInternalRaw(tokens, (tokens[m_pos++].type >> AST_LOGIC_EXTRA_TYPE_START_BIT), it, type); } else { return true; } //--- } else if(tkn_type != AST_TOKEN_MATH_T_VALUE_NUMBER) { m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_VALOR; m_err_pos = m_pos; // previa a para return false; } const int pos_val_2 = m_pos++; // Valores [Raw | Variable | Funcion Build-in | Funcion Custom] //--- // Operador 2... este es opcional en caso exista haremos recursion // 1. Ero comprobaremos si hay espacio para el token actual // neceistamos dos tokens // [op][val] (Dado que si hay un op si o si tinee uqe haber un val o parentesis) bool is_closed = false; if(m_pos + 1 >= m_size || (is_closed = ((tokens[m_pos].type & 0xFFFF) == AST_TOKEN_MATH_T_VALUE_OPERDOR_PARA_END))) { //if(is_closed) // m_para_stack--; // No hay espacio, solo tenemos V X V // Ahora la idea aqui seria calcular el resultado return RawOp(tokens, type_op_1, pos_val_1, pos_val_2, it, type); } //--- Extraemos op2 tkn_type = tokens[m_pos].type & 0xFFFF; if(tkn_type != AST_TOKEN_MATH_T_VALUE_OPERDOR_MATEMATICO) { // Luego de un valor siempre debe de haber un operador matematico m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_OPERADOR_MATEMATICO; m_err_pos = m_pos; return false; } const int type_op_2 = tokens[m_pos++].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; // m_pos = apunta ahora mismo al siguiente valor o () o ~ //--- En caso haya otro operador +\* etc.. // La idea sera ver cual es mas improtante el op1 uno actual?¿ o el otro if(type_op_2 > type_op_1) { //--- // El operador siguiente 2 es mas impornate que el actual... enotnces lo que haremos // Sera reducir primermente nuestor puntero de lectura para que apunte al valor 2 // [Valor][Op1][Valor2][Op2] //--- Asignamos un valor type = tokens[pos_val_1].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; if(type == AST_LOGIC_NUMBER_INTEGER) { it.long_value = tokens[pos_val_1].v.long_value; } else { it.double_value = tokens[pos_val_1].v.double_value; } //--- //Print("VALUE 1: ", (type == AST_LOGIC_NUMBER_DOUBLE ? it.double_value : it.long_value), " TYPE: ", type); //--- Continuamos con op2 BitInterpreter bit; int bit_type = tokens[pos_val_2].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; if(bit_type == AST_LOGIC_NUMBER_INTEGER) { bit.long_value = tokens[pos_val_2].v.long_value; } else { bit.double_value = tokens[pos_val_2].v.double_value; } if(!ParseInternalRaw(tokens, type_op_2, bit, bit_type)) return false; //Print("VALUE 2: ", (bit_type == AST_LOGIC_NUMBER_DOUBLE ? bit.double_value : bit.long_value), " TYPE: ", bit_type); //--- // Print("OP2: " , type_op_1); //--- // Junsta ambos bits y reotrn true return RawOpSobreBits(tokens, type_op_1, it, type, bit, bit_type); } else { // En este caso No lo es asi que aprovecharemos en dar valor a nuesto it actual if(!RawOp(tokens, type_op_1, pos_val_1, pos_val_2, it, type)) return false; // Luego continuareoms return ParseInternalRaw(tokens, type_op_2, it, type); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ // 200 - 10 + 2 * 2 // 190 + 2 * 2.0 bool CMathExpresionEvalStatic::ParseInternalRaw(const TokenMathExp& tokens[], const int math_op, BitInterpreter &val, int& type) { //--- // Ahora mismo nos ubicamos en la posicion de valor (Valor | () | ~) //--- Valor int tkn_type = tokens[m_pos].type & 0xFFFF; if(tkn_type == AST_TOKEN_MATH_T_VALUE_OPERDOR_PARA_INI) { //--- //const int locked = m_para_stack; BitInterpreter bit; int bit_type; // aumenta stak if(!ParsNoRes(tokens, bit, bit_type)) return false; //--- if(!RawOpSobreBits(tokens, math_op, val, type, bit, bit_type)) return false; //--- m_para_stack--; // Restamos la idea es acabar en ) // Asi quye ahora mismo pos apunta a ello m_pos=) if(m_pos + 2 < m_size) { // Necesitaremos dos espacios mas.. // Ahora misto esmtaos en = [)][Opeador][( Or Valor] const int t = (tokens[++m_pos].type & 0xFFFF); if(t == AST_TOKEN_MATH_T_VALUE_OPERDOR_PARA_END) { // Para end no hay nada mas que hacer return true; } else if(t != AST_TOKEN_MATH_T_VALUE_OPERDOR_MATEMATICO) { // Error luego de un ) tiene que haber un operador matematico (no un numoero valor) m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_OPERADOR_LUEGO_DE_PARA; m_err_pos = m_pos - 1; return false; } // Continuamos el parseo agregando al valor actual // pos++ para el siguiente valor seira [( or valor] return ParseInternalRaw(tokens, (tokens[m_pos++].type >> AST_LOGIC_EXTRA_TYPE_START_BIT), val, type); } else { return true; } } else if(tkn_type != AST_TOKEN_MATH_T_VALUE_NUMBER) { m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_VALOR; m_err_pos = m_pos; // previa a para return false; } const int pos_val_2 = m_pos++; // Valores [Raw | Variable | Funcion Build-in | Funcion Custom] //--- // Operador 2... este es opcional en caso exista haremos recursion // 1. Ero comprobaremos si hay espacio para el token actual // neceistamos dos tokens // [op][val] (Dado que si hay un op si o si tinee uqe haber un val o parentesis) bool is_closed = false; if(m_pos + 1 >= m_size || (is_closed = ((tokens[m_pos].type & 0xFFFF) == AST_TOKEN_MATH_T_VALUE_OPERDOR_PARA_END))) { // if(is_closed) // m_para_stack--; // No hay espacio, solo tenemos V X V // Ahora la idea aqui seria calcular el resultado return RawOpAply(tokens, math_op, pos_val_2, val, type); } //--- Extraemos op2 tkn_type = tokens[m_pos].type & 0xFFFF; if(tkn_type != AST_TOKEN_MATH_T_VALUE_OPERDOR_MATEMATICO) { // Luego de un valor siempre debe de haber un operador matematico m_last_err = MATH_EVAL_EXP_ERR_PARSE_SE_ESPERABA_UN_OPERADOR_MATEMATICO; m_err_pos = m_pos; return false; } const int type_op_2 = tokens[m_pos++].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; // m_pos = apunta ahora mismo al siguiente valor o () o ~ //--- En caso haya otro operador +\* etc.. // La idea sera ver cual es mas improtante el op1 uno actual?¿ o el otro if(type_op_2 > math_op) { // El operador siguiente 2 es mas impornate que el actual... enotnces lo que haremos // Sera reducir primermente nuestor puntero de lectura para que apunte al valor 2 // m_pos -= 2; // 2 espacios [Valor2][Op2][AHORA MISMO APUNTAMOS AQUI] // Luego BitInterpreter bit; int bit_type = tokens[pos_val_2].type >> AST_LOGIC_EXTRA_TYPE_START_BIT; if(bit_type == AST_LOGIC_NUMBER_INTEGER) { bit.long_value = tokens[pos_val_2].v.long_value; } else { bit.double_value = tokens[pos_val_2].v.double_value; } if(!ParseInternalRaw(tokens, type_op_2, bit, bit_type)) return false; //--- // Junsta ambos bits y reotrn true return RawOpSobreBits(tokens, math_op, val, type, bit, bit_type); } else { //--- Revisamos si aun hay tokens if(!RawOpAply(tokens, math_op, pos_val_2, val, type)) { // No hay espacio, solo tenemos V X V // Ahora la idea aqui seria calcular el resultado return false; } return ParseInternalRaw(tokens, type_op_2, val, type); // Continuamos } } //+------------------------------------------------------------------+ #endif // EXPRESSEVALBYLEO_SRC_MATHEVAL_STATIC_AST_MQH