#include #include #include #include #include #include #include #include #include #include "pilang.hpp" static inline bool is_sep(const char character) { return character == ' ' or character == '\t' or character == '\n'; } static inline bool is_end_of_arg(const char character) { return character == ',' or character == ';'; } namespace Pilang3 { std::unordered_map builtinCmds; Condition::Condition(const std::string& expression) { for (auto characterit = expression.begin(); characterit < expression.end(); characterit++) { if (*characterit == ',') { exprlen = std::distance(expression.begin(), characterit); break; } } } Evaluation::Evaluation(SharedEnvironment env, const std::string& expression, const bool autoeval, const bool subderef) { // Count off trailing spaces ssize_t tspaces = 0; for (const auto& character : expression) { if (is_sep(character)) { tspaces++; } else { break; } } // Get scope auto &scope = env->currScope(); // Parse arguments if any if (not expression.empty() and static_cast(expression.size()) != tspaces) { Variable thisarg; bool in_command_name = true; bool in_string = false; bool is_condition = false; bool newarg = false; bool escapenext = false; bool parsethis = true; condition::array condition_cache; std::string cache; for (auto characterit = expression.begin() + tspaces; characterit < expression.end(); characterit++) { char character = *characterit; // Start new command name if (in_command_name) { if (not is_sep(character) and character != ';') { cache.push_back(character); } else { command_name = cache; cache.clear(); if (character == ';') { exprlen = std::distance(expression.begin(), characterit); break; } else { in_command_name = false; newarg = true; } } } // Start new argument else if (newarg) { // Skip spaces if (is_sep(character)) continue; // Guess type by first character newarg = false; parsethis = true; if (std::isdigit(character) or character == '-' or character == '+') { // Integer thisarg.type = Variable::id_integer; goto parsechar; } else if (character == '"') { // String in_string = true; thisarg.type = Variable::id_string; } else if (character == '&') { // Expression thisarg.type = Variable::id_evaluation; } else if (character == '{' or character == '(') { // Function thisarg.type = Variable::id_function; goto parsechar; } else if (std::isalpha(character) or character == '_') { // Reference thisarg.type = Variable::id_reference; goto parsechar; } else { // Condition if (not is_condition) { is_condition = true; arguments.pop_back(); } parsethis = false; goto parsechar; } } else { parsechar: bool maybe_end_of_arg = is_end_of_arg(character); // Do whatever (depends on type) if (parsethis) switch (thisarg.type) { case Variable::id_integer: { // Integer bool c_is_sep = is_sep(character); if (c_is_sep and cache.empty()) continue; else if (maybe_end_of_arg or c_is_sep) { // End argument try { thisarg.data = integer_type(std::stol(cache)); } catch (std::invalid_argument&) { throw exceptions::BadInteger(); } newarg = true; } else { cache.push_back(character); } } break; case Variable::id_string: { // String if (character == '"' and not escapenext) { thisarg.data = cache; in_string = false; newarg = true; } else if (in_string and character == '\\' and not escapenext) { escapenext = true; } else if (in_string) { cache.push_back(character); } } break; case Variable::id_reference: { // Reference if (maybe_end_of_arg or is_sep(character)) { thisarg.data = cache; newarg = true; } else { cache.push_back(character); } } break; case Variable::id_evaluation: { // Check for exclaimation mark bool noexec = *characterit == '!'; if (noexec) { // Skip exclaimation mark if required characterit++; } // Evaluate auto evaluation = std::make_shared(env, std::string(characterit, expression.end()), autoeval, subderef); // Derefer if possible if (subderef) { evaluation->derefer(env); } // Execute if possible if (autoeval and not noexec) { thisarg = evaluation->execute(env); } else { thisarg.data = SharedEvaluationWBool{evaluation, not noexec}; } // Skip over expression in iteration character = *(characterit += evaluation->exprlen + 1); newarg = true; } break; case Variable::id_function: { auto thisfunc = std::make_shared(); if (character == '(') { thisfunc->new_scope = true; // Parse while (++characterit != expression.end()) { if (*characterit == ',' or *characterit == ')') { if (not cache.empty()) { thisfunc->argumentNames.push_back(cache); cache.clear(); } if (*characterit == ')') { break; } else { continue; } } else if (is_sep(*characterit)) { // Check if seperator is after ( or , if (cache.empty()) { // If yes; ignore character continue; } else { // If no, go on } } cache.push_back(*characterit); } // Skip spaces while (characterit + 1 != expression.end() and is_sep(*(++characterit))); // Check for { if (*characterit != '{') { throw exceptions::UnexpectedEndOfExpression(); } } // Parse expression itself characterit++; while (*characterit != '}') { // Evaluate SharedEvaluation evaluation; try { evaluation = std::make_shared(env, std::string(characterit, expression.end()), false, false); } catch (exceptions::emptyExpr&) { throw exceptions::UnexpectedEndOfExpression(); } thisfunc->evalChain.push_back(evaluation); // Skip over expression in iteration characterit += evaluation->exprlen + 1; if (characterit == expression.end()) { throw exceptions::UnexpectedEndOfExpression(); } } // Append character = *(++characterit); thisarg.data = thisfunc; newarg = true; } break; } if (not in_string) { // Continue building condition if (is_condition) { uint64_t cond_extends = condition::_none; // Check if too close to end of expression if (maybe_end_of_arg) { goto cond_append; } else if (characterit + 1 == expression.end()) { throw exceptions::UnexpectedEndOfExpression(); } // Equals else if (character == '=' and *(characterit + 1) == '=') { cond_extends = condition::equals; } // Not equals else if (character == '!' and *(characterit + 1) == '=') { cond_extends = condition::not_equals; } // Bigger than or equals else if (character == '>' and *(characterit + 1) == '=') { cond_extends = condition::biggerthanorequals; } // Smaller than or equals else if (character == '<' and *(characterit + 1) == '=') { cond_extends = condition::smallerthanorequals; } // Bigger than else if (character == '>') { cond_extends = condition::biggerthan; } // Smaller than else if (character == '<') { cond_extends = condition::smallerthan; } // And else if (character == '&' and *(characterit + 1) == '&') { cond_extends = condition::ncpp_and; } // Or else if (character == '|' and *(characterit + 1) == '|') { cond_extends = condition::ncpp_or; } // Not else if (character == '!') { cond_extends = condition::ncpp_not; } // Something else else { throw exceptions::langException(); // TODO: proper exception } // Extend if (cond_extends != condition::_none) { // Note: some conditional strings have just one character if (cond_extends != condition::biggerthan and cond_extends != condition::smallerthan and cond_extends != condition::ncpp_not) { characterit += 1; } // Append variable cond_append: condition_cache.push_back(new Variable(thisarg)); newarg = true; // If required end condition if (maybe_end_of_arg) { thisarg.data = condition_cache; thisarg.type = Variable::id_condition; condition_cache.clear(); is_condition = false; newarg = true; } else { condition_cache.push_back(reinterpret_cast(cond_extends)); } } } } // Actually end argument if required if (newarg) { cache.clear(); if (not is_condition) { arguments.push_back(thisarg); // Check if end of command was reached if (character == ';') { exprlen = std::distance(expression.begin(), characterit); break; } } } } } // Check exprlen if (exprlen < 0) { throw exceptions::UnexpectedEndOfExpression(); } // Find command auto res = Pilang3::builtinCmds.find(command_name); if (res == Pilang3::builtinCmds.end()) { // Find function auto res = scope.find(command_name); if (res == scope.end() or res->second->type != Variable::id_function) { command = {command_name, {nullptr, {}, false}}; } else { auto fnc = std::get(res->second->data); // Create argument list std::vector argtype_list; argtype_list.reserve(fnc->argumentNames.size()); for (auto counter = fnc->argumentNames.size(); counter != 0; counter--) { argtype_list.push_back(Variable::id_any); } // Command is function executor command = {"", { [fnc] (SharedEnvironment env, Cmdargs& args) { return fnc->execute(env, args); }, argtype_list, false }}; } } else { std::get<1>(command) = res->second; } } else { throw exceptions::emptyExpr(); } } template static bool anycomp(const uint64_t comparator, const Variable& a, const Variable& b) { T val[] = {std::get(a.data), std::get(b.data)}; switch (comparator) { using namespace condition; case equals: return val[0] == val[1]; break; case not_equals: return val[0] != val[1]; break; case biggerthan: return val[0] > val[1]; break; case smallerthan: return val[0] < val[1]; break; case biggerthanorequals: return val[0] >= val[1]; break; case smallerthanorequals: return val[0] <= val[1]; break; default: throw exceptions::langException(); // TODO: Proper exception } } void Variable::derefer(SharedEnvironment env, Variable& var) { switch (var.type) { case Variable::id_evaluation: { SharedEvaluation evaluation; bool exec; std::tie(evaluation, exec) = std::get(var.data); // Execute evaluation only if allowed (ie not &!) if (exec) { evaluation->derefer(env); var = evaluation->execute(env); } } break; case Variable::id_reference: { auto &scope = env->currScope(); auto res = scope.find(std::get(var.data)); if (res == scope.end()) { throw exceptions::NoSuchVariable(); } var = *res->second; } break; case Variable::id_condition: { auto condition = std::get(var.data); // We only support a size of 3 (as of now) if (condition.size() != 3) { throw exceptions::langException(); // TODO: Proper exception } // Derefer both sides Variable::derefer(env, *condition[0]); Variable::derefer(env, *condition[2]); // Both sides must be of the same type if (condition[0]->type != condition[2]->type) { throw exceptions::langException(); // TODO: Proper exception } auto type = condition[0]->type; auto comparator = reinterpret_cast(condition[1]); bool fres = false; // Compare integer if (type == Variable::id_integer) { fres = anycomp(comparator, *condition[0], *condition[2]); } // Compare string else if (type == Variable::id_string) { fres = anycomp(comparator, *condition[0], *condition[2]); } // Store result var.type = Variable::id_integer; var.data = fres; } break; default: return; } } void Evaluation::derefer(SharedEnvironment env) { for (auto& arg : arguments) { Variable::derefer(env, arg); } } Variable Evaluation::execute(SharedEnvironment env) { std::vector argstypes; bool anymoreopts; Cmdfnc func; Cmddesc desc; std::string name; std::tie(name, desc) = command; std::tie(func, argstypes, anymoreopts) = desc; if (func) { // Check args if ((arguments.size() > argstypes.size() and not anymoreopts) or (arguments.size() < argstypes.size())) { throw exceptions::BadArgs(); } for (uint16_t argidx = 0; argidx != argstypes.size(); argidx++) { if (argstypes[argidx] != Variable::id_any and argstypes[argidx] != arguments[argidx].type) { throw exceptions::BadArgs(); } } // Run return func(env, arguments); } else { // Find function auto &scope = env->currScope(); auto res = scope.find(command_name); if (res == scope.end() or res->second->type != Variable::id_function) { throw exceptions::NoSuchCommand(); } else { // Execute function return std::get(res->second->data)->execute(env, arguments); } } } Variable Function::execute(SharedEnvironment env, Cmdargs& args) { // Check if enough arguments were given if (args.size() != argumentNames.size()) { throw exceptions::BadArgs(); } // Go on Variable fres; // Apply arguments to new scope if (new_scope) { auto scope = env->currScope(); scope.reserve(args.size()); for (auto args_idx = args.size(); args_idx != 0; args_idx--) { scope[argumentNames[args_idx - 1]] = std::make_shared(args[args_idx - 1]); } env->variableScope.push(std::move(scope)); // Start new scope } // Copy, derefer and execute all evaluations for (auto& evaluation : evalChain) { auto thiseval = Evaluation(*evaluation); thiseval.derefer(env); fres = thiseval.execute(env); // Return on return if (fres.type == Variable::id_retval and new_scope) { auto fresdata = std::get(fres.data); if (new_scope) env->variableScope.pop(); // End scope return *fresdata; } } if (new_scope) env->variableScope.pop(); // End scope return Variable({ Variable::id_null, 0 }); } };