mirror of
https://gitlab.com/niansa/pilang3.git
synced 2025-03-06 20:49:20 +01:00
494 lines
22 KiB
C++
494 lines
22 KiB
C++
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <unordered_map>
|
|
#include <variant>
|
|
#include <exception>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <functional>
|
|
#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<std::string, Cmddesc> 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<ssize_t>(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<Evaluation>(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<Function>();
|
|
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<Evaluation>(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<Variable*>(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<SharedFunction>(res->second->data);
|
|
// Create argument list
|
|
std::vector<Variable::Type> 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<typename T>
|
|
static bool anycomp(const uint64_t comparator, const Variable& a, const Variable& b) {
|
|
T val[] = {std::get<T>(a.data), std::get<T>(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<SharedEvaluationWBool>(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<std::string>(var.data));
|
|
if (res == scope.end()) {
|
|
throw exceptions::NoSuchVariable();
|
|
}
|
|
var = *res->second;
|
|
} break;
|
|
case Variable::id_condition: {
|
|
auto condition = std::get<condition::array>(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<uint64_t>(condition[1]);
|
|
bool fres = false;
|
|
// Compare integer
|
|
if (type == Variable::id_integer) {
|
|
fres = anycomp<integer_type>(comparator, *condition[0], *condition[2]);
|
|
}
|
|
// Compare string
|
|
else if (type == Variable::id_string) {
|
|
fres = anycomp<std::string>(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<Variable::Type> 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<SharedFunction>(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<Variable>(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<SharedVariable>(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
|
|
});
|
|
}
|
|
};
|