mirror of
https://github.com/ksherlock/mpw-shell.git
synced 2024-12-22 02:30:12 +00:00
initial version
This commit is contained in:
commit
7034e1193e
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.o
|
||||
build/
|
51
CMakeLists.txt
Normal file
51
CMakeLists.txt
Normal file
@ -0,0 +1,51 @@
|
||||
set(CMAKE_CXX_COMPILER "clang++")
|
||||
set(CMAKE_CXX_FLAGS "-std=c++14 -stdlib=libc++ -g -Wall -Wno-unused-const-variable -Wno-unused-variable -Wno-multichar -Wno-c++11-extensions")
|
||||
|
||||
project("mpw-shell")
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
|
||||
add_definitions(-I ${CMAKE_SOURCE_DIR}/)
|
||||
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT mpw-shell-read.cpp
|
||||
COMMAND ragel -p -G2 -o mpw-shell-read.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-read.rl"
|
||||
MAIN_DEPENDENCY mpw-shell-read.rl
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT mpw-shell-expand.cpp
|
||||
COMMAND ragel -p -G2 -o mpw-shell-expand.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-expand.rl"
|
||||
MAIN_DEPENDENCY mpw-shell-expand.rl
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT mpw-shell-token.cpp
|
||||
COMMAND ragel -p -G2 -o mpw-shell-token.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-token.rl"
|
||||
MAIN_DEPENDENCY mpw-shell-token.rl
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT mpw-shell-command.cpp
|
||||
COMMAND ragel -p -G2 -o mpw-shell-command.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-command.rl"
|
||||
MAIN_DEPENDENCY mpw-shell-command.rl
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT value.cpp
|
||||
COMMAND ragel -p -G2 -o value.cpp "${CMAKE_CURRENT_SOURCE_DIR}/value.rl"
|
||||
MAIN_DEPENDENCY value.rl
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT mpw-shell-quote.cpp
|
||||
COMMAND ragel -p -G2 -o mpw-shell-quote.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-quote.rl"
|
||||
MAIN_DEPENDENCY mpw-shell-quote.rl
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
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)
|
||||
|
44
command.h
Normal file
44
command.h
Normal file
@ -0,0 +1,44 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
typedef std::unique_ptr<struct command> command_ptr;
|
||||
typedef std::vector<command_ptr> command_ptr_vector;
|
||||
typedef std::array<command_ptr, 2> command_ptr_pair;
|
||||
|
||||
struct command {
|
||||
enum {
|
||||
|
||||
};
|
||||
int type;
|
||||
virtual ~command();
|
||||
virtual int run();
|
||||
};
|
||||
|
||||
struct simple_command : public command {
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct binary_command : public command {
|
||||
command_ptr_pair children;
|
||||
};
|
||||
|
||||
struct or_command : public binary_command {
|
||||
|
||||
};
|
||||
|
||||
struct and_command : public binary_command {
|
||||
|
||||
};
|
||||
|
||||
struct begin_command : public command {
|
||||
command_ptr_vector children;
|
||||
std::string end;
|
||||
};
|
||||
|
||||
struct if_command : public command {
|
||||
std::string begin;
|
||||
command_ptr_vector children;
|
||||
command_ptr_vector else_clause;
|
||||
std::string end;
|
||||
};
|
155
fdset.h
Normal file
155
fdset.h
Normal file
@ -0,0 +1,155 @@
|
||||
#ifndef __fdset__
|
||||
#define __fdset__
|
||||
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
class fdset;
|
||||
class fdmask;
|
||||
|
||||
/*
|
||||
* fdmask does not own the file descriptors and will not close them.
|
||||
*
|
||||
*/
|
||||
class fdmask {
|
||||
public:
|
||||
|
||||
fdmask() = default;
|
||||
fdmask(const fdmask &) = default;
|
||||
fdmask(fdmask &&) = default;
|
||||
|
||||
fdmask(const std::array<int, 3> &rhs) : _fds(rhs)
|
||||
{}
|
||||
|
||||
#if 0
|
||||
fdmask(std::initializer_list<int> rhs) : _fds(rhs)
|
||||
{}
|
||||
#endif
|
||||
|
||||
fdmask &operator=(const fdmask &) = default;
|
||||
fdmask &operator=(fdmask &&) = default;
|
||||
|
||||
fdmask &operator=(const std::array<int, 3> &rhs) {
|
||||
_fds = rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
#if 0
|
||||
fdmask &operator=(std::initializer_list<int> rhs) {
|
||||
_fds = rhs;
|
||||
}
|
||||
#endif
|
||||
|
||||
void dup() const {
|
||||
// dup fds to stdin/stdout/stderr.
|
||||
// called after fork, before exec.
|
||||
|
||||
|
||||
#define __(index, target) \
|
||||
if (_fds[index] >= 0 && _fds[index] != target) dup2(_fds[index], target)
|
||||
|
||||
__(0, STDIN_FILENO);
|
||||
__(1, STDOUT_FILENO);
|
||||
__(2, STDERR_FILENO);
|
||||
|
||||
#undef __
|
||||
}
|
||||
|
||||
|
||||
int operator[](unsigned index) const {
|
||||
return _fds[index];
|
||||
}
|
||||
|
||||
fdmask &operator|=(const fdmask &rhs) {
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
if (_fds[i] < 0) _fds[i] = rhs._fds[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class fdset;
|
||||
std::array<int, 3> _fds = {{ -1, -1, -1 }};
|
||||
};
|
||||
|
||||
/*
|
||||
* fd set owns it's descriptors and will close them.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class fdset {
|
||||
public:
|
||||
|
||||
fdset() = default;
|
||||
fdset(const fdset &) = delete;
|
||||
fdset(fdset && rhs) {
|
||||
std::swap(rhs._fds, _fds);
|
||||
}
|
||||
|
||||
~fdset() {
|
||||
close();
|
||||
}
|
||||
|
||||
fdset &operator=(const fdset &) = delete;
|
||||
fdset &operator=(fdset &&rhs) {
|
||||
if (&rhs != this) {
|
||||
std::swap(_fds, rhs._fds);
|
||||
rhs.close();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void close(void) {
|
||||
for (int &fd : _fds) {
|
||||
if (fd >= 0) {
|
||||
::close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set(int index, int fd) {
|
||||
std::swap(fd, _fds[index]);
|
||||
if (fd >= 0) ::close(fd);
|
||||
}
|
||||
|
||||
fdmask to_mask() const {
|
||||
return fdmask(_fds);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void reset() {
|
||||
_fds = {{ -1, -1, -1 }};
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::array<int, 3> _fds = {{ -1, -1, -1 }};
|
||||
|
||||
};
|
||||
|
||||
inline fdmask operator|(const fdmask &lhs, const fdmask &rhs) {
|
||||
fdmask tmp(lhs);
|
||||
tmp |= rhs;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
inline fdmask operator|(const fdset &lhs, const fdmask &rhs) {
|
||||
fdmask tmp(lhs.to_mask());
|
||||
tmp |= rhs;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
struct process {
|
||||
std::vector<std::string> arguments;
|
||||
fdset fds;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
506
mpw-shell-builtins.cpp
Normal file
506
mpw-shell-builtins.cpp
Normal file
@ -0,0 +1,506 @@
|
||||
#include "mpw-shell.h"
|
||||
|
||||
#include "fdset.h"
|
||||
#include "value.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cctype>
|
||||
|
||||
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<class FX>
|
||||
std::vector<std::string> getopt(const std::vector<std::string> &argv, FX fx) {
|
||||
|
||||
std::vector<std::string> out;
|
||||
out.reserve(argv.size());
|
||||
|
||||
std::copy_if(argv.begin()+1, argv.end(), std::back_inserter(out), [&fx](const std::string &s){
|
||||
|
||||
if (s.empty()) return false; // ?
|
||||
if (s.front() == '-') {
|
||||
std::for_each(s.begin() + 1, s.end(), fx);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* the fdopen() will assume ownership of the fd and close it.
|
||||
* this is not desirable.
|
||||
*/
|
||||
|
||||
int readfn(void *cookie, char *buffer, int size) {
|
||||
return ::read((int)(ptrdiff_t)cookie, buffer, size);
|
||||
}
|
||||
|
||||
int writefn(void *cookie, const char *buffer, int size) {
|
||||
return ::write((int)(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.
|
||||
return funopen((const void *)(ptrdiff_t)fd, readfn, writefn, nullptr, nullptr);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#undef stdin
|
||||
#undef stdout
|
||||
#undef stderr
|
||||
|
||||
#define stdin io.in
|
||||
#define stdout io.out
|
||||
#define stderr io.err
|
||||
|
||||
int builtin_unset(const std::vector<std::string> &tokens, const fdmask &) {
|
||||
for (auto iter = tokens.begin() + 1; iter != tokens.end(); ++iter) {
|
||||
|
||||
std::string name = *iter;
|
||||
lowercase(name);
|
||||
|
||||
Environment.erase(name);
|
||||
}
|
||||
// unset [no arg] removes ALL variables
|
||||
if (tokens.size() == 1) {
|
||||
Environment.clear();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int builtin_set(const std::vector<std::string> &tokens, const fdmask &fds) {
|
||||
// set var name -- set
|
||||
// set var -- just print the value
|
||||
|
||||
// 3.5 supports -e to also export it.
|
||||
|
||||
io_helper io(fds);
|
||||
|
||||
|
||||
if (tokens.size() == 1) {
|
||||
|
||||
for (const auto &kv : Environment) {
|
||||
std::string name = quote(kv.first);
|
||||
std::string value = quote(kv.second);
|
||||
|
||||
fprintf(stdout, "Set %s%s %s\n",
|
||||
bool(kv.second) ? "-e " : "",
|
||||
name.c_str(), value.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tokens.size() == 2) {
|
||||
std::string name = tokens[1];
|
||||
lowercase(name);
|
||||
auto iter = Environment.find(name);
|
||||
if (iter == Environment.end()) {
|
||||
fprintf(stderr, "### Set - No variable definition exists for %s.\n", name.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
name = quote(name);
|
||||
std::string value = quote(iter->second);
|
||||
fprintf(stdout, "Set %s%s %s\n",
|
||||
bool(iter->second) ? "-e " : "",
|
||||
name.c_str(), value.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool exported = false;
|
||||
|
||||
|
||||
if (tokens.size() == 4 && tokens[1] == "-e") {
|
||||
exported = true;
|
||||
}
|
||||
|
||||
if (tokens.size() > 3 && !exported) {
|
||||
fputs("### Set - Too many parameters were specified.\n", stderr);
|
||||
fputs("# Usage - set [name [value]]\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string name = tokens[1+exported];
|
||||
std::string value = tokens[2+exported];
|
||||
lowercase(name);
|
||||
|
||||
Environment[name] = std::move(EnvironmentEntry(std::move(value), exported));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int export_common(bool export_or_unexport, const std::vector<std::string> &tokens, io_helper &io) {
|
||||
|
||||
const char *name = export_or_unexport ? "Export" : "Unexport";
|
||||
|
||||
struct {
|
||||
int _r = 0;
|
||||
int _s = 0;
|
||||
} flags;
|
||||
bool error = false;
|
||||
|
||||
std::vector<std::string> argv = getopt(tokens, [&](char c){
|
||||
switch(c) {
|
||||
case 'r':
|
||||
case 'R':
|
||||
flags._r = true;
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
flags._s = true;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "### %s - \"-%c\" is not an option.\n", name, c);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
fprintf(stderr, "# Usage - %s [-r | -s | name...]\n", name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argv.empty()) {
|
||||
if (flags._r && flags._s) goto conflict;
|
||||
|
||||
// list of exported vars.
|
||||
// -r will generate unexport commands for exported variables.
|
||||
// -s will only print the names.
|
||||
|
||||
|
||||
name = export_or_unexport ? "Export " : "Unexport ";
|
||||
|
||||
for (const auto &kv : Environment) {
|
||||
const std::string& vname = kv.first;
|
||||
if (kv.second == export_or_unexport)
|
||||
fprintf(stdout, "%s%s\n", flags._s ? "" : name, quote(vname).c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
// mark as exported.
|
||||
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
conflict:
|
||||
fprintf(stderr, "### %s - Conflicting options or parameters were specified.\n", name);
|
||||
fprintf(stderr, "# Usage - %s [-r | -s | name...]\n", name);
|
||||
return 1;
|
||||
}
|
||||
int builtin_export(const std::vector<std::string> &tokens, const fdmask &fds) {
|
||||
|
||||
io_helper io(fds);
|
||||
return export_common(true, tokens, io);
|
||||
}
|
||||
|
||||
int builtin_unexport(const std::vector<std::string> &tokens, const fdmask &fds) {
|
||||
|
||||
io_helper io(fds);
|
||||
return export_common(false, tokens, io);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int builtin_echo(const std::vector<std::string> &tokens, const fdmask &fds) {
|
||||
|
||||
io_helper io(fds);
|
||||
|
||||
bool space = false;
|
||||
bool n = false;
|
||||
|
||||
for (auto iter = tokens.begin() + 1; iter != tokens.end(); ++iter) {
|
||||
|
||||
const std::string &s = *iter;
|
||||
if (s == "-n" || s == "-N") {
|
||||
n = true;
|
||||
continue;
|
||||
}
|
||||
if (space) {
|
||||
fputs(" ", stdout);
|
||||
}
|
||||
fputs(s.c_str(), stdout);
|
||||
space = true;
|
||||
}
|
||||
if (!n) fputs("\n", stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int builtin_quote(const std::vector<std::string> &tokens, const fdmask &fds) {
|
||||
// todo...
|
||||
|
||||
io_helper io(fds);
|
||||
|
||||
bool space = false;
|
||||
bool n = false;
|
||||
|
||||
for (auto iter = tokens.begin() + 1; iter != tokens.end(); ++iter) {
|
||||
|
||||
std::string s = *iter;
|
||||
if (s == "-n" || s == "-N") {
|
||||
n = true;
|
||||
continue;
|
||||
}
|
||||
if (space) {
|
||||
fputs(" ", stdout);
|
||||
}
|
||||
s = quote(std::move(s));
|
||||
fputs(s.c_str(), stdout);
|
||||
space = true;
|
||||
}
|
||||
if (!n) fputs("\n", stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int builtin_parameters(const std::vector<std::string> &argv, const fdmask &fds) {
|
||||
|
||||
io_helper io(fds);
|
||||
|
||||
int i = 0;
|
||||
for (const auto &s : argv) {
|
||||
fprintf(stdout, "{%d} %s\n", i++, s.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int builtin_directory(const std::vector<std::string> &tokens, const fdmask &fds) {
|
||||
// directory [-q]
|
||||
// directory path
|
||||
|
||||
// for relative names, uses {DirectoryPath} (if set) rather than .
|
||||
// set DirectoryPath ":,{MPW},{MPW}Projects:"
|
||||
|
||||
io_helper io(fds);
|
||||
|
||||
bool q = false;
|
||||
bool error = false;
|
||||
|
||||
std::vector<std::string> argv = getopt(tokens, [&](char c){
|
||||
switch(c)
|
||||
{
|
||||
case 'q':
|
||||
case 'Q':
|
||||
q = true;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "### Directory - \"-%c\" is not an option.\n", c);
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
fputs("# Usage - Directory [-q | directory]\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argv.size() > 1) {
|
||||
fputs("### Directory - Too many parameters were specified.\n", stderr);
|
||||
fputs("# Usage - Directory [-q | directory]\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
if (argv.size() == 1) {
|
||||
//cd
|
||||
if (q) {
|
||||
fputs("### Directory - Conflicting options or parameters were specified.\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
// pwd
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_assignment(int type) {
|
||||
switch(type)
|
||||
{
|
||||
case '=':
|
||||
case '+=':
|
||||
case '-=':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int builtin_evaluate(std::vector<token> &&tokens, const fdmask &fds) {
|
||||
// evaluate expression
|
||||
// evaluate variable = expression
|
||||
// evaluate variable += expression
|
||||
// evaluate variable -= expression
|
||||
|
||||
// flags -- -h -o -b -- print in hex, octal, or binary
|
||||
|
||||
// convert the arguments to a stack.
|
||||
|
||||
|
||||
int output = 'd';
|
||||
|
||||
io_helper io(fds);
|
||||
|
||||
std::reverse(tokens.begin(), tokens.end());
|
||||
|
||||
// remove 'Evaluate'
|
||||
tokens.pop_back();
|
||||
|
||||
// check for -h -x -o
|
||||
if (tokens.size() >= 2 && tokens.back().type == '-') {
|
||||
|
||||
const token &t = tokens[tokens.size() - 2];
|
||||
if (t.type == token::text && t.string.length() == 1) {
|
||||
int flag = tolower(t.string[0]);
|
||||
switch(flag) {
|
||||
case 'o':
|
||||
case 'h':
|
||||
case 'b':
|
||||
output = flag;
|
||||
tokens.pop_back();
|
||||
tokens.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (tokens.size() >= 2 && tokens.back().type == token::text)
|
||||
{
|
||||
int type = tokens[tokens.size() -2].type;
|
||||
|
||||
if (is_assignment(type)) {
|
||||
|
||||
std::string name = tokens.back().string;
|
||||
lowercase(name);
|
||||
|
||||
tokens.pop_back();
|
||||
tokens.pop_back();
|
||||
|
||||
int32_t i = evaluate_expression("Evaluate", std::move(tokens));
|
||||
|
||||
switch(type) {
|
||||
case '=':
|
||||
Environment[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;
|
||||
|
||||
switch(type) {
|
||||
case '+=':
|
||||
i = old.to_number() + i;
|
||||
break;
|
||||
case '-=':
|
||||
i = old.to_number() - i;
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t i = evaluate_expression("Evaluate", std::move(tokens));
|
||||
|
||||
// todo -- format based on -h, -o, or -b flag.
|
||||
if (output == 'h') {
|
||||
fprintf(stdout, "0x%08x\n", i);
|
||||
return 0;
|
||||
}
|
||||
if (output == 'b') {
|
||||
fputc('0', stdout);
|
||||
fputc('b', stdout);
|
||||
for (int j = 0; j < 32; ++j) {
|
||||
fputc(i & 0x80000000 ? '1' : '0', stdout);
|
||||
i <<= 1;
|
||||
}
|
||||
fputc('\n', stdout);
|
||||
return 0;
|
||||
|
||||
}
|
||||
if (output == 'o') {
|
||||
// octal.
|
||||
fprintf(stdout, "0%o\n", i);
|
||||
return 0;
|
||||
}
|
||||
fprintf(stdout, "%d\n", i);
|
||||
return 0;
|
||||
}
|
123
mpw-shell-command.rl
Normal file
123
mpw-shell-command.rl
Normal file
@ -0,0 +1,123 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mpw-shell.h"
|
||||
|
||||
%%{
|
||||
machine classify;
|
||||
alphtype unsigned char;
|
||||
|
||||
ws = [ \t];
|
||||
|
||||
IF = /if/i;
|
||||
ELSE = /else/i;
|
||||
END = /end/i;
|
||||
EVALUATE = /evaluate/i;
|
||||
|
||||
|
||||
main := |*
|
||||
IF %eof{ return command_if; };
|
||||
IF ws => {return command_if; };
|
||||
|
||||
ELSE %eof{ return command_else;};
|
||||
ELSE ws => { return command_else; };
|
||||
|
||||
ELSE ws+ IF %eof{ return command_else_if; };
|
||||
ELSE ws+ IF ws => {return command_else_if; };
|
||||
|
||||
END %eof{ return command_end; };
|
||||
END ws => {return command_end; };
|
||||
|
||||
EVALUATE %eof{ return command_evaluate; };
|
||||
EVALUATE ws => {return command_evaluate; };
|
||||
|
||||
|
||||
*|;
|
||||
|
||||
}%%
|
||||
|
||||
|
||||
int classify(const std::string &line) {
|
||||
|
||||
%% write data;
|
||||
|
||||
int cs;
|
||||
int act;
|
||||
|
||||
const unsigned char *p = (const unsigned char *)line.data();
|
||||
const unsigned char *pe = (const unsigned char *)line.data() + line.size();
|
||||
const unsigned char *eof = pe;
|
||||
const unsigned char *te, *ts;
|
||||
|
||||
%%write init;
|
||||
|
||||
%%write exec;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generates a linked-list of commands. Why? Because it also checks
|
||||
* for shell-special syntax (currently if / else /end only) and
|
||||
* adds pointers to make executing them easier.
|
||||
*
|
||||
*/
|
||||
command_ptr build_command(const std::vector<std::string> &lines) {
|
||||
|
||||
std::vector<command_ptr> if_stack;
|
||||
|
||||
command_ptr head;
|
||||
command_ptr prev;
|
||||
|
||||
for (const auto &line : lines) {
|
||||
if (line.empty()) continue;
|
||||
|
||||
int type = classify(line);
|
||||
command_ptr c = std::make_shared<command>(type, line);
|
||||
|
||||
if (!head) head = c;
|
||||
if (!prev) prev = c;
|
||||
else {
|
||||
prev->next = c;
|
||||
prev = c;
|
||||
}
|
||||
|
||||
// if stack...
|
||||
switch (type) {
|
||||
case command_if:
|
||||
if_stack.push_back(c);
|
||||
break;
|
||||
|
||||
case command_else:
|
||||
case command_else_if:
|
||||
|
||||
if (if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Else must be within if ... end.");
|
||||
}
|
||||
|
||||
if_stack.back()->alternate = c;
|
||||
if_stack.back() = c;
|
||||
break;
|
||||
|
||||
case command_end:
|
||||
if (if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Extra end command.");
|
||||
}
|
||||
if_stack.back()->alternate = c;
|
||||
if_stack.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Unterminated if command.");
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
301
mpw-shell-commands.c
Normal file
301
mpw-shell-commands.c
Normal file
@ -0,0 +1,301 @@
|
||||
|
||||
#line 1 "mpw-shell-commands.rl"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef std::shared_ptr<command> command_ptr;
|
||||
typedef std::weak_ptr<command> weak_command_ptr;
|
||||
|
||||
class command {
|
||||
enum type {
|
||||
command_if = 1,
|
||||
command_else,
|
||||
command_else_if,
|
||||
command_end
|
||||
} = 0;
|
||||
std::string line;
|
||||
command_ptr next;
|
||||
weak_command_ptr alternate; // if -> else -> end.
|
||||
};
|
||||
|
||||
|
||||
|
||||
#line 49 "mpw-shell-commands.rl"
|
||||
|
||||
|
||||
|
||||
int classify(const std::string &line) {
|
||||
|
||||
|
||||
#line 35 "mpw-shell-commands.c"
|
||||
static const int classify_start = 8;
|
||||
static const int classify_first_final = 8;
|
||||
static const int classify_error = 0;
|
||||
|
||||
static const int classify_en_main = 8;
|
||||
|
||||
|
||||
#line 55 "mpw-shell-commands.rl"
|
||||
|
||||
int cs;
|
||||
const unsigned char *p = (const unsigned char *)line.data();
|
||||
const unsigned char *pe = (const unsigned char *)line.data() + line.size();
|
||||
const unsigned char *eof = pe;
|
||||
const unsigned char *te, *ts;
|
||||
|
||||
|
||||
#line 52 "mpw-shell-commands.c"
|
||||
{
|
||||
cs = classify_start;
|
||||
ts = 0;
|
||||
te = 0;
|
||||
act = 0;
|
||||
}
|
||||
|
||||
#line 63 "mpw-shell-commands.rl"
|
||||
|
||||
|
||||
#line 63 "mpw-shell-commands.c"
|
||||
{
|
||||
if ( p == pe )
|
||||
goto _test_eof;
|
||||
switch ( cs )
|
||||
{
|
||||
tr5:
|
||||
#line 40 "mpw-shell-commands.rl"
|
||||
{{p = ((te))-1;}{ return command_else; }}
|
||||
goto st8;
|
||||
tr13:
|
||||
#line 39 "mpw-shell-commands.rl"
|
||||
{ return command_else;}
|
||||
#line 39 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr14:
|
||||
#line 39 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr16:
|
||||
#line 40 "mpw-shell-commands.rl"
|
||||
{te = p;p--;{ return command_else; }}
|
||||
goto st8;
|
||||
tr17:
|
||||
#line 42 "mpw-shell-commands.rl"
|
||||
{ return command_else_if; }
|
||||
#line 42 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr18:
|
||||
#line 42 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr19:
|
||||
#line 43 "mpw-shell-commands.rl"
|
||||
{te = p+1;{return command_else_if; }}
|
||||
goto st8;
|
||||
tr20:
|
||||
#line 45 "mpw-shell-commands.rl"
|
||||
{ return command_end; }
|
||||
#line 45 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr21:
|
||||
#line 45 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr22:
|
||||
#line 46 "mpw-shell-commands.rl"
|
||||
{te = p+1;{return command_end; }}
|
||||
goto st8;
|
||||
tr23:
|
||||
#line 36 "mpw-shell-commands.rl"
|
||||
{ return command_if; }
|
||||
#line 36 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr24:
|
||||
#line 36 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr25:
|
||||
#line 37 "mpw-shell-commands.rl"
|
||||
{te = p+1;{return command_if; }}
|
||||
goto st8;
|
||||
st8:
|
||||
#line 1 "NONE"
|
||||
{ts = 0;}
|
||||
if ( ++p == pe )
|
||||
goto _test_eof8;
|
||||
case 8:
|
||||
#line 1 "NONE"
|
||||
{ts = p;}
|
||||
#line 137 "mpw-shell-commands.c"
|
||||
switch( (*p) ) {
|
||||
case 69u: goto st1;
|
||||
case 73u: goto st7;
|
||||
case 101u: goto st1;
|
||||
case 105u: goto st7;
|
||||
}
|
||||
goto st0;
|
||||
st0:
|
||||
cs = 0;
|
||||
goto _out;
|
||||
st1:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof1;
|
||||
case 1:
|
||||
switch( (*p) ) {
|
||||
case 76u: goto st2;
|
||||
case 78u: goto st6;
|
||||
case 108u: goto st2;
|
||||
case 110u: goto st6;
|
||||
}
|
||||
goto st0;
|
||||
st2:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof2;
|
||||
case 2:
|
||||
switch( (*p) ) {
|
||||
case 83u: goto st3;
|
||||
case 115u: goto st3;
|
||||
}
|
||||
goto st0;
|
||||
st3:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof3;
|
||||
case 3:
|
||||
switch( (*p) ) {
|
||||
case 69u: goto st9;
|
||||
case 101u: goto st9;
|
||||
}
|
||||
goto st0;
|
||||
st9:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof9;
|
||||
case 9:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto tr15;
|
||||
case 32u: goto tr15;
|
||||
}
|
||||
goto tr14;
|
||||
tr15:
|
||||
#line 1 "NONE"
|
||||
{te = p+1;}
|
||||
goto st10;
|
||||
st10:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof10;
|
||||
case 10:
|
||||
#line 194 "mpw-shell-commands.c"
|
||||
switch( (*p) ) {
|
||||
case 9u: goto st4;
|
||||
case 32u: goto st4;
|
||||
case 73u: goto st5;
|
||||
case 105u: goto st5;
|
||||
}
|
||||
goto tr16;
|
||||
st4:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof4;
|
||||
case 4:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto st4;
|
||||
case 32u: goto st4;
|
||||
case 73u: goto st5;
|
||||
case 105u: goto st5;
|
||||
}
|
||||
goto tr5;
|
||||
st5:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof5;
|
||||
case 5:
|
||||
switch( (*p) ) {
|
||||
case 70u: goto st11;
|
||||
case 102u: goto st11;
|
||||
}
|
||||
goto tr5;
|
||||
st11:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof11;
|
||||
case 11:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto tr19;
|
||||
case 32u: goto tr19;
|
||||
}
|
||||
goto tr18;
|
||||
st6:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof6;
|
||||
case 6:
|
||||
switch( (*p) ) {
|
||||
case 68u: goto st12;
|
||||
case 100u: goto st12;
|
||||
}
|
||||
goto st0;
|
||||
st12:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof12;
|
||||
case 12:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto tr22;
|
||||
case 32u: goto tr22;
|
||||
}
|
||||
goto tr21;
|
||||
st7:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof7;
|
||||
case 7:
|
||||
switch( (*p) ) {
|
||||
case 70u: goto st13;
|
||||
case 102u: goto st13;
|
||||
}
|
||||
goto st0;
|
||||
st13:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof13;
|
||||
case 13:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto tr25;
|
||||
case 32u: goto tr25;
|
||||
}
|
||||
goto tr24;
|
||||
}
|
||||
_test_eof8: cs = 8; goto _test_eof;
|
||||
_test_eof1: cs = 1; goto _test_eof;
|
||||
_test_eof2: cs = 2; goto _test_eof;
|
||||
_test_eof3: cs = 3; goto _test_eof;
|
||||
_test_eof9: cs = 9; goto _test_eof;
|
||||
_test_eof10: cs = 10; goto _test_eof;
|
||||
_test_eof4: cs = 4; goto _test_eof;
|
||||
_test_eof5: cs = 5; goto _test_eof;
|
||||
_test_eof11: cs = 11; goto _test_eof;
|
||||
_test_eof6: cs = 6; goto _test_eof;
|
||||
_test_eof12: cs = 12; goto _test_eof;
|
||||
_test_eof7: cs = 7; goto _test_eof;
|
||||
_test_eof13: cs = 13; goto _test_eof;
|
||||
|
||||
_test_eof: {}
|
||||
if ( p == eof )
|
||||
{
|
||||
switch ( cs ) {
|
||||
case 9: goto tr13;
|
||||
case 10: goto tr16;
|
||||
case 4: goto tr5;
|
||||
case 5: goto tr5;
|
||||
case 11: goto tr17;
|
||||
case 12: goto tr20;
|
||||
case 13: goto tr23;
|
||||
}
|
||||
}
|
||||
|
||||
_out: {}
|
||||
}
|
||||
|
||||
#line 65 "mpw-shell-commands.rl"
|
||||
|
||||
return 0;
|
||||
}
|
295
mpw-shell-execute.cpp
Normal file
295
mpw-shell-execute.cpp
Normal file
@ -0,0 +1,295 @@
|
||||
#include "mpw-shell.h"
|
||||
#include "fdset.h"
|
||||
#include "value.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sysexits.h>
|
||||
|
||||
|
||||
/*
|
||||
* Relevant shell variables (not currently supported)
|
||||
*
|
||||
* Echo {Echo} # control the echoing of commands to diagnostic output
|
||||
* Echo {Exit} # control script termination based on {Status}
|
||||
*
|
||||
*/
|
||||
|
||||
typedef std::vector<std::string> vs;
|
||||
|
||||
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<std::string, int (*)(const std::vector<std::string> &, 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},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
typedef std::pair<int, command_ptr> icp;
|
||||
|
||||
|
||||
|
||||
icp execute_all(command_ptr cmd);
|
||||
|
||||
// returns status and pointer to the next command to execute.
|
||||
icp execute_if(command_ptr cmd) {
|
||||
|
||||
assert(cmd && cmd->type == command_if);
|
||||
// evaluate condition...
|
||||
// skip to else or end.
|
||||
|
||||
command_ptr head(cmd);
|
||||
|
||||
int status = 0;
|
||||
|
||||
// find the end pointer.
|
||||
// if ... end > file.text
|
||||
// redirects all output within the block.
|
||||
|
||||
command_ptr end = head;
|
||||
while (end && end->type != command_end) {
|
||||
end = end->alternate.lock();
|
||||
}
|
||||
|
||||
|
||||
fdmask fds; // todo -- inherit from block, can be parsed from end line.
|
||||
|
||||
|
||||
|
||||
fprintf(stdout, " %s ... %s\n", cmd->string.c_str(), end ? end->string.c_str() : "");
|
||||
|
||||
// todo -- indent levels.
|
||||
while(cmd && cmd->type != command_end) {
|
||||
|
||||
int32_t e;
|
||||
|
||||
std::string s = cmd->string;
|
||||
s = expand_vars(s, Environment);
|
||||
|
||||
auto tokens = tokenize(s, true);
|
||||
|
||||
std::reverse(tokens.begin(), tokens.end());
|
||||
e = 0;
|
||||
status = 0;
|
||||
switch(cmd->type) {
|
||||
case command_else_if:
|
||||
tokens.pop_back();
|
||||
case command_if:
|
||||
tokens.pop_back();
|
||||
try {
|
||||
e = evaluate_expression("If", std::move(tokens));
|
||||
} catch (std::exception &ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
status = -5;
|
||||
}
|
||||
break;
|
||||
case command_else:
|
||||
e = 1;
|
||||
if (tokens.size() > 1) {
|
||||
fprintf(stderr, "### Else - Missing if keyword.\n");
|
||||
fprintf(stderr, "# Usage - Else [if expression...]\n");
|
||||
e = 0;
|
||||
status = -3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (e) {
|
||||
command_ptr tmp;
|
||||
std::tie(status, tmp) = execute_all(cmd->next);
|
||||
break;
|
||||
}
|
||||
// skip to next condition.
|
||||
cmd = cmd->alternate.lock();
|
||||
}
|
||||
|
||||
// todo -- print but don't execute remaining alternates
|
||||
|
||||
// print the end tokens... [ doesn't include other tokens.]
|
||||
fprintf(stdout, " End\n");
|
||||
|
||||
return std::make_pair(status, end); // return end token -- will advance later.
|
||||
|
||||
}
|
||||
|
||||
int execute_evaluate(command_ptr cmd) {
|
||||
|
||||
fdmask fds; // todo -- inherit from block.
|
||||
|
||||
std::string s = cmd->string;
|
||||
s = expand_vars(s, Environment);
|
||||
|
||||
|
||||
fprintf(stdout, " %s\n", s.c_str());
|
||||
|
||||
auto tokens = tokenize(s, true);
|
||||
|
||||
return builtin_evaluate(std::move(tokens), fds);
|
||||
}
|
||||
|
||||
|
||||
int execute_external(const std::vector<std::string> &argv, const fdmask &fds) {
|
||||
|
||||
std::vector<char *> 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 -1;
|
||||
fprintf(stderr, "waitpid - unexpected result\n");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int execute_one(command_ptr cmd) {
|
||||
|
||||
if (!cmd) return 0;
|
||||
|
||||
assert(cmd && cmd->type == 0);
|
||||
|
||||
|
||||
// todo -- before variable expansion,
|
||||
// expand |, ||, && control structures.
|
||||
// (possibly when classifing.)
|
||||
|
||||
std::string s = cmd->string;
|
||||
s = expand_vars(s, Environment);
|
||||
|
||||
|
||||
fprintf(stdout, " %s\n", s.c_str());
|
||||
|
||||
auto tokens = tokenize(s);
|
||||
|
||||
process p;
|
||||
parse_tokens(std::move(tokens), p);
|
||||
|
||||
|
||||
|
||||
fdmask fds = p.fds.to_mask();
|
||||
|
||||
std::string name = p.arguments.front();
|
||||
lowercase(name);
|
||||
|
||||
auto iter = builtins.find(name);
|
||||
if (iter != builtins.end()) {
|
||||
int status = iter->second(p.arguments, fds);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
return execute_external(p.arguments, fds);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
icp execute_all(command_ptr cmd) {
|
||||
if (!cmd) return std::make_pair(0, cmd);
|
||||
|
||||
int status;
|
||||
|
||||
while(cmd) {
|
||||
|
||||
unsigned type = cmd->type;
|
||||
switch(type)
|
||||
{
|
||||
case command_evaluate:
|
||||
status = execute_evaluate(cmd);
|
||||
break;
|
||||
|
||||
default:
|
||||
status = execute_one(cmd);
|
||||
break;
|
||||
|
||||
case command_if:
|
||||
std::tie(status, cmd) = execute_if(cmd);
|
||||
break;
|
||||
|
||||
case command_end:
|
||||
case command_else:
|
||||
case command_else_if:
|
||||
return std::make_pair(status, cmd);
|
||||
}
|
||||
|
||||
Environment["status"] = std::to_string(status);
|
||||
|
||||
if (status != 0) {
|
||||
// only if Environment["Exit"] ?
|
||||
throw std::runtime_error("### MPW Shell - Execution of input terminated.");
|
||||
}
|
||||
cmd = cmd->next;
|
||||
}
|
||||
|
||||
return std::make_pair(status, cmd);
|
||||
}
|
||||
|
||||
int execute(command_ptr cmd) {
|
||||
int status;
|
||||
std::tie(status, cmd) = execute_all(cmd);
|
||||
return status;
|
||||
}
|
132
mpw-shell-expand.rl
Normal file
132
mpw-shell-expand.rl
Normal file
@ -0,0 +1,132 @@
|
||||
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mpw-shell.h"
|
||||
|
||||
|
||||
%%{
|
||||
machine line_parser;
|
||||
alphtype unsigned char;
|
||||
|
||||
escape = 0xb6;
|
||||
ws = [ \t];
|
||||
nl = '\n';
|
||||
|
||||
action push_back {
|
||||
line.push_back(fc);
|
||||
}
|
||||
action push_back_escape {
|
||||
line.push_back(escape);
|
||||
line.push_back(fc);
|
||||
}
|
||||
|
||||
|
||||
sstring =
|
||||
['] $push_back
|
||||
( (any-nl-[']) $push_back )*
|
||||
['] $push_back
|
||||
$err{
|
||||
fprintf(stderr, "### MPW Shell - 's must occur in pairs.\n");
|
||||
}
|
||||
;
|
||||
|
||||
# same quoting logic as ' string
|
||||
vstring =
|
||||
'{'
|
||||
( (any-nl-'}') ${var.push_back(fc); } )*
|
||||
'}'
|
||||
${
|
||||
if (!var.empty()) {
|
||||
|
||||
// flag to pass through vs "" ?
|
||||
auto iter = env.find(var);
|
||||
if (iter == env.end()) {
|
||||
line.push_back('{');
|
||||
line.append(var);
|
||||
line.push_back('}');
|
||||
}
|
||||
else {
|
||||
line.append((std::string)iter->second);
|
||||
}
|
||||
}
|
||||
var.clear();
|
||||
}
|
||||
$err{
|
||||
fprintf(stderr, "### MPW Shell - {s must occur in pairs.\n");
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
# double-quoted string.
|
||||
# escape \n is ignored. others do nothing.
|
||||
dstring =
|
||||
["] $push_back
|
||||
(
|
||||
escape (
|
||||
nl ${ /* esc newline */ }
|
||||
|
|
||||
(any-nl) $push_back_escape
|
||||
)
|
||||
|
|
||||
vstring
|
||||
|
|
||||
(any-escape-nl-["{]) $push_back
|
||||
)* ["] $push_back
|
||||
$err{
|
||||
fprintf(stderr, "### MPW Shell - \"s must occur in pairs.\n");
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
main :=
|
||||
(
|
||||
sstring
|
||||
|
|
||||
dstring
|
||||
|
|
||||
vstring
|
||||
|
|
||||
escape any $push_back_escape
|
||||
|
|
||||
(any-['"{]) $push_back
|
||||
)*
|
||||
;
|
||||
|
||||
|
||||
|
||||
|
||||
}%%
|
||||
|
||||
|
||||
|
||||
%% write data;
|
||||
|
||||
|
||||
/*
|
||||
* has to be done separately since you can do dumb stuff like:
|
||||
* set q '"' ; echo {q} dsfsdf"
|
||||
*/
|
||||
|
||||
std::string expand_vars(const std::string &s, const std::unordered_map<std::string, EnvironmentEntry> &env) {
|
||||
|
||||
if (s.find('{') == s.npos) return s;
|
||||
std::string var;
|
||||
std::string line;
|
||||
|
||||
int cs;
|
||||
const unsigned char *p = (const unsigned char *)s.data();
|
||||
const unsigned char *pe = (const unsigned char *)s.data() + s.size();
|
||||
const unsigned char *eof = pe;
|
||||
|
||||
%%write init;
|
||||
|
||||
%%write exec;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
406
mpw-shell-parser.cpp
Normal file
406
mpw-shell-parser.cpp
Normal file
@ -0,0 +1,406 @@
|
||||
#include "mpw-shell.h"
|
||||
#include "fdset.h"
|
||||
#include "value.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/*
|
||||
* I'm sick of fighting with lemon. Just generate it by hand.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template<class T>
|
||||
T pop(std::vector<T> &v) {
|
||||
T t = std::move(v.back());
|
||||
v.pop_back();
|
||||
return t;
|
||||
}
|
||||
|
||||
int open(const std::string &name, int flags) {
|
||||
|
||||
// dup2 does not copy the O_CLOEXEC flag so it's safe to use.
|
||||
|
||||
int fd = ::open(name.c_str(), flags | O_CLOEXEC, 0666);
|
||||
if (fd < 0) {
|
||||
std::string error = "### MPW Shell - Unable to open ";
|
||||
error.push_back('"');
|
||||
error.append(name);
|
||||
error.push_back('"');
|
||||
error.push_back('.');
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
void parse_tokens(std::vector<token> &&tokens, process &p) {
|
||||
|
||||
|
||||
fdset fds;
|
||||
std::vector<std::string> argv;
|
||||
|
||||
std::reverse(tokens.begin(), tokens.end());
|
||||
argv.reserve(tokens.size());
|
||||
|
||||
// first token is always treated as a string.
|
||||
token t = pop(tokens);
|
||||
argv.emplace_back(std::move(t.string));
|
||||
|
||||
while(!tokens.empty()) {
|
||||
|
||||
t = pop(tokens);
|
||||
|
||||
switch (t.type) {
|
||||
|
||||
// >, >> -- redirect stdout.
|
||||
case '>':
|
||||
case '>>':
|
||||
{
|
||||
int flags;
|
||||
if (t.type == '>') flags = O_WRONLY | O_CREAT | O_TRUNC;
|
||||
else flags = O_WRONLY | O_CREAT | O_APPEND;
|
||||
|
||||
if (tokens.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Missing file name.");
|
||||
}
|
||||
token name = pop(tokens);
|
||||
int fd = open(name.string, flags);
|
||||
fds.set(1, fd);
|
||||
}
|
||||
break;
|
||||
|
||||
// < -- redirect stdin.
|
||||
case '<':
|
||||
{
|
||||
int flags = O_RDONLY;
|
||||
|
||||
if (tokens.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Missing file name.");
|
||||
}
|
||||
token name = pop(tokens);
|
||||
int fd = open(name.string, flags);
|
||||
fds.set(0, fd);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
argv.emplace_back(std::move(t.string));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
p.arguments = std::move(argv);
|
||||
p.fds = std::move(fds);
|
||||
}
|
||||
|
||||
|
||||
class expression_parser {
|
||||
|
||||
public:
|
||||
|
||||
expression_parser(const std::string &n, std::vector<token> &&t) :
|
||||
name(n), tokens(std::move(t))
|
||||
{}
|
||||
|
||||
expression_parser(const expression_parser &) = delete;
|
||||
expression_parser(expression_parser &&) = delete;
|
||||
|
||||
expression_parser& operator=(const expression_parser &) = delete;
|
||||
expression_parser& operator=(expression_parser &&) = delete;
|
||||
|
||||
// returns integer value of the expression.
|
||||
int32_t evaluate();
|
||||
|
||||
|
||||
private:
|
||||
|
||||
value terminal();
|
||||
value unary();
|
||||
value binary();
|
||||
|
||||
|
||||
value eval(int op, value &lhs, value &rhs);
|
||||
|
||||
[[noreturn]] void expect_binary_operator();
|
||||
[[noreturn]] void end_of_expression();
|
||||
[[noreturn]] void divide_by_zero();
|
||||
|
||||
int peek_type() const;
|
||||
token next();
|
||||
static int precedence(int);
|
||||
|
||||
void skip() {
|
||||
if (!tokens.empty()) tokens.pop_back();
|
||||
}
|
||||
|
||||
const std::string &name;
|
||||
std::vector<token> tokens;
|
||||
};
|
||||
|
||||
int expression_parser::peek_type() const {
|
||||
if (tokens.empty()) return token::eof;
|
||||
return tokens.back().type;
|
||||
}
|
||||
|
||||
token expression_parser::next() {
|
||||
if (tokens.empty()) return token("", token::eof); // error?
|
||||
return pop(tokens);
|
||||
}
|
||||
|
||||
void expression_parser::expect_binary_operator() {
|
||||
token t = next();
|
||||
|
||||
std::string error;
|
||||
error = "### " + name;
|
||||
error += " - Expected a binary operator when \"";
|
||||
error += t.string;
|
||||
error += "\" was encountered.";
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
|
||||
void expression_parser::end_of_expression() {
|
||||
std::string error;
|
||||
error = "### " + name + " - Unexpected end of expression.";
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
|
||||
void expression_parser::divide_by_zero() {
|
||||
std::string error;
|
||||
error = "### " + name + " - Attempt to divide by zero.";
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
|
||||
|
||||
value expression_parser::binary() {
|
||||
|
||||
std::vector<value> output;
|
||||
std::vector<std::pair<int, int>> operators;
|
||||
|
||||
value v = unary();
|
||||
|
||||
output.emplace_back(std::move(v));
|
||||
|
||||
for(;;) {
|
||||
|
||||
// check for an operator.
|
||||
|
||||
int type = peek_type();
|
||||
if (type == token::eof) break;
|
||||
if (type == ')') break;
|
||||
|
||||
int p = precedence(type);
|
||||
if (!p) expect_binary_operator();
|
||||
skip();
|
||||
|
||||
while (!operators.empty() && operators.back().second <= p) {
|
||||
// reduce top ops.
|
||||
int op = operators.back().first;
|
||||
operators.pop_back();
|
||||
value rhs = pop(output);
|
||||
value lhs = pop(output);
|
||||
|
||||
output.emplace_back(eval(op, lhs, rhs));
|
||||
}
|
||||
|
||||
|
||||
operators.push_back(std::make_pair(type, p));
|
||||
|
||||
v = unary();
|
||||
|
||||
output.emplace_back(std::move(v));
|
||||
|
||||
}
|
||||
|
||||
// reduce...
|
||||
while (!operators.empty()) {
|
||||
|
||||
int op = pop(operators).first;
|
||||
value rhs = pop(output);
|
||||
value lhs = pop(output);
|
||||
|
||||
output.emplace_back(eval(op, lhs, rhs));
|
||||
}
|
||||
|
||||
if (output.size() != 1) throw std::runtime_error("binary stack error");
|
||||
return pop(output);
|
||||
}
|
||||
|
||||
int expression_parser::precedence(int op) {
|
||||
switch (op) {
|
||||
|
||||
case '*':
|
||||
case '%':
|
||||
case '/':
|
||||
return 3;
|
||||
|
||||
case '+':
|
||||
case '-':
|
||||
return 4;
|
||||
|
||||
case '>>':
|
||||
case '<<':
|
||||
return 5;
|
||||
|
||||
case '<':
|
||||
case '<=':
|
||||
case '>':
|
||||
case '>=':
|
||||
return 6;
|
||||
|
||||
case '==':
|
||||
case '!=':
|
||||
case token::equivalent:
|
||||
case token::not_equivalent:
|
||||
return 7;
|
||||
case '&':
|
||||
return 8;
|
||||
case '^':
|
||||
return 9;
|
||||
case '|':
|
||||
return 10;
|
||||
case '&&':
|
||||
return 11;
|
||||
case '||':
|
||||
return 12;
|
||||
}
|
||||
return 0;
|
||||
//throw std::runtime_error("unimplemented op";);
|
||||
}
|
||||
|
||||
value expression_parser::eval(int op, value &lhs, value &rhs) {
|
||||
switch (op) {
|
||||
|
||||
case '*':
|
||||
return lhs.to_number() * rhs.to_number();
|
||||
|
||||
case '/':
|
||||
if (!rhs.to_number()) divide_by_zero();
|
||||
return lhs.to_number() / rhs.to_number();
|
||||
|
||||
case '%':
|
||||
if (!rhs.to_number()) divide_by_zero();
|
||||
return lhs.to_number() % rhs.to_number();
|
||||
|
||||
|
||||
case '+':
|
||||
return lhs.to_number() + rhs.to_number();
|
||||
case '-':
|
||||
return lhs.to_number() - rhs.to_number();
|
||||
case '>':
|
||||
return lhs.to_number() > rhs.to_number();
|
||||
case '<':
|
||||
return lhs.to_number() < rhs.to_number();
|
||||
|
||||
case '<=':
|
||||
return lhs.to_number() <= rhs.to_number();
|
||||
|
||||
case '>=':
|
||||
return lhs.to_number() >= rhs.to_number();
|
||||
|
||||
case '>>':
|
||||
return lhs.to_number() >> rhs.to_number();
|
||||
|
||||
case '<<':
|
||||
return lhs.to_number() >> rhs.to_number();
|
||||
|
||||
// logical || . NaN ok
|
||||
case '||':
|
||||
return lhs.to_number(1) || rhs.to_number(1);
|
||||
|
||||
// logical && . NaN ok
|
||||
case '&&':
|
||||
return lhs.to_number(1) && rhs.to_number(1);
|
||||
|
||||
case '|':
|
||||
return lhs.to_number() | rhs.to_number();
|
||||
|
||||
case '&':
|
||||
return lhs.to_number() & rhs.to_number();
|
||||
|
||||
case '^':
|
||||
return lhs.to_number() ^ rhs.to_number();
|
||||
|
||||
case '==':
|
||||
// string ==. 0x00==0 -> 0
|
||||
// as a special case, 0=="". go figure.
|
||||
if (lhs.string == "" && rhs.string == "0") return 1;
|
||||
if (lhs.string == "0" && rhs.string == "") return 1;
|
||||
return lhs.string == rhs.string;
|
||||
|
||||
case '!=':
|
||||
if (lhs.string == "" && rhs.string == "0") return 0;
|
||||
if (lhs.string == "0" && rhs.string == "") return 0;
|
||||
return lhs.string != rhs.string;
|
||||
|
||||
|
||||
}
|
||||
// todo...
|
||||
throw std::runtime_error("unimplemented op");
|
||||
}
|
||||
|
||||
value expression_parser::unary() {
|
||||
|
||||
int type = peek_type();
|
||||
|
||||
switch (type) {
|
||||
case '-':
|
||||
case '+':
|
||||
case '!':
|
||||
case '~':
|
||||
next();
|
||||
value v = unary();
|
||||
// + is a nop.. doesn't even check if it's a number.
|
||||
if (type == '-') v = -v.to_number();
|
||||
if (type == '~') v = ~v.to_number();
|
||||
if (type == '!') v = !v.to_number(1); // logical !, NaN ok.
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
return terminal();
|
||||
}
|
||||
|
||||
value expression_parser::terminal() {
|
||||
|
||||
int type = peek_type();
|
||||
|
||||
if (type == token::text) {
|
||||
token t = next();
|
||||
return value(std::move(t.string));
|
||||
}
|
||||
|
||||
if (type == '(') {
|
||||
next();
|
||||
value v = binary();
|
||||
type = peek_type();
|
||||
if (type != ')') {
|
||||
end_of_expression();
|
||||
}
|
||||
next();
|
||||
return v;
|
||||
}
|
||||
// insert a fake token.
|
||||
return value();
|
||||
}
|
||||
|
||||
int32_t expression_parser::evaluate() {
|
||||
if (tokens.empty()) return 0;
|
||||
|
||||
value v = binary();
|
||||
if (!tokens.empty()) {
|
||||
if (tokens.back().type == ')')
|
||||
throw std::runtime_error("### MPW Shell - Extra ) command.");
|
||||
throw std::runtime_error("evaluation stack error."); // ?? should be caught above.
|
||||
}
|
||||
return v.to_number(1);
|
||||
}
|
||||
|
||||
int32_t evaluate_expression(const std::string &name, std::vector<token> &&tokens) {
|
||||
|
||||
expression_parser p(name, std::move(tokens));
|
||||
return p.evaluate();
|
||||
}
|
75
mpw-shell-quote.rl
Normal file
75
mpw-shell-quote.rl
Normal file
@ -0,0 +1,75 @@
|
||||
#include <string>
|
||||
|
||||
bool must_quote(const std::string &s){
|
||||
%%{
|
||||
|
||||
machine must_quote;
|
||||
alphtype unsigned char;
|
||||
|
||||
quotable = (
|
||||
[ \t\r\n]
|
||||
|
|
||||
0x00
|
||||
|
|
||||
[0x80-0xff]
|
||||
|
|
||||
[+#;&|()'"/\\{}`?*<>]
|
||||
|
|
||||
'-'
|
||||
|
|
||||
'['
|
||||
|
|
||||
']'
|
||||
);
|
||||
|
||||
#simpler just to say what's ok.
|
||||
normal = [A-Za-z0-9_.:];
|
||||
|
||||
main :=
|
||||
(
|
||||
normal
|
||||
|
|
||||
(any-normal) ${return true;}
|
||||
)*
|
||||
;
|
||||
}%%
|
||||
|
||||
%%write data;
|
||||
|
||||
int cs;
|
||||
const unsigned char *p = (const unsigned char *)s.data();
|
||||
const unsigned char *pe = (const unsigned char *)s.data() + s.size();
|
||||
const unsigned char *eof = nullptr;
|
||||
|
||||
%%write init;
|
||||
%%write exec;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
std::string quote(const std::string &s) {
|
||||
std::string tmp(s);
|
||||
return quote(std::move(tmp));
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string quote(const std::string &s) {
|
||||
const char q = '\'';
|
||||
const char *escape_q = "'\xd8''";
|
||||
|
||||
if (!must_quote(s)) return s;
|
||||
|
||||
std::string out;
|
||||
out.reserve(s.length() + (s.length() >> 1));
|
||||
out.push_back(q);
|
||||
|
||||
for (char c : s) {
|
||||
if (c == q) {
|
||||
out.append(escape_q);
|
||||
} else
|
||||
out.push_back(c);
|
||||
}
|
||||
|
||||
out.push_back(q);
|
||||
return out;
|
||||
}
|
374
mpw-shell-read.rl
Normal file
374
mpw-shell-read.rl
Normal file
@ -0,0 +1,374 @@
|
||||
#include "mpw-shell.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
|
||||
%%{
|
||||
machine classify;
|
||||
alphtype unsigned char;
|
||||
|
||||
ws = [ \t];
|
||||
|
||||
IF = /if/i;
|
||||
ELSE = /else/i;
|
||||
END = /end/i;
|
||||
BEGIN = /begin/i;
|
||||
EVALUATE = /evaluate/i;
|
||||
|
||||
|
||||
main := |*
|
||||
IF %eof{ return command_if; };
|
||||
IF ws => {return command_if; };
|
||||
|
||||
ELSE %eof{ return command_else;};
|
||||
ELSE ws => { return command_else; };
|
||||
|
||||
ELSE ws+ IF %eof{ return command_else_if; };
|
||||
ELSE ws+ IF ws => {return command_else_if; };
|
||||
|
||||
END %eof{ return command_end; };
|
||||
END ws => {return command_end; };
|
||||
|
||||
EVALUATE %eof{ return command_evaluate; };
|
||||
EVALUATE ws => {return command_evaluate; };
|
||||
|
||||
|
||||
*|;
|
||||
|
||||
}%%
|
||||
|
||||
|
||||
static int classify(const std::string &line) {
|
||||
|
||||
%%machine classify;
|
||||
%% write data;
|
||||
|
||||
int cs;
|
||||
int act;
|
||||
|
||||
const unsigned char *p = (const unsigned char *)line.data();
|
||||
const unsigned char *pe = (const unsigned char *)line.data() + line.size();
|
||||
const unsigned char *eof = pe;
|
||||
const unsigned char *te, *ts;
|
||||
|
||||
%%write init;
|
||||
|
||||
%%write exec;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* this state machine splits input into lines.
|
||||
* only new-line escapes are removed.
|
||||
* "", '', and {} are also matched.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* from experimentation, mpw splits on ; after variable expansion;
|
||||
* this splits before. something stupid like:
|
||||
* set q '"'; echo {q} ; "
|
||||
* will not be handled correctly. oh well.
|
||||
* (should probably just drop that and we can then combine tokenizing w/
|
||||
* variable expansion)
|
||||
*/
|
||||
%%{
|
||||
machine line_parser;
|
||||
alphtype unsigned char;
|
||||
|
||||
|
||||
escape = 0xb6;
|
||||
ws = [ \t];
|
||||
nl = ('\n' | '\r');
|
||||
|
||||
action add_line {
|
||||
/* strip trailing ws */
|
||||
while (!scratch.empty() && isspace(scratch.back())) scratch.pop_back();
|
||||
if (!scratch.empty()) {
|
||||
command_ptr cmd = std::make_shared<command>(std::move(scratch));
|
||||
cmd->line = start_line;
|
||||
start_line = line;
|
||||
program.emplace_back(std::move(cmd));
|
||||
}
|
||||
scratch.clear();
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action push_back {
|
||||
scratch.push_back(fc);
|
||||
}
|
||||
|
||||
action push_back_escape {
|
||||
scratch.push_back(escape);
|
||||
scratch.push_back(fc);
|
||||
}
|
||||
|
||||
comment = '#' (any-nl)*;
|
||||
|
||||
escape_seq =
|
||||
escape
|
||||
(
|
||||
nl ${ /* esc newline */ line++; }
|
||||
|
|
||||
(any-nl) $push_back_escape
|
||||
)
|
||||
;
|
||||
|
||||
|
||||
# single-quoted string. only escape \n is special.
|
||||
# handling is so stupid I'm not going to support it.
|
||||
|
||||
sstring =
|
||||
['] $push_back
|
||||
( (any-nl-[']) $push_back )*
|
||||
['] $push_back
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - 's must occur in pairs.");
|
||||
}
|
||||
;
|
||||
|
||||
# same quoting logic as ' string
|
||||
vstring =
|
||||
'{' $push_back
|
||||
( (any-nl-'}') $push_back )*
|
||||
'}' $push_back
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - {s must occur in pairs.");
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
# double-quoted string.
|
||||
# escape \n is ignored. others do nothing.
|
||||
dstring =
|
||||
["] $push_back
|
||||
(
|
||||
escape_seq
|
||||
|
|
||||
vstring
|
||||
|
|
||||
(any-escape-nl-["{]) $push_back
|
||||
)* ["] $push_back
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - \"s must occur in pairs.");
|
||||
}
|
||||
;
|
||||
|
||||
# this is a mess ...
|
||||
coalesce_ws =
|
||||
ws
|
||||
(
|
||||
ws
|
||||
|
|
||||
escape nl ${ line++; }
|
||||
)*
|
||||
<:
|
||||
any ${ scratch.push_back(' '); fhold; }
|
||||
;
|
||||
|
||||
line :=
|
||||
(
|
||||
sstring
|
||||
|
|
||||
dstring
|
||||
|
|
||||
vstring
|
||||
|
|
||||
[;] $add_line
|
||||
|
|
||||
escape_seq
|
||||
|
|
||||
coalesce_ws
|
||||
|
|
||||
(any-escape-nl-ws-[;#'"{]) $push_back
|
||||
)*
|
||||
comment?
|
||||
nl ${ line++; } $add_line
|
||||
;
|
||||
|
||||
main :=
|
||||
# strip leading whitespace.
|
||||
ws*
|
||||
<: # left guard -- higher priority to ws.
|
||||
any ${ fhold; fgoto line; }
|
||||
;
|
||||
|
||||
}%%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class line_parser {
|
||||
|
||||
public:
|
||||
|
||||
void process(const void *data, size_t size) {
|
||||
process((const unsigned char *)data, size, false);
|
||||
}
|
||||
|
||||
command_ptr finish() {
|
||||
process((const unsigned char *)"\n\n", 2, true);
|
||||
return build_program();
|
||||
}
|
||||
|
||||
line_parser();
|
||||
|
||||
private:
|
||||
|
||||
%% machine line_parser;
|
||||
%% write data;
|
||||
|
||||
|
||||
std::vector<command_ptr> program;
|
||||
std::string scratch;
|
||||
int line = 1;
|
||||
int cs;
|
||||
|
||||
command_ptr build_program();
|
||||
void process(const unsigned char *data, size_t size, bool final);
|
||||
};
|
||||
|
||||
line_parser::line_parser() {
|
||||
%% machine line_parser;
|
||||
%% write init;
|
||||
}
|
||||
|
||||
void line_parser::process(const unsigned char *data, size_t size, bool final) {
|
||||
|
||||
int start_line;
|
||||
|
||||
const unsigned char *p = data;
|
||||
const unsigned char *pe = data + size;
|
||||
const unsigned char *eof = nullptr;
|
||||
|
||||
if (final)
|
||||
eof = pe;
|
||||
|
||||
start_line = line;
|
||||
%% machine line_parser;
|
||||
%% write exec;
|
||||
|
||||
if (cs == line_parser_error) {
|
||||
throw std::runtime_error("MPW Shell - Lexer error.");
|
||||
|
||||
}
|
||||
|
||||
if (cs != line_parser_start && final) {
|
||||
// will this happen?
|
||||
throw std::runtime_error("MPW Shell - Lexer error.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generates a linked-list of commands. Why? Because it also checks
|
||||
* for shell-special syntax (currently if / else /end only) and
|
||||
* adds pointers to make executing them easier.
|
||||
*
|
||||
*/
|
||||
|
||||
// todo -- use recursive descent parser, support begin/end, (), ||, &&, etc.
|
||||
command_ptr line_parser::build_program() {
|
||||
|
||||
|
||||
std::vector<command_ptr> if_stack;
|
||||
|
||||
command_ptr head;
|
||||
command_ptr ptr;
|
||||
|
||||
if (program.empty()) return head;
|
||||
|
||||
std::reverse(program.begin(), program.end());
|
||||
|
||||
head = program.back();
|
||||
|
||||
while (!program.empty()) {
|
||||
|
||||
if (ptr) ptr->next = program.back();
|
||||
|
||||
ptr = std::move(program.back());
|
||||
program.pop_back();
|
||||
|
||||
int type = ptr->type = classify(ptr->string);
|
||||
|
||||
ptr->level = if_stack.size();
|
||||
|
||||
// if stack...
|
||||
switch (type) {
|
||||
default:
|
||||
break;
|
||||
|
||||
case command_if:
|
||||
if_stack.push_back(ptr);
|
||||
break;
|
||||
|
||||
case command_else:
|
||||
case command_else_if:
|
||||
|
||||
if (if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Else must be within if ... end.");
|
||||
}
|
||||
|
||||
ptr->level--;
|
||||
if_stack.back()->alternate = ptr;
|
||||
if_stack.back() = ptr;
|
||||
break;
|
||||
|
||||
case command_end:
|
||||
if (if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Extra end command.");
|
||||
}
|
||||
|
||||
ptr->level--;
|
||||
if_stack.back()->alternate = ptr;
|
||||
if_stack.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Unterminated if command.");
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
|
||||
command_ptr read_fd(int fd) {
|
||||
unsigned char buffer[1024];
|
||||
|
||||
line_parser p;
|
||||
|
||||
for(;;) {
|
||||
ssize_t s = read(fd, buffer, sizeof(buffer));
|
||||
if (s < 0) {
|
||||
throw std::runtime_error("MPW Shell - Read error.");
|
||||
}
|
||||
p.process(buffer, s);
|
||||
}
|
||||
return p.finish();
|
||||
}
|
||||
|
||||
command_ptr read_file(const std::string &name) {
|
||||
int fd;
|
||||
fd = open(name.c_str(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
throw std::runtime_error("MPW Shell - Unable to open file " + name + ".");
|
||||
}
|
||||
|
||||
auto tmp = read_fd(fd);
|
||||
close(fd);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
command_ptr read_string(const std::string &s) {
|
||||
line_parser p;
|
||||
|
||||
p.process(s.data(), s.size());
|
||||
return p.finish();
|
||||
}
|
258
mpw-shell-token.rl
Normal file
258
mpw-shell-token.rl
Normal file
@ -0,0 +1,258 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mpw-shell.h"
|
||||
|
||||
%%{
|
||||
machine tokenizer;
|
||||
alphtype unsigned char;
|
||||
|
||||
|
||||
escape = 0xb6;
|
||||
ws = [ \t];
|
||||
nl = '\n' | '\r';
|
||||
|
||||
action push_token {
|
||||
if (!scratch.empty()) {
|
||||
tokens.emplace_back(std::move(scratch));
|
||||
scratch.clear();
|
||||
}
|
||||
}
|
||||
|
||||
action push_back {
|
||||
scratch.push_back(fc);
|
||||
}
|
||||
|
||||
# vstring_quoted =
|
||||
# [{]
|
||||
# ( (any-nl-[}]) ${ var.push_back(fc); } )*
|
||||
# [}]
|
||||
# %{
|
||||
# auto iter = Environment.find(var);
|
||||
# if (iter != Environment.end() {
|
||||
# scratch.append(iter->second);
|
||||
# })
|
||||
# var.clear();
|
||||
# }
|
||||
# $err{
|
||||
# throw std::runtime_error("### MPW Shell - '{ must occur in pairs.");
|
||||
# }
|
||||
# ;
|
||||
|
||||
# vstring_unqoted =
|
||||
# [{]
|
||||
# ( (any-nl-[}]) ${ var.push_back(fc); } )*
|
||||
# [}]
|
||||
# %{
|
||||
# auto iter = Environment.find(var);
|
||||
# if (iter != Environment.end() {
|
||||
# // re-parse. ", ', { are not
|
||||
# // special. all others are treated normally.
|
||||
# })
|
||||
# var.clear();
|
||||
# }
|
||||
# $err{
|
||||
# throw std::runtime_error("### MPW Shell - '{ must occur in pairs.");
|
||||
# }
|
||||
# ;
|
||||
|
||||
sstring =
|
||||
[']
|
||||
( (any-nl-[']) $push_back )*
|
||||
[']
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - 's must occur in pairs.");
|
||||
}
|
||||
;
|
||||
|
||||
escape_seq =
|
||||
escape
|
||||
(
|
||||
'f' ${scratch.push_back('\f'); }
|
||||
|
|
||||
'n' ${scratch.push_back('\n'); /* \r ? */ }
|
||||
|
|
||||
't' ${scratch.push_back('\t'); }
|
||||
|
|
||||
any-[fnt] $push_back
|
||||
)
|
||||
;
|
||||
|
||||
# double-quoted string.
|
||||
dstring =
|
||||
["]
|
||||
(
|
||||
escape_seq
|
||||
|
|
||||
(any-escape-["]) $push_back
|
||||
)*
|
||||
["]
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - \"s must occur in pairs.");
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
action eval { eval }
|
||||
|
||||
# > == start state (single char tokens or common prefix)
|
||||
# % == final state (multi char tokens w/ unique prefix)
|
||||
# $ == all states
|
||||
|
||||
main := |*
|
||||
ws+ >push_token;
|
||||
'>>' %push_token => { tokens.emplace_back(">>", '>>'); };
|
||||
'>' %push_token => { tokens.emplace_back(">", '>'); };
|
||||
|
||||
'<' %push_token => { tokens.emplace_back("<", '<'); };
|
||||
|
||||
'||' %push_token => { tokens.emplace_back("||", '||'); };
|
||||
'|' %push_token => { tokens.emplace_back("|", '|'); };
|
||||
|
||||
'&&'
|
||||
%push_token => { tokens.emplace_back("&&", '&&'); };
|
||||
|
||||
# eval-only.
|
||||
|
||||
'(' when eval
|
||||
%push_token => { tokens.emplace_back("(", '('); };
|
||||
|
||||
')' when eval
|
||||
%push_token => { tokens.emplace_back(")", ')'); };
|
||||
|
||||
|
||||
'<<' when eval
|
||||
%push_token => { tokens.emplace_back("<<", '<<'); };
|
||||
|
||||
'<=' when eval
|
||||
%push_token => { tokens.emplace_back("<=", '<='); };
|
||||
|
||||
'>=' when eval
|
||||
%push_token => { tokens.emplace_back(">=", '>='); };
|
||||
|
||||
'==' when eval
|
||||
%push_token => { tokens.emplace_back("==", '=='); };
|
||||
|
||||
'!=' when eval
|
||||
%push_token => { tokens.emplace_back("!=", '!='); };
|
||||
|
||||
'&' when eval
|
||||
%push_token => { tokens.emplace_back("&", '&'); };
|
||||
|
||||
'+' when eval
|
||||
>push_token => { tokens.emplace_back("+", '+'); };
|
||||
|
||||
'*' when eval
|
||||
%push_token => { tokens.emplace_back("*", '*'); };
|
||||
|
||||
'%' when eval
|
||||
%push_token => { tokens.emplace_back("%", '%'); };
|
||||
|
||||
|
||||
'-' when eval
|
||||
%push_token => { tokens.emplace_back("+", '-'); };
|
||||
|
||||
'!' when eval
|
||||
%push_token => { tokens.emplace_back("!", '!'); };
|
||||
|
||||
'^' when eval
|
||||
%push_token => { tokens.emplace_back("^", '^'); };
|
||||
|
||||
'~' when eval
|
||||
%push_token => { tokens.emplace_back("~", '~'); };
|
||||
|
||||
|
||||
'=' when eval
|
||||
%push_token => { tokens.emplace_back("=", '='); };
|
||||
|
||||
'+=' when eval
|
||||
%push_token => { tokens.emplace_back("+=", '+='); };
|
||||
|
||||
'-=' when eval
|
||||
%push_token => { tokens.emplace_back("-=", '-='); };
|
||||
|
||||
|
||||
sstring ;
|
||||
dstring ;
|
||||
escape_seq;
|
||||
|
||||
(any-escape-['"]) => push_back; # { scratch.append(ts, te); };
|
||||
#(any-escape-ws-[>'"])+ => { scratch.append(ts, te); };
|
||||
*|
|
||||
;
|
||||
}%%
|
||||
|
||||
|
||||
|
||||
inline void replace_eval_token(token &t) {
|
||||
|
||||
%%{
|
||||
|
||||
machine eval_keywords;
|
||||
|
||||
main :=
|
||||
/and/i %{ t.type = '&&'; }
|
||||
|
|
||||
/or/i %{ t.type = '||'; }
|
||||
|
|
||||
/not/i %{ t.type = '!'; }
|
||||
|
|
||||
/div/i %{ t.type = '/'; }
|
||||
|
|
||||
/mod/i %{ t.type = '%'; }
|
||||
;
|
||||
}%%
|
||||
|
||||
|
||||
%%machine eval_keywords;
|
||||
%%write data;
|
||||
|
||||
|
||||
const char *p = t.string.data();
|
||||
const char *pe = t.string.data() + t.string.size();
|
||||
const char *eof = pe;
|
||||
int cs;
|
||||
%%write init;
|
||||
|
||||
%%write exec;
|
||||
}
|
||||
std::vector<token> tokenize(const std::string &s, bool eval)
|
||||
{
|
||||
std::vector<token> tokens;
|
||||
std::string scratch;
|
||||
|
||||
%%machine tokenizer;
|
||||
%% write data;
|
||||
|
||||
int cs, act;
|
||||
unsigned const char *p = (const unsigned char *)s.data();
|
||||
unsigned const char *pe = (const unsigned char *)s.data() + s.size();
|
||||
unsigned const char *eof = pe;
|
||||
|
||||
unsigned const char *ts, *te;
|
||||
|
||||
%%write init;
|
||||
|
||||
%%write exec;
|
||||
|
||||
if (cs == tokenizer_error) {
|
||||
throw std::runtime_error("MPW Shell - Lexer error.");
|
||||
}
|
||||
|
||||
if (!scratch.empty()) {
|
||||
tokens.emplace_back(std::move(scratch));
|
||||
scratch.clear();
|
||||
}
|
||||
|
||||
// alternate operator tokens for eval
|
||||
if (eval) {
|
||||
|
||||
for (token & t : tokens) {
|
||||
if (t.type == token::text) replace_eval_token(t);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
50
mpw-shell.cpp
Normal file
50
mpw-shell.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "mpw-shell.h"
|
||||
|
||||
|
||||
|
||||
|
||||
std::unordered_map<std::string, EnvironmentEntry> 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.
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
init();
|
||||
|
||||
command_ptr head;
|
||||
|
||||
|
||||
try {
|
||||
head = read_fd(0);
|
||||
} catch (std::exception &ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
int status = execute(head);
|
||||
exit(status);
|
||||
} catch(std::exception &ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
}
|
159
mpw-shell.h
Normal file
159
mpw-shell.h
Normal file
@ -0,0 +1,159 @@
|
||||
#ifndef __mpw_shell_h__
|
||||
#define __mpw_shell_h__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
class command;
|
||||
typedef std::shared_ptr<command> command_ptr;
|
||||
typedef std::weak_ptr<command> weak_command_ptr;
|
||||
|
||||
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<std::string, EnvironmentEntry> Environment;
|
||||
|
||||
enum {
|
||||
command_if = 1,
|
||||
command_else,
|
||||
command_else_if,
|
||||
command_end,
|
||||
command_begin,
|
||||
command_evaluate,
|
||||
};
|
||||
|
||||
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))
|
||||
{}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
class token {
|
||||
public:
|
||||
enum {
|
||||
text = 0,
|
||||
eof,
|
||||
equivalent,
|
||||
not_equivalent,
|
||||
// remainder are characters.
|
||||
|
||||
};
|
||||
unsigned type = text;
|
||||
std::string string;
|
||||
|
||||
token() = default;
|
||||
token(token &&) = default;
|
||||
token(const token&) = default;
|
||||
|
||||
token &operator=(token &&) = default;
|
||||
token &operator=(const token &) = default;
|
||||
|
||||
token(const std::string &s, unsigned t = text) :
|
||||
type(t), string(s)
|
||||
{}
|
||||
|
||||
token(std::string &&s, unsigned t = text) :
|
||||
type(t), string(std::move(s))
|
||||
{}
|
||||
|
||||
operator std::string() const {
|
||||
return string;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
command_ptr read_fd(int fd);
|
||||
command_ptr read_file(const std::string &);
|
||||
command_ptr read_string(const std::string &);
|
||||
|
||||
std::vector<token> tokenize(const std::string &s, bool eval = false);
|
||||
std::string expand_vars(const std::string &s, const std::unordered_map<std::string, EnvironmentEntry> &env);
|
||||
|
||||
//std::string quote(std::string &&s);
|
||||
std::string quote(const std::string &s);
|
||||
|
||||
|
||||
struct process;
|
||||
struct value;
|
||||
class fdmask;
|
||||
|
||||
void parse_tokens(std::vector<token> &&tokens, process &p);
|
||||
|
||||
|
||||
int execute(command_ptr cmd);
|
||||
|
||||
int32_t evaluate_expression(const std::string &name, std::vector<token> &&tokens);
|
||||
|
||||
int builtin_directory(const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_echo(const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_parameters(const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_quote(const std::vector<std::string> &tokens, const fdmask &);
|
||||
int builtin_set(const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_unset(const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_export(const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_unexport(const std::vector<std::string> &, const fdmask &);
|
||||
|
||||
int builtin_evaluate(std::vector<token> &&, const fdmask &);
|
||||
|
||||
|
||||
#endif
|
21
mpw-shell.text
Normal file
21
mpw-shell.text
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
parser differences.
|
||||
|
||||
I've tried to follow mpw's command line parsing algorithm but there are some differences.
|
||||
|
||||
Mostly this is because
|
||||
|
||||
set q '"' ; echo {q} really "
|
||||
|
||||
is equivalent to
|
||||
|
||||
echo " really "
|
||||
|
||||
mpw removes # comments before shell expansion so this _is_ an error:
|
||||
|
||||
echo {q} # "
|
||||
|
||||
- mpw doesn't split on ; until after variables are expanded. I split before variable expansion.
|
||||
- escape new-line is not allowed in a '' or {} string.
|
||||
- quote matching happens when the line is read.
|
73
value.h
Normal file
73
value.h
Normal file
@ -0,0 +1,73 @@
|
||||
|
||||
#ifndef __value_h__
|
||||
#define __value_h__
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
// hold a string and number value.
|
||||
|
||||
struct value {
|
||||
|
||||
public:
|
||||
|
||||
std::string string;
|
||||
int32_t number = 0;
|
||||
|
||||
// empty token treated as 0.
|
||||
value() : status(valid)
|
||||
{}
|
||||
|
||||
value(const value &) = default;
|
||||
value(value &&) = default;
|
||||
|
||||
value(int32_t n) :
|
||||
string(std::to_string(n)),
|
||||
number(n),
|
||||
status(valid)
|
||||
{}
|
||||
|
||||
value(const std::string &s) : string(s)
|
||||
{}
|
||||
|
||||
value(std::string &&s) : string(std::move(s))
|
||||
{}
|
||||
|
||||
value &operator=(const value&) = default;
|
||||
value &operator=(value &&) = default;
|
||||
|
||||
|
||||
int32_t to_number() {
|
||||
if (status == unknown)
|
||||
scan_number();
|
||||
if (status == valid) return number;
|
||||
expect_number();
|
||||
}
|
||||
|
||||
int32_t to_number(int default_value) noexcept {
|
||||
if (status == unknown)
|
||||
scan_number();
|
||||
if (status == valid) return number;
|
||||
return default_value;
|
||||
}
|
||||
|
||||
bool is_number() noexcept {
|
||||
if (status == unknown)
|
||||
scan_number();
|
||||
return status == valid;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
[[noreturn]] void expect_number() const;
|
||||
void scan_number() noexcept;
|
||||
|
||||
mutable enum {
|
||||
unknown,
|
||||
valid,
|
||||
invalid
|
||||
} status = unknown;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
76
value.rl
Normal file
76
value.rl
Normal file
@ -0,0 +1,76 @@
|
||||
|
||||
#include "value.h"
|
||||
#include <stdexcept>
|
||||
|
||||
void value::expect_number() const {
|
||||
|
||||
std::string error;
|
||||
|
||||
error = "Expected a number when \"";
|
||||
error += string;
|
||||
error += "\" was encountered";
|
||||
|
||||
throw std::domain_error(error);
|
||||
}
|
||||
|
||||
void value::scan_number(void) noexcept {
|
||||
|
||||
%%{
|
||||
machine scanner;
|
||||
hexnumber =
|
||||
('$' | '0x' | '0X')
|
||||
(
|
||||
[0-9] ${ value = (value << 4) + fc - '0'; }
|
||||
|
|
||||
[A-Fa-f] ${value = (value << 4) + (fc | 0x20) - 'a' + 10; }
|
||||
)+
|
||||
;
|
||||
|
||||
binnumber =
|
||||
('0b' | '0B')
|
||||
[01]+ ${ value = (value << 1) + fc - '0'; }
|
||||
;
|
||||
|
||||
octalnumber =
|
||||
'0'
|
||||
[0-7]+ ${ value = (value << 3) + fc - '0'; }
|
||||
;
|
||||
|
||||
# a leading 0 is ambiguous since it could also
|
||||
# be part of the binary or hex prefix.
|
||||
# however, setting it to 0 is safe.
|
||||
decnumber =
|
||||
'0'
|
||||
|
|
||||
([1-9] [0-9]*) ${ value = value * 10 + fc - '0'; }
|
||||
;
|
||||
|
||||
|
||||
main :=
|
||||
( hexnumber | decnumber |binnumber)
|
||||
%{
|
||||
status = valid;
|
||||
number = value;
|
||||
return;
|
||||
}
|
||||
;
|
||||
}%%
|
||||
|
||||
if (string.empty()) {
|
||||
// special case.
|
||||
status = valid;
|
||||
number = 0;
|
||||
return;
|
||||
}
|
||||
const char *p = string.data();
|
||||
const char *pe = string.data() + string.size();
|
||||
const char *eof = pe;
|
||||
int cs;
|
||||
int32_t value = 0;
|
||||
|
||||
%%write data;
|
||||
%%write init;
|
||||
%%write exec;
|
||||
|
||||
status = invalid;
|
||||
}
|
Loading…
Reference in New Issue
Block a user