forked from Lephenixnoir/fxos
305 lines
7.3 KiB
C++
305 lines
7.3 KiB
C++
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <csignal>
|
|
#include <csetjmp>
|
|
|
|
#include <readline/readline.h>
|
|
#include <readline/history.h>
|
|
#include <fmt/core.h>
|
|
#include <fmt/color.h>
|
|
|
|
#include "session.h"
|
|
#include "shell.h"
|
|
#include "theme.h"
|
|
#include "parser.h"
|
|
#include "commands.h"
|
|
|
|
struct CommandSpec {
|
|
ShellFunction function;
|
|
ShellCompleter completer;
|
|
};
|
|
|
|
static std::map<std::string, CommandSpec> commands;
|
|
|
|
void shell_register_command(std::string name, ShellFunction function,
|
|
ShellCompleter completer)
|
|
{
|
|
commands[name] = (CommandSpec){ function, completer };
|
|
}
|
|
|
|
static Session global_session;
|
|
|
|
//---
|
|
// Autocompletion
|
|
//---
|
|
|
|
char *complete_command(char const *text, int state, Session &)
|
|
{
|
|
static auto it = commands.begin();
|
|
if(state == 0) it = commands.begin();
|
|
|
|
while(it != commands.end()) {
|
|
char const *current = (it++)->first.c_str();
|
|
if(!strncmp(current, text, strlen(text)))
|
|
return strdup(current);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
char *complete_vspace(char const *text, int state, Session &session)
|
|
{
|
|
static auto it = session.spaces.begin();
|
|
if(state == 0) it = session.spaces.begin();
|
|
|
|
while(it != session.spaces.end()) {
|
|
char const *current = (it++)->first.c_str();
|
|
if(!strncmp(current, text, strlen(text)))
|
|
return strdup(current);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
char *complete_region(char const *text, int state, Session &)
|
|
{
|
|
static auto const &all = MemoryRegion::all();
|
|
static auto it = all.begin();
|
|
if(state == 0) it = all.begin();
|
|
|
|
while(it != all.end()) {
|
|
char const *current = (*it++)->name.c_str();
|
|
if(!strncmp(current, text, strlen(text)))
|
|
return strdup(current);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
std::string autocomplete_category(Session &session, char const *line_buffer,
|
|
int point)
|
|
{
|
|
/* Parse partial input and try to get a category of suggestions */
|
|
std::string input(line_buffer, point);
|
|
lex_repl(input);
|
|
Parser p(true);
|
|
p.start();
|
|
|
|
/* Read commands until hitting an unfinished one */
|
|
try {
|
|
do {
|
|
/* First obtain command name */
|
|
p.skip_separators();
|
|
std::string cmd = p.symbol("command");
|
|
/* Then run parser on that command's completer */
|
|
if(commands.count(cmd) && commands[cmd].completer)
|
|
commands[cmd].completer(session, p);
|
|
|
|
} while(!lex_idle());
|
|
}
|
|
catch(Parser::CompleteCategory &e) {
|
|
// fmt::print("Completing: {}\n", e.category());
|
|
return e.category();
|
|
}
|
|
catch(Parser::SyntaxError &e) {
|
|
// fmt::print("Syntax error: {}\n", e.what());
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
char *autocomplete(char const *text, int state)
|
|
{
|
|
static char * (*current_completer)(char const *, int, Session &) = NULL;
|
|
|
|
if(state == 0) {
|
|
std::string category = autocomplete_category(global_session,
|
|
rl_line_buffer, rl_point);
|
|
if(category == "command")
|
|
current_completer = complete_command;
|
|
else if(category == "vspace_name")
|
|
current_completer = complete_vspace;
|
|
else if(category == "memory_region")
|
|
current_completer = complete_region;
|
|
else return strdup(category.c_str());
|
|
}
|
|
|
|
if(current_completer == NULL)
|
|
return NULL;
|
|
|
|
char *rc = current_completer(text, state, global_session);
|
|
if(rc == NULL) current_completer = NULL;
|
|
return rc;
|
|
}
|
|
|
|
//---
|
|
// Shell routine
|
|
//---
|
|
|
|
static std::vector<std::string> parse_dot(Session &, Parser &parser)
|
|
{
|
|
std::vector<std::string> files;
|
|
|
|
while(!parser.at_end())
|
|
files.push_back(parser.str());
|
|
|
|
return files;
|
|
}
|
|
|
|
void _dot(Session &s, std::vector<std::string> const &files, bool absolute)
|
|
{
|
|
std::vector<std::string> paths;
|
|
for(auto const &file: files) {
|
|
paths.push_back(absolute ? file : s.file(file).string());
|
|
}
|
|
lex_include(paths);
|
|
}
|
|
|
|
static std::string read_interactive(Session const &s, bool &leave)
|
|
{
|
|
std::string prompt = "(empty)> ";
|
|
if(s.current_space) {
|
|
std::string name = "(none)";
|
|
|
|
for(auto &it: s.spaces) {
|
|
if(&it.second == s.current_space)
|
|
name = it.first;
|
|
}
|
|
|
|
prompt = fmt::format("{} $=0x{:08x}> ", name, s.current_space->cursor);
|
|
}
|
|
|
|
/* We need to insert RL_PROMPT_{START,END}_IGNORE into the color
|
|
formatting, so we trick a little bit by using a space */
|
|
std::string color = fmt::format(theme(9), " ");
|
|
int space_pos = color.find(' ');
|
|
std::string SC = color.substr(0, space_pos);
|
|
std::string EC = color.substr(space_pos+1);
|
|
|
|
std::string SI(1, RL_PROMPT_START_IGNORE);
|
|
std::string EI(1, RL_PROMPT_END_IGNORE);
|
|
|
|
prompt = SI+SC+EI + prompt + SI+EC+EI;
|
|
|
|
/* Get a command to execute */
|
|
char *cmd_ptr = readline(prompt.c_str());
|
|
if(!cmd_ptr) {
|
|
leave = true;
|
|
return "";
|
|
}
|
|
|
|
std::string cmd = cmd_ptr;
|
|
add_history(cmd_ptr);
|
|
free(cmd_ptr);
|
|
return cmd;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
Session &s = global_session;
|
|
/* Register a no-op quit command for auto-completion */
|
|
shell_register_command("q", NULL, NULL);
|
|
/* Register the include command */
|
|
shell_register_command(".",
|
|
[](Session &s, Parser &p) { _dot(s, parse_dot(s, p), false); },
|
|
[](Session &s, Parser &p) { parse_dot(s, p); });
|
|
|
|
theme_builtin("tomorrow-night");
|
|
|
|
rl_completion_entry_function = autocomplete;
|
|
rl_basic_word_break_characters =
|
|
" \t\n\"\\'`@$><=;|&{(" /* Readline's default */
|
|
"+-*%)"; /* Word breaks with special characters in fxos */
|
|
|
|
/* Load path into the session */
|
|
char const *fxos_path_env = std::getenv("FXOS_PATH");
|
|
if(fxos_path_env) {
|
|
std::string fxos_path = fxos_path_env;
|
|
|
|
size_t offset=0, end;
|
|
while(true) {
|
|
offset = fxos_path.find_first_not_of(":", offset);
|
|
if(offset >= fxos_path.size()) break;
|
|
|
|
end = fxos_path.find_first_of(":", offset);
|
|
s.path.push_back(fxos_path.substr(offset, end-offset));
|
|
offset = end;
|
|
}
|
|
}
|
|
else {
|
|
fmt::print("warning: no FXOS_PATH in environment, using WD\n");
|
|
s.path.push_back(fs::current_path());
|
|
}
|
|
|
|
/* Clear readline input when receiving SIGINT */
|
|
static sigjmp_buf sigint_buf;
|
|
std::signal(SIGINT, [](int) {
|
|
rl_free_line_state();
|
|
rl_cleanup_after_signal();
|
|
rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
|
|
printf("\n");
|
|
siglongjmp(sigint_buf, 1);
|
|
});
|
|
rl_set_signals();
|
|
|
|
/* Load command history */
|
|
char histfile[128] = "";
|
|
if(std::getenv("HOME"))
|
|
snprintf(histfile, 128, "%s/.fxos_history", std::getenv("HOME"));
|
|
if(histfile[0]) read_history(histfile);
|
|
|
|
/* Load fxosrc files from all library folders */
|
|
std::vector<std::string> fxosrc_files;
|
|
for(auto const &path: s.path) {
|
|
if(fs::exists(path / "fxosrc"))
|
|
fxosrc_files.push_back(path / "fxosrc");
|
|
}
|
|
if(fxosrc_files.size() > 0)
|
|
_dot(s, fxosrc_files, true);
|
|
|
|
/* Shell main loop */
|
|
while(true) {
|
|
/* Get a command if there isn't a file being executed */
|
|
if(lex_idle()) {
|
|
while(sigsetjmp(sigint_buf, 1) != 0);
|
|
|
|
bool leave = false;
|
|
std::string cmdline = read_interactive(s, leave);
|
|
if(leave) break;
|
|
lex_repl(cmdline);
|
|
}
|
|
|
|
Parser parser(false);
|
|
|
|
try {
|
|
/* Read the next command from the lexer */
|
|
parser.start();
|
|
parser.skip_separators();
|
|
|
|
/* Read the command name */
|
|
if(parser.lookahead().type == T::END)
|
|
continue;
|
|
std::string cmd = parser.symbol("command");
|
|
if(cmd == "q") break;
|
|
|
|
if(cmd == "p") {
|
|
parser.dump_command();
|
|
}
|
|
else if(commands.count(cmd))
|
|
{
|
|
if(commands[cmd].function) commands[cmd].function(s, parser);
|
|
}
|
|
else fmt::print("\e[31;1merror:\e[0m unknown command '{}'\n", cmd);
|
|
}
|
|
catch(std::exception &e) {
|
|
fmt::print("\e[31;1merror:\e[0m {}\n", e.what());
|
|
}
|
|
|
|
/* Exhaust command input (if not all used by the command) */
|
|
parser.exhaust_until_separator();
|
|
}
|
|
|
|
/* Save command history */
|
|
if(histfile[0]) write_history(histfile);
|
|
|
|
return 0;
|
|
}
|