From 5723656988ff96112ca00cf5ee7747b004cd2faf Mon Sep 17 00:00:00 2001 From: Kelvin Sherlock Date: Mon, 1 Feb 2016 20:38:29 -0500 Subject: [PATCH] lots of updates! --- CMakeLists.txt | 6 +- mpw-shell-builtins.cpp => builtins.cpp | 187 ++++--------- builtins.h | 22 ++ command.cpp | 346 ++++++++++++++++++++++--- command.h | 49 +++- environment.cpp | 102 ++++++++ environment.h | 106 ++++++++ fdset.h | 9 +- mpw-shell-execute.cpp | 1 + mpw-shell-expand.rl | 10 +- mpw-shell.cpp | 25 +- mpw-shell.h | 98 +------ phase2-parser.lemon | 82 +++++- phase2.rl | 28 +- 14 files changed, 752 insertions(+), 319 deletions(-) rename mpw-shell-builtins.cpp => builtins.cpp (66%) create mode 100644 builtins.h create mode 100644 environment.cpp create mode 100644 environment.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fa351d..9a0c755 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,10 +108,10 @@ add_custom_command( ) - +# mpw-shell-execute.cpp mpw-shell-builtins.cpp add_executable(mpw-shell mpw-shell.cpp mpw-shell-read.cpp mpw-shell-token.cpp mpw-shell-expand.cpp - mpw-shell-execute.cpp mpw-shell-builtins.cpp mpw-shell-parser.cpp value.cpp mpw-shell-quote.cpp - phase1.cpp phase2.cpp phase2-parser.cpp command.cpp) + mpw-shell-parser.cpp value.cpp mpw-shell-quote.cpp + phase1.cpp phase2.cpp phase2-parser.cpp command.cpp environment.cpp builtins.cpp) # all this for -std=c++14 set_property (TARGET mpw-shell PROPERTY CXX_STANDARD 14) diff --git a/mpw-shell-builtins.cpp b/builtins.cpp similarity index 66% rename from mpw-shell-builtins.cpp rename to builtins.cpp index 79d4a84..29de5f3 100644 --- a/mpw-shell-builtins.cpp +++ b/builtins.cpp @@ -3,6 +3,7 @@ #include "fdset.h" #include "value.h" +#include "environment.h" #include #include @@ -11,14 +12,17 @@ #include #include +//MAXPATHLEN +#include + namespace { - +/* std::string &lowercase(std::string &s) { std::transform(s.begin(), s.end(), s.begin(), [](char c){ return std::tolower(c); }); return s; } - +*/ // doesn't handle flag arguments but builtins don't have arguments. template @@ -40,83 +44,6 @@ namespace { return out; } - - -#if 0 - /* - * the fdopen() will assume ownership of the fd and close it. - * this is not desirable. - */ - - // linux uses size_t. *BSD uses int. - int readfn(void *cookie, char *buffer, int size) { - return ::read((int)(std::ptrdiff_t)cookie, buffer, size); - } - - int readfn(void *cookie, char *buffer, size_t size) { - return ::read((int)(std::ptrdiff_t)cookie, buffer, size); - } - - int writefn(void *cookie, const char *buffer, int size) { - return ::write((int)(std::ptrdiff_t)cookie, buffer, size); - } - - int writefn(void *cookie, const char *buffer, size_t size) { - return ::write((int)(std::ptrdiff_t)cookie, buffer, size); - } - - FILE *file_stream(int index, int fd) { - if (fd < 0) { - switch (index) { - case 0: return stdin; - case 1: return stdout; - case 2: return stderr; - default: - return stderr; - } - } - // will not close. - - #ifdef __linux__ - /* Linux */ - cookie_io_functions_t io = { readfn, writefn, nullptr, nullptr }; - return fopencookie((void *)(std::ptrdiff_t)fd, "w+", io); - #else - /* *BSD */ - return funopen((const void *)(std::ptrdiff_t)fd, readfn, writefn, nullptr, nullptr); - #endif - - } - - - class io_helper { - - public: - FILE *in; - FILE *out; - FILE *err; - - io_helper(const fdmask &fds) { - in = file_stream(0, fds[0]); - out = file_stream(1, fds[1]); - err = file_stream(2, fds[2]); - } - - ~io_helper() { - #define __(x, target) if (x != target) fclose(x) - __(in, stdin); - __(out, stdout); - __(err, stderr); - #undef __ - } - - io_helper() = delete; - io_helper(const io_helper &) = delete; - io_helper &operator=(const io_helper &) = delete; - }; - -#endif - } #undef stdin @@ -142,31 +69,21 @@ inline int fputc(int c, int fd) { } -int builtin_unset(const std::vector &tokens, const fdmask &) { +int builtin_unset(Environment &env, const std::vector &tokens, const fdmask &) { for (auto iter = tokens.begin() + 1; iter != tokens.end(); ++iter) { - std::string name = *iter; - lowercase(name); + const std::string &name = *iter; - Environment.erase(name); + env.unset(name); } // unset [no arg] removes ALL variables if (tokens.size() == 1) { - Environment.clear(); + env.unset(); } return 0; } - -template -void insert_or_assign(UO &map, K&& k, M&& obj) { - - auto iter = map.find(k); - if (iter != map.end()) iter->second = std::forward(obj); - else map.emplace(std::make_pair(std::forward(k), std::forward(obj))); -} - -int builtin_set(const std::vector &tokens, const fdmask &fds) { +int builtin_set(Environment &env, const std::vector &tokens, const fdmask &fds) { // set var name -- set // set var -- just print the value @@ -177,7 +94,7 @@ int builtin_set(const std::vector &tokens, const fdmask &fds) { if (tokens.size() == 1) { - for (const auto &kv : Environment) { + for (const auto &kv : env) { std::string name = quote(kv.first); std::string value = quote(kv.second); @@ -190,9 +107,8 @@ int builtin_set(const std::vector &tokens, const fdmask &fds) { if (tokens.size() == 2) { std::string name = tokens[1]; - lowercase(name); - auto iter = Environment.find(name); - if (iter == Environment.end()) { + auto iter = env.find(name); + if (iter == env.end()) { fprintf(stderr, "### Set - No variable definition exists for %s.\n", name.c_str()); return 2; } @@ -220,15 +136,14 @@ int builtin_set(const std::vector &tokens, const fdmask &fds) { std::string name = tokens[1+exported]; std::string value = tokens[2+exported]; - lowercase(name); - Environment[name] = EnvironmentEntry(std::move(value), exported); + env.set(name, value, exported); return 0; } -static int export_common(bool export_or_unexport, const std::vector &tokens, const fdmask &fds) { +static int export_common(Environment &env, bool export_or_unexport, const std::vector &tokens, const fdmask &fds) { const char *name = export_or_unexport ? "Export" : "Unexport"; @@ -270,7 +185,7 @@ static int export_common(bool export_or_unexport, const std::vector name = export_or_unexport ? "Export " : "Unexport "; - for (const auto &kv : Environment) { + for (const auto &kv : env) { const std::string& vname = kv.first; if (kv.second == export_or_unexport) fprintf(stdout, "%s%s\n", flags._s ? "" : name, quote(vname).c_str()); @@ -283,9 +198,8 @@ static int export_common(bool export_or_unexport, const std::vector if (flags._r || flags._s) goto conflict; for (std::string s : argv) { - lowercase(s); - auto iter = Environment.find(s); - if (iter != Environment.end()) iter->second = export_or_unexport; + auto iter = env.find(s); + if (iter != env.end()) iter->second = export_or_unexport; } return 0; } @@ -295,21 +209,20 @@ conflict: fprintf(stderr, "# Usage - %s [-r | -s | name...]\n", name); return 1; } -int builtin_export(const std::vector &tokens, const fdmask &fds) { - //io_helper io(fds); - return export_common(true, tokens, fds); +int builtin_export(Environment &env, const std::vector &tokens, const fdmask &fds) { + + return export_common(env, true, tokens, fds); } -int builtin_unexport(const std::vector &tokens, const fdmask &fds) { +int builtin_unexport(Environment &env, const std::vector &tokens, const fdmask &fds) { - //io_helper io(fds); - return export_common(false, tokens, fds); + return export_common(env, false, tokens, fds); } -int builtin_echo(const std::vector &tokens, const fdmask &fds) { +int builtin_echo(Environment &env, const std::vector &tokens, const fdmask &fds) { //io_helper io(fds); @@ -333,7 +246,7 @@ int builtin_echo(const std::vector &tokens, const fdmask &fds) { return 0; } -int builtin_quote(const std::vector &tokens, const fdmask &fds) { +int builtin_quote(Environment &env, const std::vector &tokens, const fdmask &fds) { // todo... //io_helper io(fds); @@ -359,7 +272,7 @@ int builtin_quote(const std::vector &tokens, const fdmask &fds) { return 0; } -int builtin_parameters(const std::vector &argv, const fdmask &fds) { +int builtin_parameters(Environment &env, const std::vector &argv, const fdmask &fds) { //io_helper io(fds); @@ -371,7 +284,7 @@ int builtin_parameters(const std::vector &argv, const fdmask &fds) } -int builtin_directory(const std::vector &tokens, const fdmask &fds) { +int builtin_directory(Environment &env, const std::vector &tokens, const fdmask &fds) { // directory [-q] // directory path @@ -416,12 +329,29 @@ int builtin_directory(const std::vector &tokens, const fdmask &fds) return 1; } - return 0; + // todo -- pathname translation. + int ok = chdir(argv.front().c_str()); + if (ok < 0) { + fputs("### Directory - Unable to set current directory.\n", stderr); + perror("chdir: "); + return 1; + } } else { // pwd - return 0; + char buffer[MAXPATHLEN]; + char *cp = getcwd(buffer, sizeof(buffer)); + if (!cp) { + perror("getcwd: "); + return -1; + } + // todo -- pathname translation? + + std::string s = buffer; + if (!q) s = quote(std::move(s)); + fprintf(stdout, "%s\n", s.c_str()); } + return 0; } static bool is_assignment(int type) { @@ -436,7 +366,7 @@ static bool is_assignment(int type) { } } -int builtin_evaluate(std::vector &&tokens, const fdmask &fds) { +int builtin_evaluate(Environment &env, std::vector &&tokens, const fdmask &fds) { // evaluate expression // evaluate variable = expression // evaluate variable += expression @@ -481,7 +411,6 @@ int builtin_evaluate(std::vector &&tokens, const fdmask &fds) { if (is_assignment(type)) { std::string name = tokens.back().string; - lowercase(name); tokens.pop_back(); tokens.pop_back(); @@ -490,14 +419,14 @@ int builtin_evaluate(std::vector &&tokens, const fdmask &fds) { switch(type) { case '=': - Environment[name] = std::to_string(i); + env.set(name, std::to_string(i)); break; case '+=': case '-=': { value old; - auto iter = Environment.find(name); - if (iter != Environment.end()) old = (const std::string &)iter->second; + auto iter = env.find(name); + if (iter != env.end()) old = (const std::string &)iter->second; switch(type) { case '+=': @@ -509,10 +438,7 @@ int builtin_evaluate(std::vector &&tokens, const fdmask &fds) { } std::string s = std::to_string(i); - if (iter == Environment.end()) - Environment.emplace(std::move(name), std::move(s)); - else iter->second = std::move(s); - + env.set(name, s); } break; } @@ -529,15 +455,6 @@ int builtin_evaluate(std::vector &&tokens, const fdmask &fds) { } if (output == 'b') { std::string tmp("0b"); - /* - fputc('0', stdout); - fputc('b', stdout); - for (int j = 0; j < 32; ++j) { - fputc(i & 0x80000000 ? '1' : '0', stdout); - i <<= 1; - } - fputc('\n', stdout); - */ for (int j = 0; j < 32; ++j) { tmp.push_back(i & 0x80000000 ? '1' : '0'); diff --git a/builtins.h b/builtins.h new file mode 100644 index 0000000..d7d91c7 --- /dev/null +++ b/builtins.h @@ -0,0 +1,22 @@ +#ifndef __builtins_h__ +#define __builtins_h__ + +#include +#include + +class Environment; +class fdmask; +class token; + +int builtin_directory(Environment &e, const std::vector &, const fdmask &); +int builtin_echo(Environment &e, const std::vector &, const fdmask &); +int builtin_parameters(Environment &e, const std::vector &, const fdmask &); +int builtin_quote(Environment &e, const std::vector &tokens, const fdmask &); +int builtin_set(Environment &e, const std::vector &, const fdmask &); +int builtin_unset(Environment &e, const std::vector &, const fdmask &); +int builtin_export(Environment &e, const std::vector &, const fdmask &); +int builtin_unexport(Environment &e, const std::vector &, const fdmask &); + +int builtin_evaluate(Environment &e, std::vector &&, const fdmask &); + +#endif \ No newline at end of file diff --git a/command.cpp b/command.cpp index e644006..e19b085 100644 --- a/command.cpp +++ b/command.cpp @@ -1,92 +1,362 @@ #include "command.h" #include "phase2-parser.h" +#include "environment.h" +#include "fdset.h" +#include "builtins.h" +#include "mpw-shell.h" + #include +#include +#include +#include +#include + + +#include +#include +#include + +namespace { + + std::string &lowercase(std::string &s) { + std::transform(s.begin(), s.end(), s.begin(), [](char c){ return std::tolower(c); }); + return s; + } + + std::unordered_map &, const fdmask &)> builtins = { + {"directory", builtin_directory}, + {"echo", builtin_echo}, + {"parameters", builtin_parameters}, + {"quote", builtin_quote}, + {"set", builtin_set}, + {"unset", builtin_unset}, + {"export", builtin_export}, + {"unexport", builtin_unexport}, + }; + + + + + int execute_external(const Environment &env, const std::vector &argv, const fdmask &fds) { + + std::vector cargv; + cargv.reserve(argv.size() + 3); + + int status; + int pid; + + cargv.push_back((char *)"mpw"); + //cargv.push_back((char *)"--shell"); + + unsigned offset = cargv.size(); + + + std::transform(argv.begin(), argv.end(), std::back_inserter(cargv), + [](const std::string &s) { return strdup(s.c_str()); } + ); + + cargv.push_back(nullptr); + + + pid = fork(); + if (pid < 0) { + perror("fork: "); + exit(EX_OSERR); + } + + + if (pid == 0) { + + // also export environment... + + // handle any indirection... + fds.dup(); + + execvp(cargv.front(), cargv.data()); + perror("execvp: "); + exit(EX_OSERR); + } + + std::for_each(cargv.begin()+offset, cargv.end(), free); + + for(;;) { + int status; + pid_t ok; + ok = waitpid(pid, &status, 0); + if (ok < 0) { + if (errno == EINTR) continue; + perror("waitpid:"); + exit(EX_OSERR); + } + + if (WIFEXITED(status)) return WEXITSTATUS(status); + if (WIFSIGNALED(status)) return -9; // command-. user abort exits via -9. + + fprintf(stderr, "waitpid - unexpected result\n"); + exit(EX_OSERR); + } + + } + + +} + + + +//std::string expand_vars(const std::string &s, const class Environment &); command::~command() {} +/* + * todo -- all errors should be handled the same, regardless of source. (throw, parse, etc). + * echo and error should respect the active fdmask. + */ -int simple_command::execute() { - // echo - fprintf(stderr, " %s\n", text.c_str()); - return 0; +int simple_command::execute(Environment &env, const fdmask &fds) { + std::string s = expand_vars(text, env); + + if (env.echo()) fprintf(stderr, " %s\n", s.c_str()); + + process p; + try { + auto tokens = tokenize(s, false); + parse_tokens(std::move(tokens), p); + } catch(std::exception &e) { + fprintf(stderr, "%s", e.what()); + return env.status(-4); + } + + if (p.arguments.empty()) return 0; + + fdmask newfds = p.fds | fds; + + std::string name = p.arguments.front(); + lowercase(name); + + auto iter = builtins.find(name); + if (iter != builtins.end()) { + int status = iter->second(env, p.arguments, newfds); + return env.status(status); + } + + if (env.test()) return env.status(0); + if (env.startup()) { + fprintf(stderr, "### MPW Shell - startup file may not contain external commands.\n"); + return env.status(0); + } + + int status = execute_external(env, p.arguments, newfds); + + return env.status(status); } -int or_command::execute() { - /* - for (const auto &c : children) { - if (!c) throw std::runtime_error("corruption in || command."); - } - */ +int evaluate_command::execute(Environment &env, const fdmask &fds) { + + std::string s = expand_vars(text, env); + + if (env.echo()) fprintf(stderr, " %s\n", s.c_str()); + + auto tokens = tokenize(s, true); + if (tokens.empty()) return 0; + + int status = builtin_evaluate(env, std::move(tokens), fds); + + return env.status(status); +} + + +int or_command::execute(Environment &e, const fdmask &fds) { int rv = 0; + bool pv = e.and_or(true); + for (auto &c : children) { - rv = c->execute(); + rv = c->execute(e, fds); if (rv == 0) return rv; } - return rv; + e.and_or(pv); + + return e.status(rv); } -int and_command::execute() { +int and_command::execute(Environment &e, const fdmask &fds) { int rv = 0; + bool pv = e.and_or(true); + for (auto &c : children) { - rv = c->execute(); + rv = c->execute(e, fds); if (rv != 0) return rv; } + e.and_or(pv); + return rv; } -int vector_command::execute() { +int vector_command::execute(Environment &e, const fdmask &fds) { int rv = 0; for (auto &c : children) { - rv = c->execute(); - // todo -- env.exit + rv = c->execute(e, fds); + if (e.exit()) break; } - return rv; + return e.status(rv); } -int begin_command::execute() { +int error_command::execute(Environment &e, const fdmask &fds) { + std::string s = expand_vars(text, e); + + if (e.echo()) fprintf(stderr, " %s\n", s.c_str()); + + switch(type) { + case END: + fprintf(stderr, "### MPW Shell - Extra END command.\n"); + break; + + case RPAREN: + fprintf(stderr, "### MPW Shell - Extra ) command.\n"); + break; + + case ELSE: + case ELSE_IF: + fprintf(stderr, "### MPW Shell - ELSE must be within IF ... END.\n"); + break; + } + + + return e.status(-3); +} + +namespace { + + int check_ends(const std::string &s, fdmask &fds) { + + // MPW ignores any END tokens other than redirection. + process p; + try { + auto tokens = tokenize(s, false); + parse_tokens(std::move(tokens), p); + } + catch (std::exception &e) { + fprintf(stderr, "%s\n", e.what()); + return -4; + } + fds = p.fds.to_mask(); + return 0; + } +} + +int begin_command::execute(Environment &env, const fdmask &fds) { // todo -- parse end for redirection. + std::string b = expand_vars(begin, env); + std::string e = expand_vars(end, env); + // echo! - fprintf(stderr, " %s ... %s\n", - type == BEGIN ? "begin" : "(", - end.c_str() + if (env.echo()) fprintf(stderr, " %s ... %s\n", + b.c_str(), + e.c_str() ); - int rv = vector_command::execute(); - fprintf(stderr, " %s\n", type == BEGIN ? "end" : ")"); - return rv; + // tokenize the begin and end commands. + // begin may not have extra arguments. end may have redirection. + + auto bt = tokenize(b, true); + if (bt.size() != 1) { + fprintf(stderr, "### Begin - Too many parameters were specified.\n"); + fprintf(stderr, "Usage - Begin\n"); + return env.status(-3); + } + + fdmask newfds; + int status = check_ends(e, newfds); + newfds |= fds; + if (status) return env.status(status); + + int rv = vector_command::execute(env, newfds); + if (env.echo()) fprintf(stderr, " %s\n", type == BEGIN ? "end" : ")"); + + return env.status(rv); } -int if_command::execute() { +namespace { + + + + bool evaluate(int type, const std::string &s, Environment &env) { + + auto tokens = tokenize(s, true); + std::reverse(tokens.begin(), tokens.end()); + + int32_t e; + + switch(type) { + default: return 0; + + case ELSE_IF: + tokens.pop_back(); + case IF: + tokens.pop_back(); + try { + e = evaluate_expression("If", std::move(tokens)); + } + catch (std::exception &ex) { + fprintf(stderr, "%s\n", ex.what()); + env.status(-5); + e = 0; + } + break; + + case ELSE: + e = 1; + if (tokens.size() > 1) { + fprintf(stderr, "### Else - Missing if keyword.\n"); + fprintf(stderr, "# Usage - Else [if expression...]\n"); + env.status(-3); + e = 0; + } + } + return e; + } +} + +int if_command::execute(Environment &env, const fdmask &fds) { int rv = 0; bool ok = false; + std::string e = expand_vars(end, env); + + // parse end for indirection. + fdmask newfds; + int status = check_ends(e, newfds); + newfds |= fds; + if (status) return env.status(status); - // todo -- parse end for redirection. for (auto &c : clauses) { - if (c->type == IF) {// special. - fprintf(stderr, " %s ... %s\n", - c->clause.c_str(), end.c_str()); - } - else { - fprintf(stderr, " %s\n", c->clause.c_str()); + std::string s = expand_vars(c->clause, env); + + if (env.echo()) { + if (c->type == IF) { // special. + fprintf(stderr, " %s ... %s\n", + s.c_str(), e.c_str()); + } + else { + fprintf(stderr, " %s\n", s.c_str()); + } } if (ok) continue; - ok = c->evaluate(); + ok = evaluate(c->type, s, env); if (!ok) continue; - rv = c->execute(); + rv = c->execute(env, newfds); } - fprintf(stderr, " end\n"); - return rv; + if (env.echo()) fprintf(stderr, " end\n"); + return env.status(rv); } /* @@ -95,6 +365,6 @@ int if_else_clause::execute() { } */ -bool if_else_clause::evaluate() { +bool if_else_clause::evaluate(const Environment &e) { return false; } diff --git a/command.h b/command.h index eea4ec1..6ad3e5e 100644 --- a/command.h +++ b/command.h @@ -11,6 +11,9 @@ typedef std::unique_ptr command_ptr; typedef std::vector command_ptr_vector; typedef std::array command_ptr_pair; +#include "environment.h" +class Environment; +class fdmask; struct command { @@ -19,7 +22,17 @@ struct command { int type = 0; virtual ~command(); - virtual int execute() = 0; + virtual int execute(Environment &e, const fdmask &fds) = 0; +}; + +struct error_command : public command { + + template + error_command(int t, S &&s) : command(t), text(std::forward(s)) + {} + + std::string text; + virtual int execute(Environment &e, const fdmask &fds) override final; }; struct simple_command : public command { @@ -29,7 +42,17 @@ struct simple_command : public command { std::string text; - virtual int execute() final override; + virtual int execute(Environment &e, const fdmask &fds) final override; +}; + +struct evaluate_command : public command { + template + evaluate_command(S &&s) : command(EVALUATE), text(std::forward(s)) + {} + + std::string text; + + virtual int execute(Environment &e, const fdmask &fds) final override; }; struct binary_command : public command { @@ -47,7 +70,7 @@ struct or_command : public binary_command { binary_command(PIPE_PIPE, std::move(a), std::move(b)) {} - virtual int execute() final override; + virtual int execute(Environment &e, const fdmask &fds) final override; }; struct and_command : public binary_command { @@ -55,7 +78,7 @@ struct and_command : public binary_command { binary_command(AMP_AMP, std::move(a), std::move(b)) {} - virtual int execute() final override; + virtual int execute(Environment &e, const fdmask &fds) final override; }; @@ -65,7 +88,7 @@ struct pipe_command : public binary_command { binary_command(PIPE, std::move(a), std::move(b)) {} - virtual int execute() final override; + virtual int execute(Environment &e) final override; }; #endif @@ -75,18 +98,19 @@ struct vector_command : public command { {} command_ptr_vector children; - virtual int execute() override; + virtual int execute(Environment &e, const fdmask &fds) override; }; struct begin_command : public vector_command { - template - begin_command(int t, command_ptr_vector &&v, S &&s) : - vector_command(t, std::move(v)), end(std::forward(s)) + template + begin_command(int t, command_ptr_vector &&v, S1 &&b, S2 &&e) : + vector_command(t, std::move(v)), begin(std::forward(b)), end(std::forward(e)) {} + std::string begin; std::string end; - virtual int execute() final override; + virtual int execute(Environment &e, const fdmask &fds) final override; }; typedef std::unique_ptr if_else_clause_ptr; @@ -103,7 +127,7 @@ struct if_command : public command { clause_vector_type clauses; std::string end; - virtual int execute() final override; + virtual int execute(Environment &e, const fdmask &fds) final override; }; struct if_else_clause : public vector_command { @@ -115,8 +139,7 @@ struct if_else_clause : public vector_command { std::string clause; - bool evaluate(); - //virtual int execute() final override; + bool evaluate(const Environment &e); }; #endif \ No newline at end of file diff --git a/environment.cpp b/environment.cpp new file mode 100644 index 0000000..5d7d947 --- /dev/null +++ b/environment.cpp @@ -0,0 +1,102 @@ +#include "environment.h" +#include +#include + +namespace { + + + std::string &lowercase(std::string &s) { + std::transform(s.begin(), s.end(), s.begin(), [](char c){ return std::tolower(c); }); + return s; + } + + /* 0 or "" -> false. all others -> true */ + bool tf(const std::string &s) { + if (s.empty()) return false; + if (s.size() == 1 && s == "0") return false; + return true; + } +} + + + Environment::iterator Environment::find( const std::string & key ) { + std::string k(key); + lowercase(k); + return _table.find(k); + } + + Environment::const_iterator Environment::find( const std::string & key ) const { + std::string k(key); + lowercase(k); + return _table.find(k); + } + + void Environment::set(const std::string &key, const std::string &value, bool exported) { + std::string k(key); + lowercase(k); + + if (k == "echo") _echo = tf(value); + if (k == "exit") _exit = tf(value); + if (k == "test") _test = tf(value); + + // don't need to check {status} because that will be clobbered + // by the return value. + + EnvironmentEntry v(value, exported); + + auto iter = _table.find(k); + if (iter == _table.end()) { + _table.emplace(std::make_pair(k, std::move(v))); + } + else { + // if previously exported, keep exported. + if (iter->second) v = true; + iter->second = std::move(v); + } + } + + void Environment::unset(const std::string &key) { + std::string k(key); + lowercase(k); + if (k == "echo") _echo = false; + if (k == "exit") _exit = false; + if (k == "test") _test = false; + _table.erase(k); + } + + void Environment::unset() { + _table.clear(); + _echo = false; + _exit = false; + _test = false; + _status = 0; + } + + + int Environment::status(int i) { + + if (_status == i) return i; + + _status = i; + _table["status"] = std::to_string(i); + return i; + } + +/* + int Environment::status(int i) { + status(i, std::nothrow); + if (_exit) { + throw std::runtime_error("Execution of input terminated."); + } + return i; + } + + void Environment::echo(const char *fmt, ...) { + if (_echo) {} + va_list ap; + va_start(ap, fmt); + va_end(ap); + vfprintf(stderr, fmt, ap); + } + } +*/ diff --git a/environment.h b/environment.h new file mode 100644 index 0000000..e946743 --- /dev/null +++ b/environment.h @@ -0,0 +1,106 @@ +#ifndef __environment_h__ +#define __environment_h__ + +#include +#include +#include + +// environment has a bool which indicates if exported. +struct EnvironmentEntry { +public: + operator bool() const { return exported; } + operator bool&() { return exported; } + + operator const std::string&() const { return value; } + operator std::string&() { return value; } + + EnvironmentEntry() = default; + EnvironmentEntry(const EnvironmentEntry &) = default; + EnvironmentEntry(EnvironmentEntry &&) = default; + + EnvironmentEntry(const std::string &s, bool e = false) : value(s), exported(e) + {} + EnvironmentEntry(std::string &&s, bool e = false) : value(std::move(s)), exported(e) + {} + + ~EnvironmentEntry() = default; + + EnvironmentEntry& operator=(bool rhs) { exported = rhs; return *this; } + EnvironmentEntry& operator=(const std::string &rhs) { value = rhs; return *this; } + EnvironmentEntry& operator=(const EnvironmentEntry &) = default; + EnvironmentEntry& operator=(EnvironmentEntry &&) = default; + +private: + std::string value; + bool exported = false; + +}; + + + +class Environment { + +public: + typedef std::unordered_map mapped_type; + typedef mapped_type::iterator iterator; + typedef mapped_type::const_iterator const_iterator; + + //const EnvironmentEntry & lookup(const std::string &s); + + void set(const std::string &k, const std::string &value, bool exported = false); + void unset(const std::string &k); + void unset(); + + constexpr bool echo() const noexcept { return _echo; } + constexpr bool test() const noexcept { return _test; } + constexpr bool exit() const noexcept { return _and_or ? false : _exit; } + constexpr int status() const noexcept { return _status; } + + bool and_or(bool v) { std::swap(v, _and_or); return v; } + + int status(int i); + + constexpr bool startup() const noexcept { return _startup; } + constexpr void startup(bool tf) noexcept { _startup = tf; } + + + constexpr bool passthrough() const noexcept { return _passthrough; } + constexpr void passthrough(bool tf) noexcept { _passthrough = tf; } + + template + void foreach(FX && fx) { for (const auto &kv : _table) { fx(kv.first, kv.second); }} + + iterator begin() { return _table.begin(); } + const_iterator begin() const { return _table.begin(); } + const_iterator cbegin() const { return _table.cbegin(); } + + iterator end() { return _table.end(); } + const_iterator end() const { return _table.end(); } + const_iterator cend() const { return _table.cend(); } + + + iterator find( const std::string & key ); + const_iterator find( const std::string & key ) const; + + + void echo(const char *format, ...); + +private: + // magic variables. + + bool _exit = false; + bool _test = false; + + bool _and_or = false; + + bool _echo = false; + int _status = 0; + int _indent = 0; + bool _startup = false; + bool _passthrough = false; + + std::unordered_map _table; +}; + + +#endif diff --git a/fdset.h b/fdset.h index ac519b6..edf5046 100644 --- a/fdset.h +++ b/fdset.h @@ -61,7 +61,8 @@ class fdmask { int operator[](unsigned index) const { - return _fds[index]; + if (_fds[index] >= 0) return _fds[index]; + return default_fd(index); } fdmask &operator|=(const fdmask &rhs) { @@ -72,6 +73,12 @@ class fdmask { } private: + + static int default_fd(int index) { + static constexpr std::array _default_fds = {{ STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO }}; + return _default_fds[index]; + } + friend class fdset; std::array _fds = {{ -1, -1, -1 }}; }; diff --git a/mpw-shell-execute.cpp b/mpw-shell-execute.cpp index 8d8ac9d..850a4f1 100644 --- a/mpw-shell-execute.cpp +++ b/mpw-shell-execute.cpp @@ -20,6 +20,7 @@ * * Echo {Echo} # control the echoing of commands to diagnostic output * Echo {Exit} # control script termination based on {Status} + * Echo {Test} # don't actually run anything. * */ diff --git a/mpw-shell-expand.rl b/mpw-shell-expand.rl index 1933436..882e53c 100644 --- a/mpw-shell-expand.rl +++ b/mpw-shell-expand.rl @@ -46,9 +46,11 @@ // flag to pass through vs "" ? auto iter = env.find(var); if (iter == env.end()) { - line.push_back('{'); - line.append(var); - line.push_back('}'); + if (env.passthrough()) { + line.push_back('{'); + line.append(var); + line.push_back('}'); + } } else { line.append((std::string)iter->second); @@ -112,7 +114,7 @@ * set q '"' ; echo {q} dsfsdf" */ -std::string expand_vars(const std::string &s, const std::unordered_map &env) { +std::string expand_vars(const std::string &s, const Environment &env) { if (s.find('{') == s.npos) return s; std::string var; diff --git a/mpw-shell.cpp b/mpw-shell.cpp index e9c6a8a..b77b983 100644 --- a/mpw-shell.cpp +++ b/mpw-shell.cpp @@ -9,23 +9,20 @@ #include #include -#define NOCOMMAND #include "mpw-shell.h" +#include "fdset.h" #include "phase1.h" #include "phase2.h" #include "command.h" -std::unordered_map Environment; - - - // should set {MPW}, {MPWVersion}, then execute {MPW}StartUp -void init(void) { - Environment.emplace("status", std::string("0")); - Environment.emplace("exit", std::string("1")); // terminate script on error. +void init(Environment &e) { + e.set("status", std::string("0")); + e.set("exit", std::string("1")); // terminate script on error. + e.set("echo", std::string("1")); } int read_stdin(phase1 &p) { @@ -61,18 +58,16 @@ int read_stdin(phase1 &p) { int main(int argc, char **argv) { - init(); - - command_ptr head; - + Environment e; + init(e); phase1 p1; phase2 p2; p1 >>= p2; - p2 >>= [](command_ptr &&ptr) { - printf("command: %d\n", ptr->type); - ptr->execute(); + p2 >>= [&e](command_ptr &&ptr) { + fdmask fds; + ptr->execute(e, fds); }; /* p1 >>= [&p2](std::string &&s) { diff --git a/mpw-shell.h b/mpw-shell.h index 9626620..48fd696 100644 --- a/mpw-shell.h +++ b/mpw-shell.h @@ -7,94 +7,10 @@ #include #include +#include "environment.h" const unsigned char escape = 0xb6; -// environment has a bool which indicates if exported. -struct EnvironmentEntry { -public: - operator bool() const { return exported; } - operator bool&() { return exported; } - - operator const std::string&() const { return value; } - operator std::string&() { return value; } - - EnvironmentEntry() = default; - EnvironmentEntry(const EnvironmentEntry &) = default; - EnvironmentEntry(EnvironmentEntry &&) = default; - - EnvironmentEntry(const std::string &s, bool e = false) : value(s), exported(e) - {} - EnvironmentEntry(std::string &&s, bool e = false) : value(std::move(s)), exported(e) - {} - - ~EnvironmentEntry() = default; - - EnvironmentEntry& operator=(bool &rhs) { exported = rhs; return *this; } - EnvironmentEntry& operator=(const std::string &rhs) { value = rhs; return *this; } - EnvironmentEntry& operator=(const EnvironmentEntry &) = default; - EnvironmentEntry& operator=(EnvironmentEntry &&) = default; - -private: - std::string value; - bool exported = false; - -}; - -extern std::unordered_map Environment; - -#ifndef NOCOMMAND - -enum { - command_if = 1, - command_else, - command_else_if, - command_end, - command_begin, - command_evaluate, -}; - -class command; -typedef std::shared_ptr command_ptr; -typedef std::weak_ptr weak_command_ptr; - -class command { - public: - unsigned type = 0; - unsigned line = 0; - unsigned level = 0; - - std::string string; - command_ptr next; - weak_command_ptr alternate; // if -> else -> end. weak to prevent cycles. - - command() = default; - command(command &&) = default; - command(const command &) = default; - - command(unsigned t, const std::string &s) : - type(t), string(s) - {} - - command(unsigned t, std::string &&s) : - type(t), string(std::move(s)) - {} - - command(const std::string &s) : string(s) - {} - - command(std::string &&s) : string(std::move(s)) - {} - - -}; - -command_ptr read_fd(int fd); -command_ptr read_file(const std::string &); -command_ptr read_string(const std::string &); -int execute(command_ptr cmd); - -#endif class token { public: @@ -133,7 +49,7 @@ public: std::vector tokenize(const std::string &s, bool eval = false); -std::string expand_vars(const std::string &s, const std::unordered_map &env); +std::string expand_vars(const std::string &s, const class Environment &); //std::string quote(std::string &&s); std::string quote(const std::string &s); @@ -149,16 +65,6 @@ void parse_tokens(std::vector &&tokens, process &p); int32_t evaluate_expression(const std::string &name, std::vector &&tokens); -int builtin_directory(const std::vector &, const fdmask &); -int builtin_echo(const std::vector &, const fdmask &); -int builtin_parameters(const std::vector &, const fdmask &); -int builtin_quote(const std::vector &tokens, const fdmask &); -int builtin_set(const std::vector &, const fdmask &); -int builtin_unset(const std::vector &, const fdmask &); -int builtin_export(const std::vector &, const fdmask &); -int builtin_unexport(const std::vector &, const fdmask &); - -int builtin_evaluate(std::vector &&, const fdmask &); #endif diff --git a/phase2-parser.lemon b/phase2-parser.lemon index 7fb6b5f..85d65dc 100644 --- a/phase2-parser.lemon +++ b/phase2-parser.lemon @@ -28,11 +28,13 @@ std::unique_ptr phase2_parser::make() { %type start {void} %type opt_command_list {void} %type command_list {void} +%type opt_command_sep {void} /* these are put into a queue for immmediate execution */ -start ::= opt_command_list. + +start ::= opt_command_list. opt_command_list ::= . opt_command_list ::= command_list. @@ -44,16 +46,30 @@ command_list ::= opt_command(C) sep. { if (C) command_queue.emplace_back(std::move(C)); } +/* +command_list ::= opt_command_sep. +command_list ::= command_list opt_command_sep. + + +opt_command_sep ::= opt_command(C) sep. { + if (C) command_queue.emplace_back(std::move(C)); +} +*/ + /* compound_list is identical to command_list, but it is not executed immediately. */ %type opt_compound_list { command_ptr_vector } -opt_compound_list(RV) ::= . { /* RV */ } -opt_compound_list(RV) ::= compound_list(L). { RV = std::move(L); } - %type compound_list { command_ptr_vector } +//%type opt_paren_list { command_ptr_vector } +//%type paren_list { command_ptr_vector } + + +opt_compound_list ::= . +opt_compound_list(RV) ::= compound_list(L). { RV = std::move(L); } + compound_list(RV) ::= compound_list(L) opt_command(C) sep . { RV = std::move(L); if (C) RV.emplace_back(std::move(C)); @@ -63,21 +79,36 @@ compound_list(RV) ::= opt_command(C) sep. { if (C) RV.emplace_back(std::move(C)); } +/* +opt_paren_list ::= . +opt_paren_list(RV) ::= paren_list(L). { RV = std::move(L); } + +paren_list(RV) ::= opt_command(C). { + if (C) RV.emplace_back(std::move(C)); +} + +paren_list(RV) ::= paren_list(L) sep opt_command(C). { + RV = std::move(L); + if (C) RV.emplace_back(std::move(C)); +} +*/ sep ::= SEMI. sep ::= NL. %type opt_command { command_ptr } opt_command(RV) ::= command(C). { RV = std::move(C); } -opt_command(RV) ::= . { /* RV */ } +opt_command ::= . %type command { command_ptr } +/* nb -- ||, &&, | -- both sides are optional. This does not. */ + command(RV) ::= command(L) PIPE_PIPE opt_nl command(R). { RV = std::make_unique(std::move(L), std::move(R)); } -command(RV) ::= command(L) AMP_AMP opt_nl command(R). { +command(RV) ::= command(L) AMP_AMP opt_nl command(R). { RV = std::make_unique(std::move(L), std::move(R)); } @@ -90,17 +121,48 @@ command(RV) ::= command PIPE opt_nl command. { command(RV) ::= term(T). { RV = std::move(T); } term(RV) ::= COMMAND(C). { RV = std::make_unique(std::move(C)); } -term(RV) ::= EVALUATE(C). { RV = std::make_unique(std::move(C)); } +term(RV) ::= EVALUATE(C). { RV = std::make_unique(std::move(C)); } term(RV) ::= if_command(C). { RV = std::move(C); } term(RV) ::= begin_command(C). { RV = std::move(C); } term(RV) ::= paren_command(C). { RV = std::move(C); } -paren_command(RV) ::= LPAREN(T) opt_compound_list(L) RPAREN(E). { - RV = std::make_unique(@T, std::move(L), std::move(E)); +/* + * fall back to an end error. w/o fallback, it will cause a parse conflict. + */ + /* +%fallback ERROR END RPAREN ELSE ELSE_IF. + +term(RV) ::= ERROR(C). { + RV = std::make_unique(@C, std::move(C)); +} +*/ + +/* opt_compound_list requires a sep after every item -- not ok for paren command! */ +/* +paren_command(RV) ::= LPAREN(T) opt_paren_list(L) RPAREN(E). { + RV = std::make_unique(@T, std::move(L), std::move(T), std::move(E)); +} +*/ + +/* compound list ends with a separator. paren command does not need the final separator */ +paren_command(RV) ::= LPAREN(T) opt_command(C) RPAREN(E). { + command_ptr_vector L; + if (C) L.emplace_back(std::move(C)); + RV = std::make_unique(@T, std::move(L), std::move(T), std::move(E)); } +paren_command(RV) ::= LPAREN(T) compound_list(L) RPAREN(E). { + RV = std::make_unique(@T, std::move(L), std::move(T), std::move(E)); +} + +paren_command(RV) ::= LPAREN(T) compound_list(L) command(C) RPAREN(E). { + if (C) L.emplace_back(std::move(C)); + RV = std::make_unique(@T, std::move(L), std::move(T), std::move(E)); +} + + begin_command(RV) ::= BEGIN(T) sep opt_compound_list(L) END(E). { - RV = std::make_unique(@T, std::move(L), std::move(E)); + RV = std::make_unique(@T, std::move(L), std::move(T), std::move(E)); } if_command(RV) ::= IF(I) sep opt_compound_list(L) END(E). { diff --git a/phase2.rl b/phase2.rl index 98d00e8..2c8fc89 100644 --- a/phase2.rl +++ b/phase2.rl @@ -34,10 +34,10 @@ vstring = [{] - ( (any-[{]) )* + ( (any-[}]) )* [}] $err{ - throw std::runtime_error("### MPW Shell - 's must occur in pairs."); + throw std::runtime_error("### MPW Shell - {s must occur in pairs."); } ; @@ -265,8 +265,28 @@ void phase2_parser::parse_failure() { } void phase2_parser::syntax_error(int yymajor, std::string &yyminor) { - if (!error) - fprintf(stderr, "### Parse error near %s\n", yyminor.c_str()); +/* + switch (yymajor) { + case END: + fprintf(stderr, "### MPW Shell - Extra END command.\n"); + break; + case RPAREN: + fprintf(stderr, "### MPW Shell - Extra ) command.\n"); + break; + + case ELSE: + case ELSE_IF: + fprintf(stderr, "### MPW Shell - ELSE must be within IF ... END.\n"); + break; + + default: + fprintf(stderr, "### Parse error near %s\n", yyminor.c_str()); + break; + } +*/ + + + fprintf(stderr, "### MPW Shell - Parse error near %s\n", yymajor ? yyminor.c_str() : "EOF"); error = true; }