From 6aca518614fd38663f684180cfeee851bc1c6c63 Mon Sep 17 00:00:00 2001 From: q66 Date: Fri, 2 Sep 2016 17:20:53 +0100 Subject: [PATCH] re-expose command and add hints/completion for now at least with linenoise --- Makefile | 4 +- cs_gen.cc | 12 ++-- cs_vm.cc | 28 +++++---- cs_vm.hh | 12 ---- cubescript.cc | 34 +++++----- cubescript.hh | 30 ++++++++- tools/edit_linenoise.hh | 49 +++++++++++++++ tools/repl.cc | 133 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 253 insertions(+), 49 deletions(-) diff --git a/Makefile b/Makefile index 5d13d8ef..5861cbec 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/cs_gen.cc b/cs_gen.cc index 489495a0..ad96e607 100644 --- a/cs_gen.cc +++ b/cs_gen.cc @@ -517,9 +517,9 @@ lookupid: if (prevargs >= MaxResults) { gs.code.push(CODE_ENTER); } - char const *fmt = static_cast(id)->cargs; - for (; *fmt; fmt++) { - switch (*fmt) { + auto fmt = static_cast(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(id)->cargs; - for (; *fmt; fmt++) { + auto fmt = static_cast(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( diff --git a/cs_vm.cc b/cs_vm.cc index cba94f12..a1e98927 100644 --- a/cs_vm.cc +++ b/cs_vm.cc @@ -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(id); + CsCommand *cb = static_cast(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(id), buf, arg, 0, true); + callcommand(cs, static_cast(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(cs.identmap[op >> 8]); - int offset = numargs - id->numargs; + CsCommand *id = static_cast(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(cs.identmap[op >> 13]); + CsCommand *id = static_cast(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(cs.identmap[op >> 13]); + CsCommand *id = static_cast(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(id), &args[offset], + cs, static_cast(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(id)->numargs) { + if (nargs < static_cast(id)->get_num_args()) { CsValue buf[MaxArguments]; memcpy(buf, args.data(), args.size() * sizeof(CsValue)); callcommand( - *this, static_cast(id), buf, ret, + *this, static_cast(id), buf, ret, nargs, false ); } else { callcommand( - *this, static_cast(id), args.data(), + *this, static_cast(id), args.data(), ret, nargs, false ); } diff --git a/cs_vm.hh b/cs_vm.hh index e3fb114d..50b780ce 100644 --- a/cs_vm.hh +++ b/cs_vm.hh @@ -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, diff --git a/cubescript.cc b/cubescript.cc index 92567c36..81e30cb3 100644 --- a/cubescript.cc +++ b/cubescript.cc @@ -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(this); +} + +CsCommand const *CsIdent::get_command() const { + if (!is_command()) { + return nullptr; + } + return static_cast(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(i)->cargs; + delete[] static_cast(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(type, name, args, argmask, nargs, ostd::move(func)); + cs.add_ident(type, name, args, nargs, ostd::move(func)); return true; } diff --git a/cubescript.hh b/cubescript.hh index ab227a0e..36c1a13d 100644 --- a/cubescript.hh +++ b/cubescript.hh @@ -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; + +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; - struct OSTD_EXPORT CsState { CsMap idents; CsVector identmap; diff --git a/tools/edit_linenoise.hh b/tools/edit_linenoise.hh index 0987291c..da690654 100644 --- a/tools/edit_linenoise.hh +++ b/tools/edit_linenoise.hh @@ -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 +#include #include #include #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(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 read_line(CsSvar *pr) { @@ -36,3 +84,4 @@ static void add_history(ostd::ConstCharRange line) { #endif #endif +#endif diff --git a/tools/repl.cc b/tools/repl.cc index 596d3c7f..56bee6b2 100644 --- a/tools/repl.cc +++ b/tools/repl.cc @@ -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]);