/* This file is part of pilang. pilang is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. pilang is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with pilang. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include // Return type defs std::string success = "Success"; std::string failed = "Failed"; std::string nosuchcommand = "No such command"; std::string nosuchvariable = "No such variable"; std::string badarguments = "Bad arguments"; // Exception defs class markergoto : public std::string {}; class pilerror : public std::string {}; std::string markergotoname; // Environment defs std::vector cmdargs; std::map variables; std::map markers; bool interactivecli; bool catcherrs = false; std::string lasterr = ""; std::vector markersetqueue; std::string execline(std::string commandstr); // Restart program on segmenation fault void sigsegv_handler(int signal) { std::cerr << std::endl << "Pilang crashed: signal " << signal << "=SIGSEGV (memory error)" << std::endl; exit(177); } // Helper functions bool is_float(std::string s) { std::istringstream iss(s); float f; iss >> std::noskipws >> f; return iss.eof() && !iss.fail(); } std::string str_lower(std::string string) { std::transform(string.begin(), string.end(), string.begin(), ::tolower); return string; } std::string str_upper(std::string string) { std::transform(string.begin(), string.end(), string.begin(), ::toupper); return string; } std::string ltrim(const std::string& s) { static const std::regex lws{"^[[:space:]]*", std::regex_constants::extended}; return std::regex_replace(s, lws, ""); } std::string rtrim(const std::string& s) { static const std::regex tws{"[[:space:]]*$", std::regex_constants::extended}; return std::regex_replace(s, tws, ""); } std::string trim(const std::string& s) { return ltrim(rtrim(s)); } std::vector strsplit(std::string_view s, char delimiter, std::vector::size_type times = 0) { std::vector to_return; decltype(s.size()) start = 0, finish = 0; while ((finish = s.find_first_of(delimiter, start)) != std::string_view::npos) { to_return.emplace_back(s.substr(start, finish - start)); start = finish + 1; if (to_return.size() == times) { break; } } to_return.emplace_back(s.substr(start)); return to_return; } void do_markersetqueue(int currline) { for (const std::string & markername : markersetqueue) { markers[markername] = currline; } markersetqueue = {}; } std::string get_rstring(std::string rstring) { if (rstring == nosuchcommand or rstring == nosuchvariable or rstring == badarguments) { lasterr = rstring; if (catcherrs) throw pilerror(); } return rstring; } // Builtin functions std::string cmd_ret(std::vector args) { std::string retstring; for (const std::string & arg : args){ retstring.append(arg); } return retstring; } std::string cmd_catch(std::vector args) { if (args.size() != 1) { return badarguments; } std::string boolstr = *args.begin(); if (str_lower(boolstr) == "true") catcherrs = true; else if (str_lower(boolstr) == "false") catcherrs = false; else return badarguments; return success; } std::string cmd_get(std::vector args) { if (args.size() != 2) { return badarguments; } auto argsit = args.begin(); std::string string = *argsit; argsit++; long unsigned int position = std::stoi(*argsit); if (position < string.length()) { std::string res; res.push_back(string.at(position)); return res; } else { return badarguments; } } std::string cmd_len(std::vector args) { int result = 0; for (const std::string & arg : args){ result += arg.length(); } return std::to_string(result); } std::string cmd_exit(std::vector args) { //Check amount of arguments if (args.size() != 1) { return badarguments; } std::string exitcode = *args.begin(); if (is_float(exitcode)) _exit(std::stoi(exitcode)); else return badarguments; } std::string cmd_marker(std::vector args) { // Check amount of arguments if (args.size() != 2) { return badarguments; } // Get arguments auto argsit = args.begin(); std::string action = *argsit; argsit++; std::string markername = *argsit; // Set or goto if (action == "goto") { markergotoname = markername; throw markergoto(); } else if (action == "set") { markersetqueue.push_back(markername); } else { return badarguments; } return success; } std::string cmd_printnnl(std::vector args) { std::cout << cmd_ret(args) << std::flush; return success; } std::string cmd_print(std::vector args) { auto result = cmd_printnnl(args); std::cout << std::endl; return result; } std::string cmd_input(std::vector args) { std::string hint = cmd_ret(args); cmd_printnnl({hint}); std::string result; std::getline(readkbd(hint), result); return result; } std::string cmd_set(std::vector args) { //Check amount of arguments if (args.size() != 2) { return badarguments; } // Get arguments auto argsit = args.begin(); std::string key = *argsit; argsit++; std::string value = *argsit; // Store in environment variables[key] = value; // Return success return success; } std::string cmd_append(std::vector args) { //Check amount of arguments if (args.size() < 2) { return badarguments; } // Get arguments auto argsit = args.begin(); std::string variablename = *argsit; argsit++; for (; argsit != args.end(); argsit++) variables[variablename].append(*argsit); return success; } std::string cmd_incdec(bool type, std::vector args) { //Check amount of arguments if (args.size() < 1 or args.size() > 2) { return badarguments; } // Get arguments auto argsit = args.begin(); std::string variablename = *argsit; float incby; if (args.size() == 2) { argsit++; incby = std::atof((*argsit).c_str()); } else { incby = 1; } // Modify variable if it exists if (variables.find(variablename) == variables.end()) { return nosuchvariable; } else { float currval = std::atof(variables[variablename].c_str()); if (type) currval += incby; else currval -= incby; variables[variablename] = std::to_string(currval); } return success; } std::string cmd_del(std::vector args) { if (args.size() < 1) { return badarguments; } for (const std::string & arg : args) { auto varit = variables.find(arg); if (varit != variables.end()) { variables.erase(variables.find(arg)); } } return success; } std::string cmd_eval(std::vector args) { if (args.size() < 1) { return badarguments; } std::string result; for (const std::string & arg : args) { result = execline(arg); } return result; } std::string cmd_cmp(std::vector args) { bool matches; //Check amount of arguments if (args.size() < 4) { return badarguments; } // Get arguments auto argsit = args.begin(); std::string action = *argsit; argsit++; std::string var1 = *argsit; argsit++; std::string var2 = *argsit; argsit++; // Compare if (action == "equal") matches = var1 == var2; else if (action == "nonequal") matches = var1 != var2; else if (action == "bigger") matches = std::atof(var1.c_str()) > std::atof(var2.c_str()); else if (action == "smaller") matches = std::atof(var1.c_str()) < std::atof(var2.c_str()); else if (action == "isnum" or action == "isnotnum") { matches = is_float(var1) and is_float(var2); if (action == "isnotnum") matches = !matches; } else return badarguments; // Eval on match if (matches) { std::string result; while (argsit != args.end()) { result = execline(*argsit); argsit++; } return result; } else { return failed; } } std::string cmd_lowerupper(bool type, std::vector args) { if (args.size() < 1) { return badarguments; } std::string result; for (auto argsit = args.begin(); argsit != args.end(); argsit++) { if (type) result.append(str_upper(*argsit)); else result.append(str_lower(*argsit)); } return result; } std::string cmd_addsub(bool type, std::vector args) { //Check amount of arguments if (args.size() < 2) { return badarguments; } // Initialise variables bool asint = false; float numarg; // Get arguments auto argsit = args.begin(); if (*argsit == "asint") { asint = true; argsit++; } // Initialise initial result if (!is_float(*argsit)) return badarguments; float result = std::atof((*argsit).c_str()); argsit++; // Calculaté for (; argsit != args.end(); argsit++) { if (!is_float(*argsit)) return badarguments; numarg = std::atof((*argsit).c_str()); if (type) result += numarg; else result -= numarg; } // Return result if (asint) return std::to_string((int) result); else return std::to_string(result); } std::string cmd_sleep(std::vector args) { // Check amount of arguments if (args.size() != 1) { return badarguments; } // Check argument auto argsit = args.begin(); if (!is_float(*argsit)) return badarguments; // Get argument float sleepsecs = std::atof((*argsit).c_str()); sleep(sleepsecs); return success; } std::string cmd_argv(std::vector args) { // Check amount of arguments if (args.size() != 1) { return badarguments; } // Check argument auto argsit = args.begin(); if (!is_float(*argsit)) return badarguments; long unsigned int argnum = std::atoi((*argsit).c_str()); if (argnum >= cmdargs.size()) return badarguments; // Get argument return cmdargs[argnum]; } std::string cmd_argc(std::vector args) { return std::to_string(cmdargs.size()); } std::string run_command(std::string command, std::vector args) { //std::cout << "Running command:" << command << "!" << std::endl; if (command == "ret") return cmd_ret(args); else if (command == "catch") return cmd_catch(args); else if (command == "get") return cmd_get(args); else if (command == "len") return cmd_len(args); else if (command == "exit") return cmd_exit(args); else if (command == "marker") return cmd_marker(args); else if (command == "print") return cmd_print(args); else if (command == "printnnl") return cmd_printnnl(args); else if (command == "input") return cmd_input(args); else if (command == "set") return cmd_set(args); else if (command == "append") return cmd_append(args); else if (command == "inc") return cmd_incdec(true, args); else if (command == "dec") return cmd_incdec(false, args); else if (command == "del") return cmd_del(args); else if (command == "eval") return cmd_eval(args); else if (command == "cmp") return cmd_cmp(args); else if (command == "lower") return cmd_lowerupper(false, args); else if (command == "upper") return cmd_lowerupper(true, args); else if (command == "add") return cmd_addsub(true, args); else if (command == "sub") return cmd_addsub(false, args); else if (command == "sleep") return cmd_sleep(args); else if (command == "argv") return cmd_argv(args); else if (command == "argc") return cmd_argc(args); else return nosuchcommand; } // Interpreter initialiser void init_interpreter(int argc, char *argv[]) { // Load modules from modules directory //struct dirent *dp; //DIR *dfd; //char filename_qfd[264]; //char moduledir [] = "modules"; //dfd = opendir(moduledir); //while ((dp = readdir(dfd)) != NULL) { // sprintf(filename_qfd , "%s/%s",moduledir,dp->d_name); // // Skip . and .. // if (strcmp(dp->d_name,"..") or strcmp(dp->d_name,".")) // continue; // // Load file // std::cout << "Module loading not yet implemented for: " << filename_qfd << std::endl; //} // Generate list of arguments for (int elemnum = 0; elemnum < argc; elemnum++) { cmdargs.push_back(std::string(argv[elemnum])); } } std::string execline(std::string commandstr) { commandstr = ltrim(commandstr); if (commandstr[0] == '#') return success; //std::cout << "$ " << commandstr << std::endl; if (rtrim(commandstr) == "") { // Command is empty return commandstr; } else if (commandstr.rfind('~', 0) == 0) { // Command is a variable commandstr.erase(0, 1); // Check if variable exists if (variables.find(commandstr) == variables.end()) { return nosuchvariable; } else { return variables[commandstr]; } } else { // Command is an actual command std::vector cmdsplit = strsplit(commandstr, ' ', 1); auto cmdsplit_it = cmdsplit.begin(); std::string command = *cmdsplit_it; std::string argsstr = ""; cmdsplit_it++; if (cmdsplit.size() == 2) { argsstr = *cmdsplit_it; argsstr.push_back(','); } std::vector args; if (argsstr != ",") { std::string currarg = ""; bool instring = false; bool arginstring = false; bool charig = false; bool skipall = false; for (std::string::iterator currchar = argsstr.begin(); currchar < argsstr.end(); currchar++) { if (skipall and *currchar != ',') { continue; } else if (*currchar == '"' and !skipall) { instring = !instring; if (currarg == "") arginstring = true; } else if (*currchar == '#' and !charig and !instring) { *(currchar + 1) = ','; skipall = true; } else if (*currchar == ',' and !charig and !instring) { if (currarg.length() != 0 and *currarg.begin() == '$' and !arginstring) { currarg.erase(0, 1); currarg = execline(currarg); } args.push_back(currarg); currarg = ""; arginstring = false; } else if (*currchar == '\\' and !charig) { charig = true; } else { currarg.push_back(*currchar); charig = false; } } } //std::cout << "Args len: " << args.size() << std::endl; return get_rstring(run_command(command, args)); } } // Main CLI int _main(int argc, char *argv[]) { using namespace std; signal(SIGSEGV, sigsegv_handler); std::string line; if (argc < 2) { // Start interactive CLI // Show copyright note cout << "pilang Copyright (C) 2020 niansa" << endl; cout << "This program comes with ABSOLUTELY NO WARRANTY; for details type `warranty'." << endl; cout << "This is free software, and you are welcome to redistribute it" << endl; cout << "under certain conditions; type `license' for details." << endl; cout << endl; // Initialise stuff init_interpreter(argc, argv); interactivecli = true; // Input loop while (true) { line = ""; cout << ">>> " << flush; std::getline(readkbd("Type in command..."), line); try { cout << execline(line) << endl; } catch (markergoto) { // Catch marker goto cerr << "Unable to use markers in CLI mode!" << endl; } catch (pilerror) { cerr << "In CLI: " << lasterr << " (catched)" << endl; } } } else { // Read from argv[1] into vector std::ifstream scriptfile(argv[1]); std::vector lines; std::string line; while (std::getline(scriptfile, line)) { lines.push_back(line); if (scriptfile.eof()) { break; } // EOF } scriptfile.close(); // Initialise stuff init_interpreter(argc, argv); interactivecli = false; // Add initial marker in line 0 markersetqueue.push_back("start"); do_markersetqueue(0); // Execute vector line-by-line auto linesit = lines.begin(); for (int currline = 0; linesit != lines.end(); currline++) { try { execline(*linesit); do_markersetqueue(currline); // Do markerqueue } catch (markergoto) { // Catch marker goto do_markersetqueue(currline); // Do markerqueue even if an exception occured if (markers.find(markergotoname) != markers.end()) { currline = markers[markergotoname]; linesit = lines.begin() + currline; } else { cerr << "No such marker: \"" << markergotoname << "\" (critical)" << endl; return 78; } } catch (pilerror) { cerr << "Line " << currline << ": " << lasterr << " (catched)" << endl; return 77; } linesit++; } } return 0; }