diff --git a/builtins.cpp b/builtins.cpp index 80b8ee2..dce601f 100644 --- a/builtins.cpp +++ b/builtins.cpp @@ -4,6 +4,7 @@ #include "fdset.h" #include "value.h" #include "environment.h" +#include "error.h" #include #include @@ -279,22 +280,14 @@ static int export_common(Environment &env, bool export_or_unexport, const std::v const char *name = export_or_unexport ? "Export" : "Unexport"; - struct { - int _r = 0; - int _s = 0; - } flags; + bool _r = false; + bool _s = false; bool error = false; - std::vector argv = getopt(tokens, [&](char c){ - switch(c) { - case 'r': - case 'R': - flags._r = true; - break; - case 's': - case 'S': - flags._s = true; - break; + auto argv = getopt(tokens, [&](char c){ + switch(tolower(c)) { + case 'r': _r = true; break; + case 's': _s = true; break; default: fdprintf(stderr, "### %s - \"-%c\" is not an option.\n", name, c); error = true; @@ -308,7 +301,7 @@ static int export_common(Environment &env, bool export_or_unexport, const std::v } if (argv.empty()) { - if (flags._r && flags._s) goto conflict; + if (_r && _s) goto conflict; // list of exported vars. // -r will generate unexport commands for exported variables. @@ -320,14 +313,14 @@ static int export_common(Environment &env, bool export_or_unexport, const std::v for (const auto &kv : env) { const std::string& vname = kv.first; if (kv.second == export_or_unexport) - fdprintf(stdout, "%s%s\n", flags._s ? "" : name, quote(vname).c_str()); + fdprintf(stdout, "%s%s\n", _s ? "" : name, quote(vname).c_str()); } return 0; } else { // mark as exported. - if (flags._r || flags._s) goto conflict; + if (_r || _s) goto conflict; for (std::string s : argv) { auto iter = env.find(s); @@ -509,12 +502,9 @@ int builtin_directory(Environment &env, const std::vector &tokens, bool error = false; std::vector argv = getopt(tokens, [&](char c){ - switch(c) + switch(tolower(c)) { - case 'q': - case 'Q': - q = true; - break; + case 'q': q = true; break; default: fdprintf(stderr, "### Directory - \"-%c\" is not an option.\n", c); error = true; @@ -585,24 +575,12 @@ int builtin_exists(Environment &env, const std::vector &tokens, con std::vector argv = getopt(tokens, [&](char c){ switch(tolower(c)) { - case 'a': - _a = true; - break; - case 'd': - _d = true; - break; - case 'f': - _f = true; - break; - case 'n': - _n = true; - break; - case 'q': - _q = true; - break; - case 'w': - _w = true; - break; + case 'a': _a = true; break; + case 'd': _d = true; break; + case 'f': _f = true; break; + case 'n': _n = true; break; + case 'q': _q = true; break; + case 'w': _w = true; break; default: fdprintf(stderr, "### Exists - \"-%c\" is not an option.\n", c); error = true; @@ -773,15 +751,15 @@ int builtin_evaluate(Environment &env, std::vector &&tokens, const fdmask int builtin_which(Environment &env, const std::vector &tokens, const fdmask &fds) { // which [-a] [-p] [command] - bool a = false; - bool p = false; + bool _a = false; + bool _p = false; bool error = false; std::vector argv = getopt(tokens, [&](char c){ - switch(c) + switch(tolower(c)) { - case 'a': case 'A': a = true; break; - case 'p': case 'P': p = true; break; + case 'a': _a = true; break; + case 'p': _p = true; break; default: fdprintf(stderr, "### Which - \"-%c\" is not an option.\n", c); @@ -832,7 +810,7 @@ int builtin_which(Environment &env, const std::vector &tokens, cons } for(; ss; ++ss) { - if (p) fdprintf(stderr, "checking %s\n", ss->c_str()); + if (_p) fdprintf(stderr, "checking %s\n", ss->c_str()); std::error_code ec; fs::path p(ToolBox::MacToUnix(ss->c_str())); @@ -841,13 +819,13 @@ int builtin_which(Environment &env, const std::vector &tokens, cons if (fs::exists(p, ec)) { found = true; fdprintf(stdout, "%s\n", quote(p).c_str()); - if (!a) break; + if (!_a) break; } } // check builtins... - if (!found || a) { + if (!found || _a) { static const char *builtins[] = { "aboutbox", @@ -951,9 +929,7 @@ int builtin_version(Environment &env, const std::vector &tokens, co auto argv = getopt(tokens, [&](char c){ switch(tolower(c)) { - case 'v': - _v = true; - break; + case 'v': _v = true; break; default: fdprintf(stderr, "### Version - \"-%c\" is not an option.\n", c); error = true; @@ -1039,6 +1015,46 @@ int builtin_false(Environment &, const std::vector &, const fdmask } +int builtin_quit(Environment &, const std::vector &tokens, const fdmask &fds) { + + bool error = false; + bool _y = false; + bool _n = false; + bool _c = false; + + auto argv = getopt(tokens, [&](char c){ + switch(tolower(c)) + { + case 'c': _c = true; break; + case 'n': _n = true; break; + case 'y': _y = true; break; + + default: + fdprintf(stderr, "### Quit - \"-%c\" is not an option.\n", c); + error = true; + break; + } + }); + if (_y + _n + _c > 1) { + fdprintf(stderr, "### Quit - Conflicting options were specified.\n"); + error = true; + } + + if (argv.size() > 0) { + fdprintf(stderr, "### Quit - Too many parameters were specified.\n"); + error = true; + } + + if (error) { + fdprintf(stderr, "# Usage - Quit [-y | -n | -c]\n"); + return 1; + } + + throw quit_command_t{}; + return 0; +} + + int builtin_execute(Environment &e, const std::vector &tokens, const fdmask &fds) { // runs argv[1] in the current environment. unlike MPW, argv[1] must be a script. diff --git a/builtins.h b/builtins.h index 0b3cb6b..add487f 100644 --- a/builtins.h +++ b/builtins.h @@ -28,7 +28,7 @@ int builtin_unalias(Environment &e, const std::vector &, const fdma int builtin_execute(Environment &e, const std::vector &, const fdmask &); int builtin_true(Environment &e, const std::vector &, const fdmask &); int builtin_false(Environment &e, const std::vector &, const fdmask &); -int builtin_execute(Environment &e, const std::vector &, const fdmask &); +int builtin_quit(Environment &e, const std::vector &, const fdmask &); int builtin_evaluate(Environment &e, std::vector &&, const fdmask &); diff --git a/command.cpp b/command.cpp index 94e87aa..a7609ec 100644 --- a/command.cpp +++ b/command.cpp @@ -5,6 +5,7 @@ #include "builtins.h" #include "mpw-shell.h" #include "error.h" +#include "value.h" #include #include @@ -17,6 +18,7 @@ #include "cxx/filesystem.h" #include "cxx/string_splitter.h" + #include #include #include @@ -40,8 +42,7 @@ typedef std::vector token_vector; namespace { - struct break_command_t {}; - struct continue_command_t {}; + /* * returns: @@ -56,6 +57,12 @@ namespace { return -3; } + int bad_exit() { + fprintf(stderr, "### Exit - Missing if keyword.\n"); + fprintf(stderr, "# Usage - Exit [Number] [if expression...]\n"); + return -3; + } + int evaluate(int type, token_vector &&tokens, Environment &env) { std::reverse(tokens.begin(), tokens.end()); @@ -64,6 +71,8 @@ namespace { switch(type) { default: return 0; + // exit [number] [if expr] ([number has been removed]) + case EXIT: case BREAK: case CONTINUE: case ELSE: @@ -77,10 +86,12 @@ namespace { case BREAK: name = "Break"; break; case CONTINUE: name = "Continue"; break; case ELSE: name = "Else"; break; + case EXIT: name = "Exit"; return bad_exit(); break; } return bad_if(name); } // fall through. + case IF: tokens.pop_back(); try { @@ -202,6 +213,7 @@ namespace { {"exists", builtin_exists}, {"export", builtin_export}, {"parameters", builtin_parameters}, + {"quit", builtin_quit}, {"quote", builtin_quote}, {"set", builtin_set}, {"shift", builtin_shift}, @@ -388,6 +400,10 @@ int eval_exec(std::string command, Environment &env, const fdmask &fds, bool thr rv = fx(tokens); } + catch (const exit_command_t &ex) { + // convert to execution of input terminated. + throw execution_of_input_terminated(ex.value); + } catch (mpw_error &e) { if (echo) env.echo("%s", command.c_str()); @@ -442,6 +458,39 @@ int continue_command::execute(Environment &env, const fdmask &fds, bool throwup) } + +int exit_command::execute(Environment &env, const fdmask &fds, bool throwup) { + + // exit + // exit [number] [if expr ]] + // todo -- + + return eval_exec(text, env, fds, throwup, [&](token_vector &tokens){ + env.set("command", "exit"); + env.status(0); + + if (tokens.size() <= 1) { + throw exit_command_t{}; + } + + + int status = 0; + value v(tokens[1]); + // remove the return value to make processing easier. + if (v.is_number()) { + tokens.erase(tokens.begin() + 1); + } + status = evaluate(EXIT, std::move(tokens), env); + + if (status) { + int ok = v.to_number(0); + throw exit_command_t{ok}; + } + + return 0; + }); +} + int or_command::execute(Environment &e, const fdmask &fds, bool throwup) { int rv = 0; diff --git a/command.h b/command.h index 79524f6..9f11db6 100644 --- a/command.h +++ b/command.h @@ -22,7 +22,7 @@ struct command { {} virtual bool terminal() const noexcept { - return type == EVALUATE || type == COMMAND || type == BREAK || type == CONTINUE || type == ERROR; + return type == EVALUATE || type == COMMAND || type == BREAK || type == CONTINUE || type == ERROR || type == EXIT; } int type = 0; @@ -81,6 +81,15 @@ struct continue_command : public command { virtual int execute(Environment &e, const fdmask &fds, bool throwup) final override; }; +struct exit_command : public command { + + template + exit_command(S &&s) : command(EXIT), text(std::forward(s)) + {} + + std::string text; + virtual int execute(Environment &e, const fdmask &fds, bool throwup) final override; +}; struct binary_command : public command { diff --git a/error.h b/error.h index bb0f173..407c5a0 100644 --- a/error.h +++ b/error.h @@ -67,4 +67,15 @@ public: {} }; +/* + these are used for flow-control. + they do not inherit from std::exception to prevent being caught + by normal handlers. +*/ + +struct break_command_t {}; +struct continue_command_t {}; +struct exit_command_t { int value = 0; }; +struct quit_command_t {}; + #endif \ No newline at end of file diff --git a/mpw-shell.cpp b/mpw-shell.cpp index 8c2a197..4c58917 100644 --- a/mpw-shell.cpp +++ b/mpw-shell.cpp @@ -91,8 +91,12 @@ int read_file(Environment &e, const std::string &file, const fdmask &fds) { mpw_parser p(e, fds); e.status(0, false); - p.parse(mf.begin(), mf.end()); - p.finish(); + try { + p.parse(mf.begin(), mf.end()); + p.finish(); + } catch(const execution_of_input_terminated &ex) { + return ex.status(); + } return e.status(); } @@ -100,9 +104,14 @@ int read_file(Environment &e, const std::string &file, const fdmask &fds) { int read_string(Environment &e, const std::string &s, const fdmask &fds) { mpw_parser p(e, fds); e.status(0, false); - p.parse(s); - p.finish(); + try { + p.parse(s); + p.finish(); + } catch(const execution_of_input_terminated &ex) { + return ex.status(); + } return e.status(); + } @@ -114,17 +123,21 @@ int read_fd(Environment &e, int fd, const fdmask &fds) { mpw_parser p(e, fds); e.status(0, false); - for (;;) { - size = read(fd, buffer, sizeof(buffer)); - if (size < 0) { - if (errno == EINTR) continue; - perror("read"); - e.status(-1, false); + try { + for (;;) { + size = read(fd, buffer, sizeof(buffer)); + if (size < 0) { + if (errno == EINTR) continue; + perror("read"); + e.status(-1, false); + } + if (size == 0) break; + p.parse(buffer, buffer + size); } - if (size == 0) break; - p.parse(buffer, buffer + size); + p.finish(); + } catch(const execution_of_input_terminated &ex) { + return ex.status(); } - p.finish(); return e.status(); } @@ -498,19 +511,28 @@ int main(int argc, char **argv) { read_file(e, startup); } catch (const std::system_error &ex) { fprintf(stderr, "### %s: %s\n", startup.c_str(), ex.what()); + } catch (const quit_command_t &) { } + e.startup(false); } - if (cflag) { - read_string(e, cflag); - exit(e.status()); + try { + + int rv = 0; + if (cflag) { + rv = read_string(e, cflag); + exit(rv); + } + + if (isatty(STDIN_FILENO)) + rv = interactive(e); + else + rv = read_fd(e, STDIN_FILENO); + + exit(rv); + } + catch (const quit_command_t &) { + exit(0); } - - if (isatty(STDIN_FILENO)) - interactive(e); - else - read_fd(e, STDIN_FILENO); - - exit(e.status()); } diff --git a/mpw_parser.cpp b/mpw_parser.cpp index e713200..d66a43b 100644 --- a/mpw_parser.cpp +++ b/mpw_parser.cpp @@ -81,6 +81,7 @@ void mpw_parser::execute() { command_ptr cmd; + try { while (!commands.empty()) { cmd = std::move(commands.back()); @@ -94,7 +95,7 @@ void mpw_parser::execute() { if (_interactive) { if (!cmd->terminal() || !commands.empty()) { - fprintf(stderr, "### %s\n", ex.what()); + if (ex.status()) fprintf(stderr, "### %s\n", ex.what()); } return; } diff --git a/phase2.rl b/phase2.rl index aa44202..647affa 100644 --- a/phase2.rl +++ b/phase2.rl @@ -193,6 +193,7 @@ int phase2::classify() { _("else", ELSE) _("end", END) _("evaluate", EVALUATE) + _("exit", EXIT) _("for", FOR) _("if", IF) _("loop", LOOP) @@ -233,6 +234,7 @@ bool phase2::special() { case EVALUATE: case BREAK: case CONTINUE: + case EXIT: return true; default: return false; diff --git a/phase3.lemon b/phase3.lemon index 2937cca..7fd3900 100644 --- a/phase3.lemon +++ b/phase3.lemon @@ -139,6 +139,7 @@ term(RV) ::= COMMAND(C). { RV = std::make_unique(std::move term(RV) ::= EVALUATE(C). { RV = std::make_unique(std::move(C)); } term(RV) ::= BREAK(C). { RV = std::make_unique(std::move(C)); } term(RV) ::= CONTINUE(C). { RV = std::make_unique(std::move(C)); } +term(RV) ::= EXIT(C). { RV = std::make_unique(std::move(C)); } term(C) ::= if_command(C). term(C) ::= begin_command(C). term(C) ::= paren_command(C).