re-expose command and add hints/completion for now at least with linenoise

master
Daniel Kolesa 2016-09-02 17:20:53 +01:00
parent 0ff65e5ed4
commit 6aca518614
8 changed files with 253 additions and 49 deletions

View File

@ -29,8 +29,8 @@ $(LIBCS_LIB): $(LIBCS_OBJ)
repl: $(LIBCS_LIB) tools/repl.cc tools/linenoise.cc tools/linenoise.hh
$(CXX) $(CXXFLAGS) $(LIBCS_CXXFLAGS) $(LDFLAGS) \
-Itools -DCS_REPL_USE_LINENOISE tools/linenoise.cc \
tools/repl.cc -o repl $(LIBCS_LIB)
-Itools -DCS_REPL_USE_LINENOISE -DCS_REPL_HAS_COMPLETE -DCS_REPL_HAS_HINTS \
tools/linenoise.cc tools/repl.cc -o repl $(LIBCS_LIB)
clean:
rm -f $(LIBCS_LIB) $(LIBCS_OBJ)

View File

@ -517,9 +517,9 @@ lookupid:
if (prevargs >= MaxResults) {
gs.code.push(CODE_ENTER);
}
char const *fmt = static_cast<Command *>(id)->cargs;
for (; *fmt; fmt++) {
switch (*fmt) {
auto fmt = static_cast<CsCommand *>(id)->get_args();
for (char c: fmt) {
switch (c) {
case 'S':
gs.gen_str();
numargs++;
@ -1204,8 +1204,8 @@ noid:
case ID_COMMAND: {
int comtype = CODE_COM, fakeargs = 0;
bool rep = false;
char const *fmt = static_cast<Command *>(id)->cargs;
for (; *fmt; fmt++) {
auto fmt = static_cast<CsCommand *>(id)->get_args();
for (; !fmt.empty(); ++fmt) {
switch (*fmt) {
case 'S':
case 's':
@ -1223,7 +1223,7 @@ noid:
ostd::ConstCharRange(), *fmt == 's'
);
fakeargs++;
} else if (!fmt[1]) {
} else if (fmt.size() == 1) {
int numconc = 1;
while ((numargs + numconc) < MaxArguments) {
more = compilearg(

View File

@ -74,7 +74,7 @@ static inline bool cs_has_cmd_cb(CsIdent *id) {
if (!id->is_command() && !id->is_special()) {
return false;
}
Command *cb = static_cast<Command *>(id);
CsCommand *cb = static_cast<CsCommand *>(id);
return !!cb->cb_cftv;
}
@ -347,12 +347,12 @@ void CsValue::copy_arg(CsValue &r) const {
}
static inline void callcommand(
CsState &cs, Command *id, CsValue *args, CsValue &res, int numargs,
CsState &cs, CsCommand *id, CsValue *args, CsValue &res, int numargs,
bool lookup = false
) {
int i = -1, fakeargs = 0;
bool rep = false;
for (char const *fmt = id->cargs; *fmt; fmt++) {
for (auto fmt = id->get_args(); !fmt.empty(); ++fmt) {
switch (*fmt) {
case 'i':
if (++i >= numargs) {
@ -611,7 +611,7 @@ static inline int cs_get_lookupu_type(
arg.cleanup();
arg.set_null();
CsValue buf[MaxArguments];
callcommand(cs, static_cast<Command *>(id), buf, arg, 0, true);
callcommand(cs, static_cast<CsCommand *>(id), buf, arg, 0, true);
force_arg(arg, op & CODE_RET_MASK);
return -2; /* ignore */
}
@ -1381,10 +1381,12 @@ static ostd::Uint32 *runcode(CsState &cs, ostd::Uint32 *code, CsValue &result) {
case CODE_COM | RET_STR:
case CODE_COM | RET_FLOAT:
case CODE_COM | RET_INT: {
Command *id = static_cast<Command *>(cs.identmap[op >> 8]);
int offset = numargs - id->numargs;
CsCommand *id = static_cast<CsCommand *>(cs.identmap[op >> 8]);
int offset = numargs - id->get_num_args();
result.force_null();
id->cb_cftv(CsValueRange(args + offset, id->numargs), result);
id->cb_cftv(
CsValueRange(args + offset, id->get_num_args()), result
);
force_arg(result, op & CODE_RET_MASK);
free_args(args, numargs, offset);
continue;
@ -1394,7 +1396,7 @@ static ostd::Uint32 *runcode(CsState &cs, ostd::Uint32 *code, CsValue &result) {
case CODE_COMV | RET_STR:
case CODE_COMV | RET_FLOAT:
case CODE_COMV | RET_INT: {
Command *id = static_cast<Command *>(cs.identmap[op >> 13]);
CsCommand *id = static_cast<CsCommand *>(cs.identmap[op >> 13]);
int callargs = (op >> 8) & 0x1F, offset = numargs - callargs;
result.force_null();
id->cb_cftv(ostd::iter(&args[offset], callargs), result);
@ -1406,7 +1408,7 @@ static ostd::Uint32 *runcode(CsState &cs, ostd::Uint32 *code, CsValue &result) {
case CODE_COMC | RET_STR:
case CODE_COMC | RET_FLOAT:
case CODE_COMC | RET_INT: {
Command *id = static_cast<Command *>(cs.identmap[op >> 13]);
CsCommand *id = static_cast<CsCommand *>(cs.identmap[op >> 13]);
int callargs = (op >> 8) & 0x1F, offset = numargs - callargs;
result.force_null();
{
@ -1559,7 +1561,7 @@ noid:
case ID_COMMAND:
idarg.cleanup();
callcommand(
cs, static_cast<Command *>(id), &args[offset],
cs, static_cast<CsCommand *>(id), &args[offset],
result, callargs
);
force_arg(result, op & CODE_RET_MASK);
@ -1673,16 +1675,16 @@ void CsState::run_ret(CsIdent *id, CsValueRange args, CsValue &ret) {
}
/* fallthrough */
case CsIdentType::command:
if (nargs < static_cast<Command *>(id)->numargs) {
if (nargs < static_cast<CsCommand *>(id)->get_num_args()) {
CsValue buf[MaxArguments];
memcpy(buf, args.data(), args.size() * sizeof(CsValue));
callcommand(
*this, static_cast<Command *>(id), buf, ret,
*this, static_cast<CsCommand *>(id), buf, ret,
nargs, false
);
} else {
callcommand(
*this, static_cast<Command *>(id), args.data(),
*this, static_cast<CsCommand *>(id), args.data(),
ret, nargs, false
);
}

View File

@ -37,18 +37,6 @@ static inline int cs_vtype_to_int(CsValueType v) {
return cs_valtypet[int(v)];
}
struct Command: CsIdent {
char *cargs;
ostd::Uint32 argmask;
int numargs;
CmdFunc cb_cftv;
Command(
int type, ostd::ConstCharRange name, ostd::ConstCharRange args,
ostd::Uint32 argmask, int numargs, CmdFunc func
);
};
enum {
CODE_START = 0,
CODE_OFFSET,

View File

@ -96,13 +96,12 @@ CsAlias::CsAlias(ostd::ConstCharRange name, CsValue const &v, int fl):
p_acode(nullptr), p_astack(nullptr), p_val(v)
{}
Command::Command(
CsCommand::CsCommand(
int tp, ostd::ConstCharRange name, ostd::ConstCharRange args,
ostd::Uint32 amask, int nargs, CmdFunc f
int nargs, CmdFunc f
):
CsIdent(CsIdentType::command, name, 0),
cargs(cs_dup_ostr(args)),
argmask(amask), numargs(nargs), cb_cftv(ostd::move(f))
p_cargs(cs_dup_ostr(args)), p_numargs(nargs), cb_cftv(ostd::move(f))
{
p_type = tp;
}
@ -129,6 +128,20 @@ bool CsIdent::is_command() const {
return get_type() == CsIdentType::command;
}
CsCommand *CsIdent::get_command() {
if (!is_command()) {
return nullptr;
}
return static_cast<CsCommand *>(this);
}
CsCommand const *CsIdent::get_command() const {
if (!is_command()) {
return nullptr;
}
return static_cast<CsCommand const *>(this);
}
bool CsIdent::is_special() const {
return get_type() == CsIdentType::special;
}
@ -273,7 +286,7 @@ CsState::~CsState() {
a->get_value().force_null();
a->clean_code();
} else if (i->is_command() || i->is_special()) {
delete[] static_cast<Command *>(i)->cargs;
delete[] static_cast<CsCommand *>(i)->p_cargs;
}
delete i;
}
@ -1102,7 +1115,6 @@ static bool cs_add_command(
CsState &cs, ostd::ConstCharRange name, ostd::ConstCharRange args,
CmdFunc func, int type = ID_COMMAND
) {
ostd::Uint32 argmask = 0;
int nargs = 0;
for (ostd::ConstCharRange fmt(args); !fmt.empty(); ++fmt) {
switch (*fmt) {
@ -1114,18 +1126,12 @@ static bool cs_add_command(
case 'T':
case 'E':
case 'N':
case 'D':
if (nargs < MaxArguments) {
nargs++;
}
break;
case 'S':
case 's':
case 'e':
case 'r':
case '$':
if (nargs < MaxArguments) {
argmask |= 1 << nargs;
nargs++;
}
break;
@ -1134,7 +1140,7 @@ static bool cs_add_command(
case '3':
case '4':
if (nargs < MaxArguments) {
fmt.push_front_n(fmt.front() - '0' + 1);
fmt.push_front_n(*fmt - '0' + 1);
}
break;
case 'C':
@ -1148,7 +1154,7 @@ static bool cs_add_command(
return false;
}
}
cs.add_ident<Command>(type, name, args, argmask, nargs, ostd::move(func));
cs.add_ident<CsCommand>(type, name, args, nargs, ostd::move(func));
return true;
}

View File

@ -130,6 +130,7 @@ struct CsIvar;
struct CsFvar;
struct CsSvar;
struct CsAlias;
struct CsCommand;
struct OSTD_EXPORT CsIdent {
friend struct CsState;
@ -151,6 +152,9 @@ struct OSTD_EXPORT CsIdent {
CsAlias const *get_alias() const;
bool is_command() const;
CsCommand *get_command();
CsCommand const *get_command() const;
bool is_special() const;
bool is_var() const;
@ -291,6 +295,30 @@ private:
CsValue p_val;
};
using CmdFunc = ostd::Function<void(CsValueRange, CsValue &)>;
struct CsCommand: CsIdent {
friend struct CsState;
ostd::ConstCharRange get_args() const {
return p_cargs;
}
int get_num_args() const {
return p_numargs;
}
char *p_cargs;
int p_numargs;
CmdFunc cb_cftv;
private:
CsCommand(
int type, ostd::ConstCharRange name, ostd::ConstCharRange args,
int numargs, CmdFunc func
);
};
struct CsIdentLink {
CsIdent *id;
CsIdentLink *next;
@ -306,8 +334,6 @@ enum {
CS_LIB_ALL = 0b1111
};
using CmdFunc = ostd::Function<void(CsValueRange, CsValue &)>;
struct OSTD_EXPORT CsState {
CsMap<ostd::ConstCharRange, CsIdent *> idents;
CsVector<CsIdent *> identmap;

View File

@ -1,18 +1,66 @@
#ifdef CS_REPL_USE_LINENOISE
#ifdef OSTD_PLATFORM_POSIX
#ifndef CS_REPL_HAS_EDIT
#define CS_REPL_HAS_EDIT
/* use the bundled linenoise library, default */
#include <errno.h>
#include <ctype.h>
#include <ostd/string.hh>
#include <ostd/maybe.hh>
#include "linenoise.hh"
#ifdef CS_REPL_HAS_COMPLETE
static void ln_complete(char const *buf, linenoiseCompletions *lc) {
ostd::ConstCharRange cmd = get_complete_cmd(buf);
for (auto id: gcs->identmap.iter()) {
if (!id->is_command()) {
continue;
}
ostd::ConstCharRange idname = id->get_name();
if (idname.size() <= cmd.size()) {
continue;
}
if (idname.slice(0, cmd.size()) == cmd) {
linenoiseAddCompletion(lc, idname.data());
}
}
}
#endif /* CS_REPL_HAS_COMPLETE */
#ifdef CS_REPL_HAS_HINTS
static char *ln_hint(char const *buf, int *color, int *bold) {
CsCommand *cmd = get_hint_cmd(buf);
if (!cmd) {
return nullptr;
}
ostd::String args = " [";
fill_cmd_args(args, cmd->get_args());
args += ']';
*color = 35;
*bold = 1;
char *ret = new char[args.size() + 1];
memcpy(ret, args.data(), args.size() + 1);
return ret;
}
static void ln_hint_free(void *hint) {
delete[] static_cast<char *>(hint);
}
#endif /* CS_REPL_HAS_HINTS */
static void init_lineedit(ostd::ConstCharRange) {
/* sensible default history size */
linenoiseHistorySetMaxLen(1000);
#ifdef CS_REPL_HAS_COMPLETE
linenoiseSetCompletionCallback(ln_complete);
#endif
#ifdef CS_REPL_HAS_HINTS
linenoiseSetHintsCallback(ln_hint);
linenoiseSetFreeHintsCallback(ln_hint_free);
#endif
}
static ostd::Maybe<ostd::String> read_line(CsSvar *pr) {
@ -36,3 +84,4 @@ static void add_history(ostd::ConstCharRange line) {
#endif
#endif
#endif

View File

@ -25,6 +25,138 @@ static bool stdin_is_tty() {
/* line editing support */
CsState *gcs = nullptr;
#ifdef CS_REPL_HAS_COMPLETE
static ostd::ConstCharRange get_complete_cmd(ostd::ConstCharRange buf) {
ostd::ConstCharRange not_allowed = "\"/;()[] \t\r\n\0";
ostd::ConstCharRange found = ostd::find_one_of(buf, not_allowed);
while (!found.empty()) {
++found;
buf = found;
found = ostd::find_one_of(found, not_allowed);
}
return buf;
}
#endif /* CS_REPL_HAS_COMPLETE */
#ifdef CS_REPL_HAS_HINTS
static inline ostd::ConstCharRange get_arg_type(char arg) {
switch (arg) {
case 'i':
return "int";
case 'b':
return "int_min";
case 'f':
return "float";
case 'F':
return "float_prev";
case 't':
return "any";
case 'T':
return "any_m";
case 'E':
return "cond";
case 'N':
return "numargs";
case 'S':
return "str_m";
case 's':
return "str";
case 'e':
return "block";
case 'r':
return "ident";
case '$':
return "self";
}
return "illegal";
}
static void fill_cmd_args(ostd::String &writer, ostd::ConstCharRange args) {
char variadic = '\0';
int nrep = 0;
if (!args.empty() && ((args.back() == 'V') || (args.back() == 'C'))) {
variadic = args.back();
args.pop_back();
if (!args.empty() && isdigit(args.back())) {
nrep = args.back() - '0';
args.pop_back();
}
}
if (args.empty()) {
if (variadic == 'C') {
writer += "concat(...)";
} else if (variadic == 'V') {
writer += "...";
}
return;
}
int norep = int(args.size()) - nrep;
if (norep > 0) {
for (int i = 0; i < norep; ++i) {
if (i != 0) {
writer += ", ";
}
writer += get_arg_type(*args);
++args;
}
}
if (variadic) {
if (norep > 0) {
writer += ", ";
}
if (variadic == 'C') {
writer += "concat(";
}
if (!args.empty()) {
if (args.size() > 1) {
writer += '{';
}
for (ostd::Size i = 0; i < args.size(); ++i) {
if (i) {
writer += ", ";
}
writer += get_arg_type(args[i]);
}
if (args.size() > 1) {
writer += '}';
}
}
writer += "...";
if (variadic == 'C') {
writer += ")";
}
}
}
static CsCommand *get_hint_cmd(ostd::ConstCharRange buf) {
ostd::ConstCharRange nextchars = "([;";
auto lp = ostd::find_one_of(buf, nextchars);
if (!lp.empty()) {
CsCommand *cmd = get_hint_cmd(buf + 1);
if (cmd) {
return cmd;
}
}
while (!buf.empty() && isspace(buf.front())) {
++buf;
}
ostd::ConstCharRange spaces = " \t\r\n";
ostd::ConstCharRange s = ostd::find_one_of(buf, spaces);
if (!s.empty()) {
buf = ostd::slice_until(buf, s);
}
if (!buf.empty()) {
auto cmd = gcs->get_ident(buf);
return cmd ? cmd->get_command() : nullptr;
}
return nullptr;
}
#endif /* CS_REPL_HAS_HINTS */
#include "tools/edit_linenoise.hh"
#include "tools/edit_libedit.hh"
#include "tools/edit_readline.hh"
@ -72,6 +204,7 @@ static void do_tty(CsState &cs) {
int main(int, char **argv) {
CsState cs;
gcs = &cs;
cs.init_libs();
if (stdin_is_tty()) {
init_lineedit(argv[0]);