libcubescript/tools/repl.cc

476 lines
13 KiB
C++
Raw Normal View History

/* avoid silly complaints about fopen */
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS 1
#endif
#include <signal.h>
2021-03-20 08:22:15 +01:00
#include <cmath>
#include <cctype>
2021-03-20 08:22:15 +01:00
#include <cstring>
#include <cstdio>
2017-01-30 19:38:11 +01:00
#include <optional>
#include <memory>
#include <iterator>
2017-01-30 19:38:11 +01:00
2016-09-07 22:57:28 +02:00
#include <cubescript/cubescript.hh>
2021-03-23 23:32:25 +01:00
namespace cs = cubescript;
std::string_view version = "CubeScript 0.0.1";
/* util */
2021-03-20 08:22:15 +01:00
#if defined(_WIN32)
#include <io.h>
static bool stdin_is_tty() {
return _isatty(_fileno(stdin));
}
#else
#include <unistd.h>
static bool stdin_is_tty() {
return isatty(0);
}
#endif
/* line editing support */
inline std::string_view get_complete_cmd(std::string_view buf) {
std::string_view not_allowed = "\"/;()[] \t\r\n\0";
auto found = buf.find_first_of(not_allowed);
while (found != buf.npos) {
buf = buf.substr(found + 1, buf.size() - found - 1);
found = buf.find_first_of(not_allowed);
}
return buf;
}
inline std::string_view get_arg_type(char arg) {
switch (arg) {
case 'i':
return "int";
case 'b':
return "int_min";
case 'f':
return "float";
case 'F':
return "float_prev";
case 't':
return "any";
case 'E':
return "cond";
case 'N':
return "numargs";
case 's':
return "str";
case 'e':
return "block";
case 'r':
return "ident";
case '$':
return "self";
}
return "illegal";
}
inline void fill_cmd_args(std::string &writer, std::string_view args) {
char variadic = '\0';
int nrep = 0;
if (!args.empty() && ((args.back() == 'V') || (args.back() == 'C'))) {
variadic = args.back();
args.remove_suffix(1);
if (!args.empty() && isdigit(args.back())) {
nrep = args.back() - '0';
args.remove_suffix(1);
}
}
if (args.empty()) {
if (variadic == 'C') {
writer += "concat(...)";
} else if (variadic == 'V') {
writer += "...";
}
return;
}
int norep = int(args.size()) - nrep;
if (norep > 0) {
for (int i = 0; i < norep; ++i) {
if (i != 0) {
writer += ", ";
}
writer += get_arg_type(args.front());
args.remove_prefix(1);
}
}
if (variadic) {
if (norep > 0) {
writer += ", ";
}
if (variadic == 'C') {
writer += "concat(";
}
if (!args.empty()) {
if (args.size() > 1) {
writer += '{';
}
2017-01-30 19:38:11 +01:00
for (std::size_t i = 0; i < args.size(); ++i) {
if (i) {
writer += ", ";
}
writer += get_arg_type(args[i]);
}
if (args.size() > 1) {
writer += '}';
}
}
writer += "...";
if (variadic == 'C') {
writer += ")";
}
}
}
2021-03-23 23:29:32 +01:00
inline cs::command *get_hint_cmd(cs::state &cs, std::string_view buf) {
std::string_view nextchars = "([;";
auto lp = buf.find_first_of(nextchars);
if (lp != buf.npos) {
2021-03-23 23:29:32 +01:00
cs::command *cmd = get_hint_cmd(cs, buf.substr(1, buf.size() - 1));
if (cmd) {
return cmd;
}
}
std::size_t nsp = 0;
for (auto c: buf) {
if (!isspace(c)) {
break;
}
++nsp;
}
buf.remove_prefix(nsp);
std::string_view spaces = " \t\r\n";
auto p = buf.find_first_of(spaces);
if (p != buf.npos) {
buf = buf.substr(0, p);
}
if (!buf.empty()) {
2016-09-11 23:33:02 +02:00
auto cmd = cs.get_ident(buf);
return cmd ? cmd->get_command() : nullptr;
}
return nullptr;
}
2016-09-07 22:57:28 +02:00
#include "edit_linenoise.hh"
#include "edit_readline.hh"
#include "edit_fallback.hh"
2016-09-07 22:46:22 +02:00
/* usage */
void print_usage(std::string_view progname, bool err) {
2021-03-20 08:22:15 +01:00
std::fprintf(
err ? stderr : stdout,
"Usage: %s [options] [file]\n"
2016-09-07 22:46:22 +02:00
"Options:\n"
" -e str run string \"str\"\n"
" -i enter interactive mode after the above\n"
" -v show version information\n"
" -h show this message\n"
" -- stop handling options\n"
" - execute stdin and stop handling options"
2021-03-20 08:22:15 +01:00
"\n",
progname.data()
2016-09-07 22:46:22 +02:00
);
}
void print_version() {
2021-03-20 08:22:15 +01:00
printf("%s\n", version.data());
2016-09-07 22:46:22 +02:00
}
2021-03-23 23:29:32 +01:00
static cs::state *scs = nullptr;
static void do_sigint(int n) {
/* in case another SIGINT happens, terminate normally */
signal(n, SIG_DFL);
scs->set_call_hook([](cs::state &css) {
css.set_call_hook(nullptr);
throw cs::error{css, "<execution interrupted>"};
2016-09-11 23:33:02 +02:00
});
}
2021-03-18 00:03:30 +01:00
/* an example of what var printer would look like in real usage */
static void repl_print_var(cs::state const &, cs::global_var const &var) {
2021-03-18 00:03:30 +01:00
switch (var.get_type()) {
2021-03-23 23:29:32 +01:00
case cs::ident_type::IVAR: {
auto &iv = static_cast<cs::integer_var const &>(var);
2021-03-18 00:03:30 +01:00
auto val = iv.get_value();
2021-03-23 23:29:32 +01:00
if (!(iv.get_flags() & cs::IDENT_FLAG_HEX) || (val < 0)) {
2021-03-20 08:22:15 +01:00
std::printf("%s = %d\n", iv.get_name().data(), val);
2021-03-18 00:03:30 +01:00
} else if (iv.get_val_max() == 0xFFFFFF) {
2021-03-20 08:22:15 +01:00
std::printf(
"%s = 0x%.6X (%d, %d, %d)\n",
iv.get_name().data(),
2021-03-18 00:03:30 +01:00
val, (val >> 16) & 0xFF, (val >> 8) & 0xFF, val & 0xFF
);
} else {
2021-03-20 08:22:15 +01:00
std::printf("%s = 0x%X\n", iv.get_name().data(), val);
2021-03-18 00:03:30 +01:00
}
break;
}
2021-03-23 23:29:32 +01:00
case cs::ident_type::FVAR: {
auto &fv = static_cast<cs::float_var const &>(var);
2021-03-18 00:03:30 +01:00
auto val = fv.get_value();
2021-03-20 08:22:15 +01:00
if (std::floor(val) == val) {
std::printf("%s = %.1f", fv.get_name().data(), val);
} else {
std::printf("%s = %.7g", fv.get_name().data(), val);
}
2021-03-18 00:03:30 +01:00
break;
}
2021-03-23 23:29:32 +01:00
case cs::ident_type::SVAR: {
auto &sv = static_cast<cs::string_var const &>(var);
auto val = std::string_view{sv.get_value()};
if (val.find('"') == val.npos) {
2021-03-20 08:22:15 +01:00
std::printf("%s = \"%s\"", sv.get_name().data(), val.data());
2021-03-18 00:03:30 +01:00
} else {
2021-03-20 08:22:15 +01:00
std::printf("%s = [%s]", sv.get_name().data(), val.data());
2021-03-18 00:03:30 +01:00
}
break;
}
default:
break;
}
}
2021-03-23 23:29:32 +01:00
static bool do_run_file(
cs::state &cs, std::string_view fname, cs::any_value &ret
) {
2021-03-20 08:22:15 +01:00
FILE *f = std::fopen(fname.data(), "rb");
if (!f) {
return false;
}
2021-03-20 08:22:15 +01:00
std::fseek(f, 0, SEEK_END);
auto len = std::ftell(f);
std::fseek(f, 0, SEEK_SET);
auto buf = std::make_unique<char[]>(len + 1);
if (!buf) {
2021-03-20 08:22:15 +01:00
std::fclose(f);
return false;
}
2021-03-20 08:22:15 +01:00
if (std::fread(buf.get(), 1, len, f) != std::size_t(len)) {
std::fclose(f);
return false;
}
2021-03-20 08:22:15 +01:00
buf[len] = '\0';
2021-03-20 08:22:15 +01:00
cs.run(std::string_view{buf.get(), std::size_t(len)}, ret, fname);
return true;
}
2021-03-23 23:29:32 +01:00
static bool do_call(cs::state &cs, std::string_view line, bool file = false) {
cs::any_value ret{cs};
2016-09-11 23:33:02 +02:00
scs = &cs;
signal(SIGINT, do_sigint);
try {
2016-09-07 22:46:22 +02:00
if (file) {
if (!do_run_file(cs, line, ret)) {
2021-03-20 08:22:15 +01:00
std::fprintf(stderr, "cannot read file: %s\n", line.data());
2016-09-07 22:46:22 +02:00
}
} else {
cs.run(line, ret);
}
2021-03-23 23:29:32 +01:00
} catch (cs::error const &e) {
signal(SIGINT, SIG_DFL);
2016-09-11 23:33:02 +02:00
scs = nullptr;
std::string_view terr = e.what();
auto col = terr.find(':');
2016-09-15 20:55:58 +02:00
bool is_lnum = false;
if (col != terr.npos) {
auto pre = terr.substr(0, col);
auto it = std::find_if(
pre.begin(), pre.end(),
2017-03-31 03:18:26 +02:00
[](auto c) { return !isdigit(c); }
);
is_lnum = (it == pre.end());
terr = terr.substr(col + 2, terr.size() - col - 2);
2016-09-10 16:22:27 +02:00
}
if (!file && ((terr == "missing \"]\"") || (terr == "missing \")\""))) {
return true;
}
2021-03-20 08:22:15 +01:00
std::printf(
"%s%s\n", !is_lnum ? "stdin: " : "stdin:", e.what().data()
);
if (e.get_stack().get()) {
std::string str;
2021-03-23 23:29:32 +01:00
cs::print_stack(std::back_inserter(str), e.get_stack());
2021-03-20 08:22:15 +01:00
std::printf("%s\n", str.data());
2016-09-10 20:45:04 +02:00
}
return false;
}
signal(SIGINT, SIG_DFL);
2016-09-11 23:33:02 +02:00
scs = nullptr;
2021-03-23 23:29:32 +01:00
if (ret.get_type() != cs::value_type::NONE) {
2021-03-20 08:22:15 +01:00
std::printf("%s\n", std::string_view{ret.get_str()}.data());
}
return false;
}
2021-03-23 23:29:32 +01:00
static void do_tty(cs::state &cs) {
2016-09-02 19:01:25 +02:00
auto prompt = cs.new_svar("PROMPT", "> ");
auto prompt2 = cs.new_svar("PROMPT2", ">> ");
bool do_exit = false;
cs.new_command("quit", "", [&do_exit](auto &, auto, auto &) {
do_exit = true;
});
2021-03-20 08:22:15 +01:00
std::printf("%s (REPL mode)\n", version.data());
for (;;) {
2016-09-11 23:33:02 +02:00
auto line = read_line(cs, prompt);
if (!line) {
return;
}
2017-01-25 01:57:33 +01:00
auto lv = std::move(line.value());
if (lv.empty()) {
continue;
}
while ((lv.back() == '\\') || do_call(cs, lv)) {
bool bsl = (lv.back() == '\\');
if (bsl) {
lv.resize(lv.size() - 1);
}
2016-09-11 23:33:02 +02:00
auto line2 = read_line(cs, prompt2);
if (!line2) {
return;
}
2016-09-10 16:22:27 +02:00
if (!bsl || (line2.value() == "\\")) {
lv += '\n';
}
lv += line2.value();
}
2016-09-11 23:33:02 +02:00
add_history(cs, lv);
if (do_exit) {
return;
}
}
}
2016-09-07 22:46:22 +02:00
int main(int argc, char **argv) {
2021-03-23 23:29:32 +01:00
cs::state gcs;
2021-03-18 00:03:30 +01:00
gcs.set_var_printer(repl_print_var);
2016-09-11 23:33:02 +02:00
gcs.init_libs();
2016-09-07 22:46:22 +02:00
gcs.new_command("exec", "s", [](auto &css, auto args, auto &) {
2021-03-17 21:57:47 +01:00
auto file = args[0].get_str();
cs::any_value val{css};
bool ret = do_run_file(css, file, val);
2016-09-07 22:46:22 +02:00
if (!ret) {
2021-03-23 23:29:32 +01:00
throw cs::error(
css, "could not run file \"%s\"", file.data()
2016-09-15 21:27:14 +02:00
);
2016-09-07 22:46:22 +02:00
}
});
gcs.new_command("echo", "C", [](auto &, auto args, auto &) {
2021-03-20 08:22:15 +01:00
std::printf("%s\n", std::string_view{args[0].get_str()}.data());
2016-09-07 22:46:22 +02:00
});
int firstarg = 0;
2016-10-06 20:29:07 +02:00
bool has_inter = false, has_ver = false, has_help = false;
char const *has_str = nullptr;
2016-09-07 22:46:22 +02:00
for (int i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
firstarg = i;
goto endargs;
}
switch (argv[i][1]) {
case '-':
if (argv[i][2] != '\0') {
firstarg = -1;
goto endargs;
}
firstarg = (argv[i + 1] != nullptr) ? (i + 1) : 0;
goto endargs;
case '\0':
firstarg = i;
goto endargs;
case 'i':
if (argv[i][2] != '\0') {
firstarg = -1;
goto endargs;
}
has_inter = true;
break;
case 'v':
if (argv[i][2] != '\0') {
firstarg = -1;
goto endargs;
}
has_ver = true;
break;
case 'h':
if (argv[i][2] != '\0') {
firstarg = -1;
goto endargs;
}
has_help = true;
break;
case 'e':
if (argv[i][2] == '\0') {
++i;
if (!argv[i]) {
firstarg = -1;
goto endargs;
2016-10-06 20:29:07 +02:00
} else {
has_str = argv[i];
2016-09-07 22:46:22 +02:00
}
2016-10-06 20:29:07 +02:00
} else {
has_str = argv[i] + 2;
2016-09-07 22:46:22 +02:00
}
break;
default:
firstarg = -1;
goto endargs;
}
}
endargs:
if (firstarg < 0) {
print_usage(argv[0], true);
return 1;
}
if (has_ver && !has_inter) {
print_version();
}
if (has_help) {
print_usage(argv[0], false);
return 0;
}
2016-10-06 20:29:07 +02:00
if (has_str) {
do_call(gcs, has_str);
2016-09-07 22:46:22 +02:00
}
if (firstarg) {
2016-09-11 23:33:02 +02:00
do_call(gcs, argv[firstarg], true);
2016-09-07 22:46:22 +02:00
}
if (!firstarg && !has_str && !has_ver) {
if (stdin_is_tty()) {
2016-09-11 23:33:02 +02:00
init_lineedit(gcs, argv[0]);
do_tty(gcs);
2016-09-07 22:46:22 +02:00
return 0;
} else {
2017-01-30 01:18:55 +01:00
std::string str;
2021-03-20 08:22:15 +01:00
for (signed char c = '\0'; (c = std::fgetc(stdin)) != EOF;) {
2016-09-07 22:46:22 +02:00
str += c;
}
2016-09-11 23:33:02 +02:00
do_call(gcs, str);
2016-09-07 22:46:22 +02:00
}
}
if (has_inter) {
if (stdin_is_tty()) {
2016-09-11 23:33:02 +02:00
init_lineedit(gcs, argv[0]);
do_tty(gcs);
2016-09-07 22:46:22 +02:00
}
return 0;
}
}