add support for several line editing approaches (pure stdin, linenoise, libedit, readline)

master
Daniel Kolesa 2016-09-01 17:42:28 +01:00
parent 3ca2a397dd
commit 9672a1091e
14 changed files with 1688 additions and 51 deletions

View File

@ -1,5 +1,5 @@
libcubescript is provided under the zlib license. Originally by Lee "eihrul" libcubescript is provided under the zlib license. Originally by Lee "eihrul"
Salzman and Wouter van Oortmerssen, it was modified by Daniel "q66" Kolesa. Salzman and Wouter van Oortmerssen, it was mostly rewritten by Daniel "q66" Kolesa.
The Cube 2 license: The Cube 2 license:
@ -21,3 +21,7 @@ freely, subject to the following restrictions:
2. Altered source versions must be plainly marked as such, and must not be 2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
Libcubescript bundles the linenoise line editing library for REPL usage.
This library is available under the BSD 3-clause license, which is available
within its source code.

View File

@ -1,7 +1,7 @@
OSTD_PATH = ../octastd OSTD_PATH = ../octastd
LIBCS_CXXFLAGS = \ LIBCS_CXXFLAGS = \
-std=c++14 -Wall -Wextra -Wshadow -Wold-style-cast -I. \ -std=c++14 -Wall -Wextra -Wshadow -Wold-style-cast -I. -g \
-fvisibility=hidden -I$(OSTD_PATH) -fvisibility=hidden -I$(OSTD_PATH)
LIBCS_LDFLAGS = -shared LIBCS_LDFLAGS = -shared
@ -27,8 +27,10 @@ library: $(LIBCS_LIB)
$(LIBCS_LIB): $(LIBCS_OBJ) $(LIBCS_LIB): $(LIBCS_OBJ)
ar rcs $(LIBCS_LIB) $(LIBCS_OBJ) ar rcs $(LIBCS_LIB) $(LIBCS_OBJ)
repl: $(LIBCS_LIB) repl.cc repl: $(LIBCS_LIB) tools/repl.cc tools/linenoise.cc tools/linenoise.hh
$(CXX) $(CXXFLAGS) $(LIBCS_CXXFLAGS) $(LDFLAGS) repl.cc -o repl $(LIBCS_LIB) $(CXX) $(CXXFLAGS) $(LIBCS_CXXFLAGS) $(LDFLAGS) \
-Itools -DCS_REPL_USE_LINENOISE tools/linenoise.cc \
tools/repl.cc -o repl $(LIBCS_LIB)
clean: clean:
rm -f $(LIBCS_LIB) $(LIBCS_OBJ) rm -f $(LIBCS_LIB) $(LIBCS_OBJ)

View File

@ -34,6 +34,11 @@ https://github.com/OctaForge/OctaSTD
If OctaSTD can work on your system, so can libcubescript. If OctaSTD can work on your system, so can libcubescript.
The supplied Makefile builds a static library on Unix-like OSes. Link this The supplied Makefile builds a static library on Unix-like OSes. Link this
library together with your application and everything should just work. library together with your application and everything should just work. It also
builds the REPL.
The project also bundles the linenoise line editing library which has been modified
to compile cleanly as C++ (with the same flags as libcubescript). It's used strictly
for the REPL only (you don't need it to build libcubescript itself).
See COPYING.md for licensing information. See COPYING.md for licensing information.

View File

@ -18,7 +18,9 @@ CsString floatstr(CsFloat v) {
char *cs_dup_ostr(ostd::ConstCharRange s) { char *cs_dup_ostr(ostd::ConstCharRange s) {
char *r = new char[s.size() + 1]; char *r = new char[s.size() + 1];
memcpy(r, s.data(), s.size()); if (s.data()) {
memcpy(r, s.data(), s.size());
}
r[s.size()] = 0; r[s.size()] = 0;
return r; return r;
} }
@ -99,7 +101,7 @@ Command::Command(
ostd::Uint32 amask, int nargs, CmdFunc f ostd::Uint32 amask, int nargs, CmdFunc f
): ):
CsIdent(CsIdentType::command, name, 0), CsIdent(CsIdentType::command, name, 0),
cargs(!args.empty() ? cs_dup_ostr(args) : nullptr), cargs(cs_dup_ostr(args)),
argmask(amask), numargs(nargs), cb_cftv(ostd::move(f)) argmask(amask), numargs(nargs), cb_cftv(ostd::move(f))
{ {
p_type = tp; p_type = tp;

View File

@ -0,0 +1,149 @@
--- linenoise.c 2016-09-01 13:45:57.027318000 +0100
+++ linenoise.cc 2016-09-01 17:42:43.681873000 +0100
@@ -115,11 +115,11 @@
#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
-#include "linenoise.h"
+#include "linenoise.hh"
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096
-static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
+static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
static linenoiseCompletionCallback *completionCallback = NULL;
static linenoiseHintsCallback *hintsCallback = NULL;
static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
@@ -430,10 +430,10 @@
size_t len = strlen(str);
char *copy, **cvec;
- copy = malloc(len+1);
+ copy = static_cast<char *>(malloc(len+1));
if (copy == NULL) return;
memcpy(copy,str,len+1);
- cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
+ cvec = static_cast<char **>(realloc(lc->cvec,sizeof(char*)*(lc->len+1)));
if (cvec == NULL) {
free(copy);
return;
@@ -459,11 +459,11 @@
}
static void abAppend(struct abuf *ab, const char *s, int len) {
- char *new = realloc(ab->b,ab->len+len);
+ char *newb = static_cast<char *>(realloc(ab->b,ab->len+len));
- if (new == NULL) return;
- memcpy(new+ab->len,s,len);
- ab->b = new;
+ if (newb == NULL) return;
+ memcpy(newb+ab->len,s,len);
+ ab->b = newb;
ab->len += len;
}
@@ -530,7 +530,7 @@
snprintf(seq,64,"\x1b[0K");
abAppend(&ab,seq,strlen(seq));
/* Move cursor to original position. */
- snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
+ snprintf(seq,64,"\r\x1b[%dC", int(pos+plen));
abAppend(&ab,seq,strlen(seq));
if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
abFree(&ab);
@@ -552,7 +552,7 @@
struct abuf ab;
/* Update maxrows if needed. */
- if (rows > (int)l->maxrows) l->maxrows = rows;
+ if (rows > int(l->maxrows)) l->maxrows = rows;
/* First step: clear all the lines used before. To do so start by
* going to the last row. */
@@ -593,7 +593,7 @@
snprintf(seq,64,"\r");
abAppend(&ab,seq,strlen(seq));
rows++;
- if (rows > (int)l->maxrows) l->maxrows = rows;
+ if (rows > int(l->maxrows)) l->maxrows = rows;
}
/* Move cursor to right position. */
@@ -608,7 +608,7 @@
}
/* Set column. */
- col = (plen+(int)l->pos) % (int)l->cols;
+ col = (plen+int(l->pos)) % int(l->cols);
lndebug("set col %d", 1+col);
if (col)
snprintf(seq,64,"\r\x1b[%dC", col);
@@ -824,7 +824,7 @@
refreshLine(&l);
hintsCallback = hc;
}
- return (int)l.len;
+ return int(l.len);
case CTRL_C: /* ctrl-c */
errno = EAGAIN;
return -1;
@@ -970,7 +970,7 @@
if (memcmp(quit,"quit",sizeof(quit)) == 0) break;
printf("'%c' %02x (%d) (type quit to exit)\n",
- isprint(c) ? c : '?', (int)c, (int)c);
+ isprint(c) ? c : '?', int(c), int(c));
printf("\r"); /* Go left edge manually, we are in raw mode. */
fflush(stdout);
}
@@ -1008,7 +1008,7 @@
if (maxlen == 0) maxlen = 16;
maxlen *= 2;
char *oldval = line;
- line = realloc(line,maxlen);
+ line = static_cast<char *>(realloc(line,maxlen));
if (line == NULL) {
if (oldval) free(oldval);
return NULL;
@@ -1104,7 +1104,7 @@
/* Initialization on first call. */
if (history == NULL) {
- history = malloc(sizeof(char*)*history_max_len);
+ history = static_cast<char **>(malloc(sizeof(char*)*history_max_len));
if (history == NULL) return 0;
memset(history,0,(sizeof(char*)*history_max_len));
}
@@ -1131,14 +1131,14 @@
* just the latest 'len' elements if the new history length value is smaller
* than the amount of items already inside the history. */
int linenoiseHistorySetMaxLen(int len) {
- char **new;
+ char **newb;
if (len < 1) return 0;
if (history) {
int tocopy = history_len;
- new = malloc(sizeof(char*)*len);
- if (new == NULL) return 0;
+ newb = static_cast<char **>(malloc(sizeof(char*)*len));
+ if (newb == NULL) return 0;
/* If we can't copy everything, free the elements we'll not use. */
if (len < tocopy) {
@@ -1147,10 +1147,10 @@
for (j = 0; j < tocopy-len; j++) free(history[j]);
tocopy = len;
}
- memset(new,0,sizeof(char*)*len);
- memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
+ memset(newb,0,sizeof(char*)*len);
+ memcpy(newb,history+(history_len-tocopy), sizeof(char*)*tocopy);
free(history);
- history = new;
+ history = newb;
}
history_max_len = len;
if (history_len > history_max_len)

View File

@ -0,0 +1,22 @@
--- linenoise.h 2016-09-01 13:45:57.027428000 +0100
+++ linenoise.hh 2016-09-01 17:42:43.681920000 +0100
@@ -39,10 +39,6 @@
#ifndef __LINENOISE_H
#define __LINENOISE_H
-#ifdef __cplusplus
-extern "C" {
-#endif
-
typedef struct linenoiseCompletions {
size_t len;
char **cvec;
@@ -66,8 +62,4 @@
void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void);
-#ifdef __cplusplus
-}
-#endif
-
#endif /* __LINENOISE_H */

44
repl.cc
View File

@ -1,44 +0,0 @@
#include <ostd/io.hh>
#include <ostd/string.hh>
#include <ostd/maybe.hh>
#include <cubescript.hh>
using namespace cscript;
ostd::ConstCharRange version =
"CubeScript 0.0.1 (REPL mode) Copyright (C) 2016 Daniel \"q66\" Kolesa";
CsSvar *prompt = nullptr;
static ostd::String read_line() {
ostd::write(prompt->get_value());
auto app = ostd::appender<ostd::String>();
/* i really need to implement some sort of get_line for ostd streams */
for (char c = ostd::in.getchar(); c && (c != '\n'); c = ostd::in.getchar()) {
app.put(c);
}
return ostd::move(app.get());
}
static void do_tty(CsState &cs) {
ostd::writeln(version);
for (;;) {
auto line = read_line();
if (line.empty()) {
continue;
}
CsValue ret;
ret.set_null();
cs.run_ret(line, ret);
if (ret.get_type() != CsValueType::null) {
ostd::writeln(ret.get_str());
}
}
}
int main() {
CsState cs;
cs.init_libs();
prompt = cs.add_ident<CsSvar>("PROMPT", "> ");
do_tty(cs);
}

View File

@ -0,0 +1,23 @@
#ifndef CS_REPL_HAS_EDIT
/* use nothing (no line editing support) */
#include <ostd/string.hh>
#include <ostd/maybe.hh>
static void init_lineedit(ostd::ConstCharRange) {
}
static ostd::Maybe<ostd::String> read_line(CsSvar *pr) {
ostd::write(pr->get_value());
ostd::String ret;
/* i really need to implement some sort of get_line for ostd streams */
for (char c = ostd::in.getchar(); c && (c != '\n'); c = ostd::in.getchar()) {
ret += c;
}
return ostd::move(ret);
}
static void add_history(ostd::ConstCharRange) {
}
#endif

View File

@ -0,0 +1,57 @@
#ifdef CS_REPL_USE_LIBEDIT
#ifndef CS_REPL_HAS_EDIT
#define CS_REPL_HAS_EDIT
/* use the NetBSD libedit library */
#include <ostd/string.hh>
#include <ostd/maybe.hh>
#include <histedit.h>
static EditLine *els = nullptr;
static History *elh = nullptr;
static char *el_prompt(EditLine *el) {
void *prompt = nullptr;
el_get(el, EL_CLIENTDATA, &prompt);
if (!prompt) {
return const_cast<char *>("");
}
return const_cast<char *>(static_cast<CsSvar *>(prompt)->get_value().data());
}
static void init_lineedit(ostd::ConstCharRange progname) {
els = el_init(progname.data(), stdin, stdout, stderr);
elh = history_init();
/* init history with reasonable size */
HistEvent ev;
history(elh, &ev, H_SETSIZE, 1000);
el_set(els, EL_HIST, history, elh);
el_set(els, EL_PROMPT, el_prompt);
}
static ostd::Maybe<ostd::String> read_line(CsSvar *pr) {
int count;
el_set(els, EL_CLIENTDATA, static_cast<void *>(pr));
auto line = el_gets(els, &count);
if (count > 0) {
ostd::String ret = line;
/* libedit keeps the trailing \n */
ret.resize(ret.size() - 1);
return ostd::move(ret);
} else if (!count) {
return ostd::String();
}
return ostd::nothing;
}
static void add_history(ostd::ConstCharRange line) {
HistEvent ev;
/* backed by ostd::String so it's terminated */
history(elh, &ev, H_ENTER, line.data());
}
#endif
#endif

View File

@ -0,0 +1,38 @@
#ifdef CS_REPL_USE_LINENOISE
#ifndef CS_REPL_HAS_EDIT
#define CS_REPL_HAS_EDIT
/* use the bundled linenoise library, default */
#include <errno.h>
#include <ostd/string.hh>
#include <ostd/maybe.hh>
#include "linenoise.hh"
static void init_lineedit(ostd::ConstCharRange) {
/* sensible default history size */
linenoiseHistorySetMaxLen(1000);
}
static ostd::Maybe<ostd::String> read_line(CsSvar *pr) {
auto line = linenoise(pr->get_value().data());
if (!line) {
/* linenoise traps ctrl-c, detect it and let the user exit */
if (errno == EAGAIN) {
return ostd::nothing;
}
return ostd::String();
}
ostd::String ret = line;
linenoiseFree(line);
return ostd::move(ret);
}
static void add_history(ostd::ConstCharRange line) {
/* backed by ostd::String so it's terminated */
linenoiseHistoryAdd(line.data());
}
#endif
#endif

View File

@ -0,0 +1,31 @@
#ifdef CS_REPL_USE_READLINE
#ifndef CS_REPL_HAS_EDIT
#define CS_REPL_HAS_EDIT
/* use the GNU readline library */
#include <ostd/string.hh>
#include <ostd/maybe.hh>
#include <readline/readline.h>
#include <readline/history.h>
static void init_lineedit(ostd::ConstCharRange) {
}
static ostd::Maybe<ostd::String> read_line(CsSvar *pr) {
auto line = readline(pr->get_value().data());
if (!line) {
return ostd::String();
}
ostd::String ret = line;
free(line);
return ostd::move(ret);
}
static void add_history(ostd::ConstCharRange line) {
/* backed by ostd::String so it's terminated */
add_history(line.data());
}
#endif
#endif

1199
tools/linenoise.cc 100644

File diff suppressed because it is too large Load Diff

65
tools/linenoise.hh 100644
View File

@ -0,0 +1,65 @@
/* linenoise.h -- VERSION 1.0
*
* Guerrilla line editing library against the idea that a line editing lib
* needs to be 20,000 lines of C code.
*
* See linenoise.c for more information.
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __LINENOISE_H
#define __LINENOISE_H
typedef struct linenoiseCompletions {
size_t len;
char **cvec;
} linenoiseCompletions;
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
typedef void(linenoiseFreeHintsCallback)(void *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
char *linenoise(const char *prompt);
void linenoiseFree(void *ptr);
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(const char *filename);
void linenoiseClearScreen(void);
void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void);
#endif /* __LINENOISE_H */

84
tools/repl.cc 100644
View File

@ -0,0 +1,84 @@
#include <ctype.h>
#include <ostd/platform.hh>
#include <ostd/io.hh>
#include <ostd/string.hh>
#include <ostd/maybe.hh>
#include <cubescript.hh>
using namespace cscript;
ostd::ConstCharRange version = "CubeScript 0.0.1 (REPL mode)";
/* util */
#ifdef OSTD_PLATFORM_WIN32
#include <io.h>
static bool stdin_is_tty() {
return _isatty(_fileno(stdin));
}
#else
#include <unistd.h>
static bool stdin_is_tty() {
return isatty(0);
}
#endif
/* line editing support */
#include "tools/edit_linenoise.hh"
#include "tools/edit_libedit.hh"
#include "tools/edit_readline.hh"
#include "tools/edit_fallback.hh"
static void do_tty(CsState &cs) {
auto prompt = cs.add_ident<CsSvar>("PROMPT", "> ");
auto prompt2 = cs.add_ident<CsSvar>("PROMPT2", ">> ");
bool do_exit = false;
cs.add_command("quit", "", [&do_exit](auto, auto &) {
do_exit = true;
});
ostd::writeln(version);
for (;;) {
auto line = read_line(prompt);
if (!line) {
return;
}
auto lv = ostd::move(line.value());
if (lv.empty()) {
continue;
}
while (lv.back() == '\\') {
lv.resize(lv.size() - 1);
auto line2 = read_line(prompt2);
if (!line2) {
return;
}
lv += line2.value();
}
add_history(lv);
CsValue ret;
ret.set_null();
cs.run_ret(lv, ret);
if (ret.get_type() != CsValueType::null) {
ostd::writeln(ret.get_str());
}
if (do_exit) {
return;
}
}
}
int main(int, char **argv) {
CsState cs;
cs.init_libs();
if (stdin_is_tty()) {
init_lineedit(argv[0]);
do_tty(cs);
} else {
ostd::err.writeln("Only interactive mode is supported for now.");
}
}