change option syntax; less exceptions; better autocompletion

* Change option syntax to <name>=<value>, with mandatory "=". This is
  still not great, but manageable.
* Remove the Session::require_vspace() function, whose named overload
  was misused (to check for the existence of a named space), and the
  associated exceptions. Explicit error flow is better error flow.
* Change the autocompletion system to generate all options in a vector
  of strings and then iterate, rather than using generators.
* Add completion for symbols.
This commit is contained in:
Lephenixnoir 2022-03-04 22:54:45 +00:00
parent 3b684389e9
commit bd5e1f918c
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
15 changed files with 142 additions and 157 deletions

View File

@ -46,10 +46,10 @@ static _afh_args parse_afh(Session &session, Parser &parser)
args.distance = -1;
args.align = -1;
parser.option('a', [&args](std::string const &value){
parser.option("align", [&args](std::string const &value){
args.align = atoi(value.c_str());
});
parser.option('d', [&args](std::string const &value){
parser.option("distance", [&args](std::string const &value){
args.distance = atoi(value.c_str());
});
@ -106,7 +106,8 @@ static _afh_args parse_afh(Session &session, Parser &parser)
void _afh(Session &session, char const *reference, char const *pattern,
size_t size, int align, int distance, std::vector<MemoryRegion> &regions)
{
session.require_vspace();
if(!session.current_space)
return;
/* Default values */
if(distance < 0) distance = 32;

View File

@ -74,7 +74,8 @@ static uint32_t parse_d(Session &session, Parser &parser)
void _d(Session &session, uint32_t address)
{
session.require_vspace();
if(!session.current_space)
return;
FxOS::Disassembly disasm(*session.current_space);
if(address & 1) {
@ -100,11 +101,10 @@ static Range parse_dr(Session &session, Parser &parser)
void _dr(Session &session, Range range)
{
session.require_vspace();
if(!session.current_space)
return;
FxOS::Disassembly disasm(*session.current_space);
if(range.start == range.end) return;
if(range.start & 1) {
fmt::print("address 0x{:08x} is odd, starting at 0x{:08x}\n",
range.start, range.start+1);
@ -116,6 +116,8 @@ void _dr(Session &session, Range range)
range.end--;
}
if(range.start >= range.end) return;
/* Load the block into memory */
for(uint32_t pc = range.start; pc < range.end; pc += 2)
disasm.readins(pc);

View File

@ -68,14 +68,15 @@ struct _ev_args {
static _ev_args parse_ev(Session &session, Parser &parser)
{
_ev_args args {};
args.space_name = parser.symbol("space_name");
session.require_vspace(args.space_name);
args.space_name = parser.symbol("vspace_name");
VirtualSpace *space = &session.spaces.at(args.space_name);
while(!parser.at_end()) {
args.values.push_back(parser.expr(space));
VirtualSpace *space = session.get_space(args.space_name);
if(space) {
while(!parser.at_end()) {
args.values.push_back(parser.expr(space));
}
}
// TODO: Error message when session specified in _ev does not exist
parser.end();
return args;

View File

@ -29,14 +29,4 @@ private:
std::string m_what;
};
/* Command error when no virtual space is selected */
class NoVirtualSpaceError: public CommandError
{
public:
NoVirtualSpaceError():
CommandError("no virtual space") {}
NoVirtualSpaceError(std::string name):
CommandError("no virtual space '{}'", name) {}
};
#endif /* FXOS_ERRORS_H */

View File

@ -17,7 +17,8 @@ static long parse_g(Session &session, Parser &parser)
void _g(Session &session, long value)
{
session.require_vspace();
if(!session.current_space)
return;
session.current_space->cursor = (value & 0xffffffff);
}

View File

@ -17,7 +17,8 @@ static bool is_selected(uint32_t address, Selections const &sel)
void _h_hexdump(Session &session, Range r, Selections sel)
{
session.require_vspace();
if(!session.current_space)
return;
VirtualSpace &v = *session.current_space;
uint32_t start = r.start & ~0xf;

View File

@ -46,13 +46,11 @@ static std::string parse_io(Session &, Parser &parser)
void _io(Session &session, std::string name)
{
VirtualSpace *space = nullptr;
session.require_vspace(name);
if(name == "")
space = session.current_space;
else
space = &session.spaces[name];
VirtualSpace *space = session.current_space;
if(name != "")
space = session.get_space(name);
if(!space)
return;
OS *os = space->os_analysis();
if(!os) throw CommandError("os analysis on '{}' failed", name);
@ -108,8 +106,8 @@ static struct _is_args parse_is(Session &, Parser &parser)
{
struct _is_args args {};
parser.option('s', [&args](std::string const &value){
args.sort = true;
parser.option("sort", [&args](std::string const &value){
args.sort = (value == "true");
});
parser.accept_options();
@ -132,13 +130,11 @@ bool operator < (const SyscallInfo &left, const SyscallInfo &right)
void _is(Session &session, std::string vspace_name, bool sort)
{
VirtualSpace *space = nullptr;
session.require_vspace(vspace_name);
if(vspace_name == "")
space = session.current_space;
else
space = &session.spaces[vspace_name];
VirtualSpace *space = session.current_space;
if(vspace_name != "")
space = session.get_space(vspace_name);
if(!space)
return;
OS *os = space->os_analysis();
if(!os) throw CommandError("os analysis on '{}' failed", vspace_name);

View File

@ -109,8 +109,8 @@ long parse_num(char const *text)
{
/* Determine base */
int base = 10;
if(text[0] == '0' && text[1] == 'x') base = 16, text += 2;
else if(text[0] == '0' && text[1] == 'b') base = 2, text += 2;
if(text[0] == '0' && tolower(text[1]) == 'x') base = 16, text += 2;
else if(text[0] == '0' && tolower(text[1]) == 'b') base = 2, text += 2;
char *end;
long val = strtoul(text, &end, base);
@ -133,15 +133,15 @@ long parse_num(char const *text)
/* Used in error rules for word boundary violations */
letter [a-zA-Z0-9_.%]
num_hex 0x[a-zA-Z0-9]+
num_hex 0[xX][a-zA-Z0-9]+
num_dec (0d)?[0-9]+
num_bin 0b[0-1]+
num_bin 0[bB][0-1]+
num_suffix [kMG]
num ({num_hex}|{num_dec}|{num_bin}){num_suffix}?
syscall [%][a-fA-F0-9]+
option [a-zA-Z]+=[^ ]+
symbol [a-zA-Z_.][a-zA-Z0-9_.]*
option -[a-zA-Z](=[^ ]+)?
space [ \t]+
@ -183,12 +183,12 @@ space [ \t]+
<STR>\\n { if(STR_len < LEX_STR_MAX) STR_buffer[STR_len++] = '\n'; }
<STR>\\t { if(STR_len < LEX_STR_MAX) STR_buffer[STR_len++] = '\t'; }
{option} { yylval.OPTION = strdup(yytext); return T::OPTION; }
<*>{syscall} { yylval.NUM = strtoul(yytext+1, NULL, 16); return T::SYSCALL; }
<*>{num} { yylval.NUM = parse_num(yytext); return T::NUM; }
<*>{symbol} { yylval.SYMBOL = strdup(yytext); return T::SYMBOL; }
{option} { yylval.OPTION = strdup(yytext); return T::OPTION; }
/* Generic error and word boundaries violations */
<*>{syscall}{letter} { err("invalid syscall number '%s'", yytext); }
<*>{num}{letter} { err("invalid numerical value '%s'", yytext); }

View File

@ -3,6 +3,7 @@
#include <cstring>
#include <csignal>
#include <csetjmp>
#include <functional>
#include <readline/readline.h>
#include <readline/history.h>
@ -34,48 +35,48 @@ static Session global_session;
// Autocompletion
//---
char *complete_command(char const *text, int state, Session &)
std::vector<std::string> complete_command(char const *text)
{
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);
std::vector<std::string> options;
for(auto const &it: commands) {
if(!strncmp(it.first.c_str(), text, strlen(text)))
options.push_back(it.first);
}
return NULL;
return options;
}
char *complete_vspace(char const *text, int state, Session &session)
std::vector<std::string> complete_vspace(char const *text, 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);
std::vector<std::string> options;
for(auto const &it: session.spaces) {
if(!strncmp(it.first.c_str(), text, strlen(text)))
options.push_back(it.first);
}
return NULL;
return options;
}
char *complete_region(char const *text, int state, Session &)
std::vector<std::string> complete_region(char const *text)
{
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);
std::vector<std::string> options;
for(auto const &it: MemoryRegion::all()) {
if(!strncmp(it->name.c_str(), text, strlen(text)))
options.push_back(it->name);
}
return NULL;
return options;
}
std::string autocomplete_category(Session &session, char const *line_buffer,
int point)
std::vector<std::string> complete_symbol(char const *text, VirtualSpace *space)
{
std::vector<std::string> options;
for(auto const &it: space->symbols.symbols) {
if(!strncmp(it.name.c_str(), text, strlen(text)))
options.push_back(it.name);
}
return options;
}
Parser::CompletionRequest parse_until_autocomplete(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);
@ -95,41 +96,39 @@ std::string autocomplete_category(Session &session, char const *line_buffer,
} while(!lex_idle());
}
catch(Parser::CompleteCategory &e) {
// fmt::print("Completing: {}\n", e.category());
return e.category();
catch(Parser::CompletionRequest r) {
return r;
}
catch(Parser::SyntaxError &e) {
// fmt::print("Syntax error: {}\n", e.what());
}
return "";
return Parser::CompletionRequest("", "");
}
char *autocomplete(char const *text, int state)
{
static char * (*current_completer)(char const *, int, Session &) = NULL;
static std::vector<std::string> options;
static size_t i = 0;
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());
Parser::CompletionRequest const &r = parse_until_autocomplete(
global_session, rl_line_buffer, rl_point);
if(r.category == "command")
options = complete_command(text);
else if(r.category == "vspace_name")
options = complete_vspace(text, global_session);
else if(r.category == "memory_region")
options = complete_region(text);
else if(r.category == "symbol" && r.space != nullptr)
options = complete_symbol(text, r.space);
else
options.clear();
i = 0;
}
if(current_completer == NULL)
return NULL;
char *rc = current_completer(text, state, global_session);
if(rc == NULL) current_completer = NULL;
return rc;
return (i < options.size()) ? strdup(options[i++].c_str()) : NULL;
}
//---
// Shell routine
//---
@ -164,7 +163,7 @@ static std::string read_interactive(Session const &s, bool &leave)
name = it.first;
}
prompt = fmt::format("{} $=0x{:08x}> ", name, s.current_space->cursor);
prompt = fmt::format("{} @ 0x{:08x}> ", name, s.current_space->cursor);
}
/* We need to insert RL_PROMPT_{START,END}_IGNORE into the color

View File

@ -156,7 +156,7 @@ void Parser::dump_command()
// Main parsing rules
//---
void Parser::option(char name, OptionHandler callback)
void Parser::option(std::string name, OptionHandler callback)
{
m_options.emplace(name, callback);
}
@ -197,7 +197,7 @@ std::string Parser::symbol(std::string category)
{
/* Auto-complete a symbol which has not been typed yet */
if(m_complete && m_la.type == T::END)
throw CompleteCategory(category, "");
throw CompletionRequest(category, "");
if(!m_complete)
return expect(T::SYMBOL).value.SYMBOL;
@ -210,7 +210,7 @@ std::string Parser::symbol(std::string category)
/* This will throw only if there is no token after, not even spaces */
if(m_la.type == T::END)
throw CompleteCategory(category, sym);
throw CompletionRequest(category, sym);
/* If a space is found, get rid of it */
if(m_la.type == T::SPC) feed();
@ -272,16 +272,14 @@ void Parser::accept_options()
while(m_la.type == T::OPTION) {
Token t = expect(T::OPTION);
char *text = t.value.OPTION;
char name = text[1];
std::string opt = t.value.OPTION;
std::string name = opt.substr(0, opt.find('='));
if(!m_options.count(name)) {
throw CommandError("unrecognized option -{}", name);
throw CommandError("unrecognized option {}", name);
}
std::string value = "";
if(strnlen(text, 3) >= 3) value = text + 3;
std::string value = opt.substr(opt.find('=')+1);
m_options[name](value);
}
}
@ -295,7 +293,11 @@ long Parser::atom()
Token t = expect({ '$', '(', '-', T::SYMBOL, T::NUM, T::SYSCALL });
if(t.type == T::SYMBOL) {
long val = 0; /* TODO: Query symbol and return its value */
/* TODO: Specify the space that symbols are taken from */
if(m_complete && m_la.type == T::END)
throw CompletionRequest("symbol", t.value.SYMBOL, m_expr_space);
long val = 0;
if(m_expr_space) {
auto const &opt = m_expr_space->symbols.lookup(t.value.SYMBOL);
if(opt && opt->type == FxOS::Symbol::Address) {
@ -312,8 +314,6 @@ long Parser::atom()
}
else throw CommandError("cannot query symbol '{}', no virtual space",
t.value.SYMBOL);
if(m_complete && m_la.type == T::END)
throw CompleteCategory("expression", t.value.SYMBOL);
return val;
}
else if(t.type == T::SYSCALL) {

View File

@ -124,7 +124,7 @@ public:
/* Specify an option to be accepted until the next SEPARATOR or END token
(basically for the current command). The function will be called when
the option is found during a call to accept_options(). */
void option(char name, OptionHandler callback);
void option(std::string name, OptionHandler callback);
/* Read a symbol from the specified category; the category name is used to
determine completion options on the command-line */
@ -162,21 +162,15 @@ public:
SyntaxError(char const *what): std::invalid_argument(what) {}
};
class CompleteCategory: public std::exception
/* A completion request is thrown by the parser when in completion mode to
signal the expected next element */
struct CompletionRequest
{
public:
CompleteCategory(std::string category, std::string value) {
m_category = category;
m_value = value;
}
std::string category() const {
return m_category;
}
std::string value() const {
return m_value;
}
private:
std::string m_category, m_value;
CompletionRequest(std::string const &c, std::string const &v,
VirtualSpace *s=nullptr): category(c), value(v), space(s) {}
std::string category;
std::string value;
VirtualSpace *space;
};
//---
@ -203,7 +197,7 @@ private:
VirtualSpace *m_expr_space;
/* Options (retained until next SEPARATOR or END) */
std::map<char, OptionHandler> m_options;
std::map<std::string, OptionHandler> m_options;
};
#endif /* FXOS_PARSER_H */

View File

@ -18,7 +18,8 @@ static void parse_sl(Session &, Parser &parser)
void _sl(Session &session)
{
session.require_vspace();
if(!session.current_space)
return;
for(auto const &s: session.current_space->symbols.symbols) {
if(s.type == FxOS::Symbol::Syscall && s.value < 0x1000) {
@ -41,8 +42,6 @@ void _sl(Session &session)
static FxOS::Symbol parse_sa(Session &session, Parser &parser)
{
session.require_vspace();
FxOS::Symbol s;
s.type = FxOS::Symbol::Address;
s.value = parser.expr(session.current_space);
@ -54,7 +53,8 @@ static FxOS::Symbol parse_sa(Session &session, Parser &parser)
void _sa(Session &session, Symbol s)
{
session.require_vspace();
if(!session.current_space)
return;
session.current_space->symbols.add(s);
}
@ -62,10 +62,8 @@ void _sa(Session &session, Symbol s)
// ss
//---
static FxOS::Symbol parse_ss(Session &session, Parser &parser)
static FxOS::Symbol parse_ss(Session &, Parser &parser)
{
session.require_vspace();
FxOS::Symbol s;
s.type = FxOS::Symbol::Syscall;
s.value = parser.expect({ T::SYSCALL }).value.NUM;
@ -77,7 +75,8 @@ static FxOS::Symbol parse_ss(Session &session, Parser &parser)
void _ss(Session &session, Symbol s)
{
session.require_vspace();
if(!session.current_space)
return;
session.current_space->symbols.add(s);
}

View File

@ -10,7 +10,13 @@ Session::Session():
this->pc = -1;
}
std::string Session::space_name(std::string prefix, bool force_suffix)
VirtualSpace *Session::get_space(std::string name)
{
auto const &it = this->spaces.find(name);
return it == this->spaces.end() ? nullptr : &it->second;
}
std::string Session::generate_space_name(std::string prefix, bool force_suffix)
{
if(!force_suffix && this->spaces.count(prefix) == 0)
return prefix;
@ -25,14 +31,6 @@ std::string Session::space_name(std::string prefix, bool force_suffix)
}
}
void Session::require_vspace(std::string name) const
{
if(name == "" && !this->current_space)
throw NoVirtualSpaceError();
if(name != "" && !this->spaces.count(name))
throw NoVirtualSpaceError(name);
}
fs::path Session::file(std::string name)
{
#define err(...) std::runtime_error(fmt::format(__VA_ARGS__))

View File

@ -43,11 +43,8 @@ struct Session
/* Find an unused name from this prefix. If force_suffix is set, always
adds a suffix even if the name itself is free */
std::string space_name(std::string prefix, bool force_suffix=false);
/* Ensure a virtual space is loaded and selected. If not, throws a
NoVirtualSpaceError. */
void require_vspace(std::string name="") const;
std::string generate_space_name(std::string prefix,
bool force_suffix=false);
//---
//

View File

@ -56,8 +56,11 @@ void _vl(Session &session, std::vector<std::string> const &args)
show_vspace(it.first, it.second, session);
}
else for(auto &name: args) {
session.require_vspace(name);
show_vspace(name, session.spaces[name], session);
VirtualSpace *s = session.get_space(name);
if(s != nullptr)
show_vspace(name, session.spaces[name], session);
else
fmt::print("Virtual space '{}' does not exist", name);
}
}
@ -74,7 +77,9 @@ static std::string parse_vs(Session &, Parser &parser)
void _vs(Session &session, std::string const &name)
{
session.require_vspace(name);
VirtualSpace *s = session.get_space(name);
if(!s)
return;
session.current_space = &session.spaces[name];
}
@ -92,8 +97,8 @@ static std::string parse_vc(Session &, Parser &parser)
static void _vc(Session &session, std::string name)
{
if(name == "") name = session.space_name("space", true);
else name = session.space_name(name, false);
if(name == "") name = session.generate_space_name("space", true);
else name = session.generate_space_name(name, false);
/* Create an empty space and select it */
session.spaces.emplace(name, VirtualSpace {});
@ -124,7 +129,7 @@ void _vct(Session &session, std::string filename, std::string name)
fs::path path = session.file(filename);
if(name == "")
name = session.space_name(path.filename(), false);
name = session.generate_space_name(path.filename(), false);
else if(session.spaces.count(name))
throw CommandError("virtual space '{}' already exists", name);
@ -156,7 +161,8 @@ static _vm_args parse_vm(Session &session, Parser &parser)
void _vm(Session &session, std::string file, std::vector<MemoryRegion> regions)
{
session.require_vspace();
if(!session.current_space)
return;
std::string path = session.file(file);
Buffer contents(path);