From e4422424ce38eef494b60d7b6fe1f78aff6661f1 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sun, 22 Feb 2026 06:48:14 -0800 Subject: [PATCH 1/7] Create $&readline primitive This primitive will be used as a target for $&prompt's new "reader commands". We don't shuffle any code around for this yet; consolidating readline logic into something like a readline.c file will be done later. --- es.h | 1 + input.c | 2 +- prim-etc.c | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/es.h b/es.h index dd7e9c1..f47dde3 100644 --- a/es.h +++ b/es.h @@ -306,6 +306,7 @@ extern List *runstring(const char *str, const char *name, int flags); #if HAVE_READLINE extern Boolean resetterminal; +extern char *callreadline(char *prompt); #endif diff --git a/input.c b/input.c index 13bee2c..4ea750d 100644 --- a/input.c +++ b/input.c @@ -98,7 +98,7 @@ static int eoffill(Input UNUSED *in) { #if HAVE_READLINE /* callreadline -- readline wrapper */ -static char *callreadline(char *prompt0) { +extern char *callreadline(char *prompt0) { char *r; Ref(char *volatile, prompt, prompt0); if (prompt == NULL) diff --git a/prim-etc.c b/prim-etc.c index de6a0f7..4a38c5d 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -342,6 +342,21 @@ PRIM(resetterminal) { resetterminal = TRUE; return ltrue; } + + +PRIM(readline) { + char *line; + char *prompt = (list == NULL ? "" : getstr(list->term)); + + do { + line = callreadline(prompt); + } while (line == NULL && errno == EINTR); + + if (line == NULL) + return NULL; + list = mklist(mkstr(line), NULL); + return list; +} #endif @@ -377,6 +392,7 @@ extern Dict *initprims_etc(Dict *primdict) { X(writehistory); X(resetterminal); X(setmaxhistorylength); + X(readline); #endif return primdict; } From 927aac1578165f984c6610822d3fda2720ad4824 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sun, 22 Feb 2026 10:05:45 -0800 Subject: [PATCH 2/7] $&newparse: $&parse, but it uses reader commands $&newparse is like $&parse, except instead of taking prompt arguments and doing all the reading itself, it takes a reader command as its arguments and calls out to that to fetch input. Ideally $&parse should be replaced with $&newparse and then a lot of code can be cleaned up. --- es.h | 2 +- initial.es | 42 +++++++++++++++++++++++++++- input.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++-------- input.h | 5 ++-- prim-etc.c | 21 ++++++++++++-- token.c | 4 --- 6 files changed, 133 insertions(+), 21 deletions(-) diff --git a/es.h b/es.h index f47dde3..f4d679b 100644 --- a/es.h +++ b/es.h @@ -288,7 +288,7 @@ extern Boolean streq2(const char *s, const char *t1, const char *t2); /* input.c */ -extern Tree *parse(char *prompt1, char *prompt2); +extern Tree *parse(List *reader, char *prompt1, char *prompt2); extern Tree *parsestring(const char *str); extern Boolean isinteractive(void); extern Boolean isfromfd(void); diff --git a/initial.es b/initial.es index 987919a..d102d9a 100644 --- a/initial.es +++ b/initial.es @@ -648,9 +648,9 @@ if {~ <=$&primitives writehistory} { # The parsed code is executed only if it is non-empty, because otherwise # result gets set to zero when it should not be. -fn-%parse = $&parse fn-%batch-loop = $&batchloop fn-%is-interactive = $&isinteractive +fn-%parse = $&parse fn %interactive-loop { let (result = <=true) { @@ -685,6 +685,46 @@ fn %interactive-loop { } } + +# These functions use the new alternative to the existing $&parse +# primitive, $&newparse, and the new $&readline primitive. +# +# The difference between $&parse and $&newparse is that $&parse +# takes prompt arguments and reads from the shell all by itself, +# using entirely hardcoded logic, while $&newparse is given a +# command to run; the latter allows users to customize how input +# is read, and how history is written, to a much greater degree. + +#if {~ <=$&primitives readline} { +# fn-%read-line = $&readline +#} { +# fn %read-line prompt { +# echo -n $prompt +# $&read +# } +#} +# +#fn %parse { +# if %is-interactive { +# let (in = (); p = $*(1)) +# unwind-protect { +# $&newparse { +# let (r = <={%read-line $p}) { +# in = $in $r +# p = $*(2) +# result $r +# } +# } +# } { +# if {!~ $#fn-%write-history 0 && !~ $#in 0} { +# %write-history <={%flatten \n $in} +# } +# } +# } { +# $&newparse # fall back to built-in read with no prompt +# } +#} + # These functions are potentially passed to a REPL as the %dispatch # function. (For %eval-noprint, note that an empty list prepended # to a command just causes the command to be executed.) diff --git a/input.c b/input.c index 4ea750d..5a0006b 100644 --- a/input.c +++ b/input.c @@ -62,7 +62,7 @@ static void warn(Input *in, char *s) { extern int get(Parser *p) { if (p->ungot > 0) return p->unget[--p->ungot]; - return p->input->get(p->input); + return p->input->get(p->input, p->reader); } /* unget -- push back one character */ @@ -71,9 +71,9 @@ extern void unget(Parser *p, int c) { p->unget[p->ungot++] = c; } -static int getnormal(Input *in) { +static int getnormal(Input *in, List *reader) { int c; - while ((c = (in->buf < in->bufend ? *in->buf++ : (*in->fill)(in))) == '\0') + while ((c = (in->buf < in->bufend ? *in->buf++ : (*in->fill)(in, reader))) == '\0') warn(in, "null character ignored"); if (c != EOF) addhistbuffer((char)c); @@ -81,8 +81,8 @@ static int getnormal(Input *in) { } /* getverbose -- get a character, print it to standard error */ -static int getverbose(Input *in) { - int c = getnormal(in); +static int getverbose(Input *in, List *reader) { + int c = getnormal(in, reader); if (c != EOF) { char buf = c; ewrite(2, &buf, 1); @@ -91,7 +91,7 @@ static int getverbose(Input *in) { } /* eoffill -- report eof when called to fill input buffer */ -static int eoffill(Input UNUSED *in) { +static int eoffill(Input UNUSED *in, List UNUSED *reader) { assert(in->fd == -1); return EOF; } @@ -124,10 +124,65 @@ extern char *callreadline(char *prompt0) { } #endif +/* cmdfill -- fill input buffer by running a command */ +static int cmdfill(Input *in, List *reader) { + int oldfd; + List *result; + char *read; + size_t nread; + + assert(reader != NULL); + assert(in->buf == in->bufend); + assert(in->fd >= 0); + + oldfd = dup(0); + if (dup2(in->fd, 0) == -1) { + close(oldfd); + fail("$&parse", "dup2: %s", esstrerror(errno)); + } + + ExceptionHandler + + result = eval(reader, NULL, 0); + + CatchException (e) + + mvfd(oldfd, 0); + throw(e); + + EndExceptionHandler + + mvfd(oldfd, 0); + + if (result == NULL) { /* eof */ + if (!in->ignoreeof) { + close(in->fd); + in->fd = -1; + in->fill = eoffill; + in->runflags &= ~run_interactive; + } + return EOF; + } + read = str("%L\n", result, " "); + if ((nread = strlen(read)) > in->buflen) { + in->bufbegin = erealloc(in->bufbegin, nread); + in->buflen = nread; + } + memcpy(in->bufbegin, read, nread); + + in->buf = in->bufbegin; + in->bufend = &in->buf[nread]; + + return *in->buf++; +} /* fdfill -- fill input buffer by reading from a file descriptor */ -static int fdfill(Input *in) { +static int fdfill(Input *in, List *reader) { long nread; + + if (reader != NULL) + return cmdfill(in, reader); + assert(in->buf == in->bufend); assert(in->fd >= 0); @@ -180,7 +235,7 @@ static int fdfill(Input *in) { */ /* parse -- call yyparse(), but disable garbage collection and catch errors */ -extern Tree *parse(char *pr1, char *pr2) { +extern Tree *parse(List *reader, char *pr1, char *pr2) { int result; Parser p; void *oldpspace; @@ -190,6 +245,8 @@ extern Tree *parse(char *pr1, char *pr2) { memzero(&p, sizeof (Parser)); p.input = input; + p.reader = reader; + RefAdd(p.reader); p.space = createpspace(); oldpspace = setpspace(p.space); @@ -207,6 +264,7 @@ extern Tree *parse(char *pr1, char *pr2) { result = yyparse(&p); + RefRemove(p.reader); assert(p.ungot == 0); if (p.tokenbuf != NULL) efree(p.tokenbuf); @@ -325,7 +383,7 @@ static void stringcleanup(Input *in) { } /* stringfill -- placeholder than turns into EOF right away */ -static int stringfill(Input *in) { +static int stringfill(Input *in, List UNUSED *reader) { in->fill = eoffill; return EOF; } @@ -366,8 +424,8 @@ extern Tree *parseinput(Input *in) { input = in; ExceptionHandler - result = parse(NULL, NULL); - if (getnormal(in) != EOF) + result = parse(NULL, NULL, NULL); + if (getnormal(in, NULL) != EOF) fail("$&parse", "more than one value in term"); CatchException (e) (*input->cleanup)(input); diff --git a/input.h b/input.h index 949fec2..3f4eae4 100644 --- a/input.h +++ b/input.h @@ -10,8 +10,8 @@ struct Input { Input *prev; /* functions used to pull from Input */ - int (*get)(Input *self); - int (*fill)(Input *self); + int (*get)(Input *self, List *reader); + int (*fill)(Input *self, List *reader); void (*cleanup)(Input *self); /* input buffer */ @@ -36,6 +36,7 @@ typedef enum { NW, RW, KW } WordState; /* nonword, realword, keyword */ /* Parser contains state that lasts for one call to $&parse or less. */ struct Parser { Input *input; + List *reader; void *space; /* where the parse tree is kept in memory */ /* these variables are all allocated in pspace */ diff --git a/prim-etc.c b/prim-etc.c index 4a38c5d..35c7295 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -149,6 +149,19 @@ PRIM(var) { return list; } +PRIM(newparse) { + List *result; + Ref(List *, reader, list); + Ref(Tree *, tree, NULL); + tree = parse(reader, NULL, NULL); + result = (tree == NULL) + ? NULL + : mklist(mkterm(NULL, mkclosure(gcmk(nThunk, tree), NULL)), + NULL); + RefEnd2(tree, reader); + return result; +} + static void loginput(char *input) { char *c; List *fn = varlookup("fn-%write-history", NULL); @@ -183,7 +196,7 @@ PRIM(parse) { Ref(Tree *, tree, NULL); ExceptionHandler - tree = parse(prompt1, prompt2); + tree = parse(NULL, prompt1, prompt2); CatchException (ex) Ref(List *, e, ex); loginput(dumphistbuffer()); @@ -347,6 +360,8 @@ PRIM(resetterminal) { PRIM(readline) { char *line; char *prompt = (list == NULL ? "" : getstr(list->term)); + if (list->next != NULL) + fail("$&readline", "usage: %read-line [prompt]"); do { line = callreadline(prompt); @@ -354,7 +369,8 @@ PRIM(readline) { if (line == NULL) return NULL; - list = mklist(mkstr(line), NULL); + list = mklist(mkstr(str("%s", line)), NULL); + efree(line); return list; } #endif @@ -376,6 +392,7 @@ extern Dict *initprims_etc(Dict *primdict) { X(fsplit); X(var); X(parse); + X(newparse); X(batchloop); X(collect); X(home); diff --git a/token.c b/token.c index 0a454c3..e6304d6 100644 --- a/token.c +++ b/token.c @@ -61,12 +61,8 @@ const char dnw[] = { extern void print_prompt2(Parser *p) { Input *in = p->input; in->lineno++; -#if HAVE_READLINE - in->prompt = in->prompt2; -#else if ((p->input->runflags & run_interactive) && in->prompt2 != NULL) eprint("%s", in->prompt2); -#endif } /* scanerror -- called for lexical errors */ From 519c4f5ffbeece9c84fa546ba7676c7145ea3e6d Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sun, 22 Feb 2026 21:07:11 -0800 Subject: [PATCH 3/7] Post-$&newparse changes and cleanup Not all of this actually needs $&newparse as a prerequisite, like input->eof and removing the various fill functions. --- Makefile.in | 18 +- es.h | 30 +-- heredoc.c | 9 +- history.c | 163 -------------- initial.es | 70 +++--- input.c | 563 ++++++++---------------------------------------- input.h | 33 +-- main.c | 4 +- prim-etc.c | 122 +---------- prim-readline.c | 468 ++++++++++++++++++++++++++++++++++++++++ prim.c | 3 + prim.h | 1 + token.c | 27 +-- 13 files changed, 630 insertions(+), 881 deletions(-) delete mode 100644 history.c create mode 100644 prim-readline.c diff --git a/Makefile.in b/Makefile.in index bf98da9..f9ebd45 100644 --- a/Makefile.in +++ b/Makefile.in @@ -51,15 +51,15 @@ LIBS = @READLINE_LIBS@ @LIBS@ $(ADDLIBS) HFILES = config.h es.h gc.h input.h prim.h print.h sigmsgs.h \ stdenv.h syntax.h term.h token.h var.h CFILES = access.c closure.c conv.c dict.c eval.c except.c fd.c gc.c glob.c \ - glom.c input.c heredoc.c history.c list.c main.c match.c open.c opt.c \ - prim-ctl.c prim-etc.c prim-io.c prim-sys.c prim.c print.c proc.c \ - sigmsgs.c signal.c split.c status.c str.c syntax.c term.c token.c \ - tree.c util.c var.c vec.c version.c y.tab.c dump.c + glom.c input.c heredoc.c list.c main.c match.c open.c opt.c \ + prim-ctl.c prim-etc.c prim-io.c prim-readline.c prim-sys.c prim.c \ + print.c proc.c sigmsgs.c signal.c split.c status.c str.c syntax.c \ + term.c token.c tree.c util.c var.c vec.c version.c y.tab.c dump.c OFILES = access.o closure.o conv.o dict.o eval.o except.o fd.o gc.o glob.o \ - glom.o input.o heredoc.o history.o list.o main.o match.o open.o opt.o \ - prim-ctl.o prim-etc.o prim-io.o prim-sys.o prim.o print.o proc.o \ - sigmsgs.o signal.o split.o status.o str.o syntax.o term.o token.o \ - tree.o util.o var.o vec.o version.o y.tab.o + glom.o input.o heredoc.o list.o main.o match.o open.o opt.o \ + prim-ctl.o prim-etc.o prim-io.o prim-readline.o prim-sys.o prim.o \ + print.o proc.o sigmsgs.o signal.o split.o status.o str.o syntax.o \ + term.o token.o tree.o util.o var.o vec.o version.o y.tab.o OTHER = Makefile parse.y mksignal GEN = esdump y.tab.h y.output sigmsgs.c initial.c version.h @@ -134,7 +134,6 @@ glob.o : glob.c es.h config.h stdenv.h gc.h glom.o : glom.c es.h config.h stdenv.h gc.h input.o : input.c es.h config.h stdenv.h input.h token.h heredoc.o : heredoc.c es.h config.h stdenv.h gc.h input.h syntax.h token.h -history.o : history.c es.h config.h stdenv.h gc.h input.h token.h list.o : list.c es.h config.h stdenv.h gc.h main.o : main.c es.h config.h stdenv.h match.o : match.c es.h config.h stdenv.h @@ -144,6 +143,7 @@ prim.o : prim.c es.h config.h stdenv.h prim.h prim-ctl.o : prim-ctl.c es.h config.h stdenv.h prim.h prim-etc.o : prim-etc.c es.h config.h stdenv.h prim.h version.h prim-io.o : prim-io.c es.h config.h stdenv.h gc.h prim.h +prim-readline.o : prim-readline.c es.h config.h stdenv.h gc.h prim.h prim-sys.o : prim-sys.c es.h config.h stdenv.h prim.h print.o : print.c es.h config.h stdenv.h print.h proc.o : proc.c es.h config.h stdenv.h prim.h diff --git a/es.h b/es.h index f4d679b..cb48dd6 100644 --- a/es.h +++ b/es.h @@ -288,14 +288,13 @@ extern Boolean streq2(const char *s, const char *t1, const char *t2); /* input.c */ -extern Tree *parse(List *reader, char *prompt1, char *prompt2); +extern Tree *parse(List *reader); extern Tree *parsestring(const char *str); extern Boolean isinteractive(void); extern Boolean isfromfd(void); -extern void initinput(void); extern List *runfd(int fd, const char *name, int flags); -extern List *runstring(const char *str, const char *name, int flags); +extern List *runstring(const char *str, int flags); /* eval_* flags are also understood as runflags */ #define run_interactive 4 /* -i or $0[0] = '-' */ @@ -304,26 +303,6 @@ extern List *runstring(const char *str, const char *name, int flags); #define run_printcmds 32 /* -x */ #define run_lisptrees 64 /* -L and defined(LISPTREES) */ -#if HAVE_READLINE -extern Boolean resetterminal; -extern char *callreadline(char *prompt); -#endif - - -/* history.c */ -#if HAVE_READLINE -extern void inithistory(void); - -extern void sethistory(char *file); -extern void loghistory(char *cmd); -extern void setmaxhistorylength(int length); -extern void checkhistory(void); -#endif - -extern void newhistbuffer(void); -extern void addhistbuffer(char c); -extern char *dumphistbuffer(void); - /* opt.c */ @@ -340,6 +319,11 @@ extern void initprims(void); extern List *primswithprefix(const char *prefix); +/* prim-readline.c */ + +extern void inithistory(void); + + /* split.c */ extern void startsplit(const char *sep, Boolean coalesce); diff --git a/heredoc.c b/heredoc.c index 27dbc77..2dcb667 100644 --- a/heredoc.c +++ b/heredoc.c @@ -40,11 +40,10 @@ extern Tree *snarfheredoc(Parser *p, const char *eof, Boolean quoted) { yyerror(p, "here document eof-marker contains a newline"); return NULL; } - p->input->ignoreeof = TRUE; for (tree = NULL, tailp = &tree, buf = openbuffer(0);;) { int c; - print_prompt2(p); + p->input->lineno++; for (s = (unsigned char *) eof; (c = get(p)) == *s; s++) ; if (*s == '\0' && (c == '\n' || c == EOF)) { @@ -60,7 +59,7 @@ extern Tree *snarfheredoc(Parser *p, const char *eof, Boolean quoted) { if (c == EOF) { yyerror(p, "incomplete here document"); freebuffer(buf); - p->input->ignoreeof = FALSE; + p->input->eof = FALSE; return NULL; } if (c == '$' && !quoted && (c = get(p)) != '$') { @@ -75,7 +74,7 @@ extern Tree *snarfheredoc(Parser *p, const char *eof, Boolean quoted) { var = getherevar(p); if (var == NULL) { freebuffer(buf); - p->input->ignoreeof = FALSE; + p->input->eof = FALSE; return NULL; } *tailp = treecons(var, NULL); @@ -88,8 +87,6 @@ extern Tree *snarfheredoc(Parser *p, const char *eof, Boolean quoted) { break; } } - - p->input->ignoreeof = FALSE; return tree->CDR == NULL ? tree->CAR : tree; } diff --git a/history.c b/history.c deleted file mode 100644 index 4d9d5a2..0000000 --- a/history.c +++ /dev/null @@ -1,163 +0,0 @@ -/* history.c -- control the history file ($Revision: 1.1.1.1 $) */ - -#include "es.h" -#include "gc.h" -#include "input.h" - - -/* - * constants - */ - -#define BUFSIZE ((size_t) 4096) /* buffer size to fill reads into */ - - -/* - * globals - */ - -static Buffer *histbuffer = NULL; - -#if HAVE_READLINE -#include - -Boolean reloadhistory = FALSE; -static char *history; - -#if 0 -/* These split history file entries by timestamp, which allows readline to pick up - * multi-line commands correctly across process boundaries. Disabled by default, - * because it leaves the history file itself kind of ugly. */ -static int history_write_timestamps = 1; -static char history_comment_char = '#'; -#endif -#endif - - -/* - * histbuffer -- build the history line during input and dump it as a gc-string - */ - - -extern void newhistbuffer(void) { - assert(histbuffer == NULL); - histbuffer = openbuffer(BUFSIZE); -} - -extern void addhistbuffer(char c) { - if (histbuffer == NULL) - return; - histbuffer = bufputc(histbuffer, c); -} - -extern char *dumphistbuffer(void) { - char *s; - size_t len; - assert(histbuffer != NULL); - - s = sealcountedbuffer(histbuffer); - histbuffer = NULL; - - len = strlen(s); - if (len > 0 && s[len - 1] == '\n') - s[len - 1] = '\0'; - return s; -} - - -/* - * history file - */ - -#if HAVE_READLINE -static int sethistorylength = -1; /* unlimited */ - -extern void setmaxhistorylength(int len) { - sethistorylength = len; -} - -extern void loghistory(char *cmd) { - int err; - if (cmd == NULL) - return; - add_history(cmd); - if (history == NULL) - return; - - if ((err = append_history(1, history))) { - eprint("history(%s): %s\n", history, esstrerror(err)); - vardef("history", NULL, NULL); - } -} - -static int count_history(void) { - int i, n, count = 0, fd = eopen(history, oOpen); - char buf[4096]; - if (fd < 0) - return -1; - while ((n = read(fd, &buf, 4096)) != 0) { - if (n < 0) { - if (errno == EINTR) { - SIGCHK(); - continue; - } else { - close(fd); - return -1; - } - } - for (i = 0; i < n; i++) - if (buf[i] == '\n') - count++; - } - close(fd); - return count; -} - -static void reload_history(void) { - /* Attempt to populate readline history with new history file. */ - if (history != NULL) { - int n = count_history() - sethistorylength; - if (sethistorylength < 0 || n < 0) n = 0; - read_history_range(history, n, -1); - } - using_history(); - - reloadhistory = FALSE; -} - -extern void sethistory(char *file) { - if (reloadhistory) - reload_history(); - reloadhistory = TRUE; - history = file; -} - -extern void checkhistory(void) { - static int effectivelength = -1; - if (reloadhistory) - reload_history(); - if (sethistorylength != effectivelength) { - switch (sethistorylength) { - case -1: - unstifle_history(); - break; - case 0: - clear_history(); - FALLTHROUGH; - default: - stifle_history(sethistorylength); - } - effectivelength = sethistorylength; - } -} - -/* - * initialization - */ - -/* inithistory -- called at dawn of time from main() */ -extern void inithistory(void) { - /* declare the global roots */ - globalroot(&history); /* history file */ -} -#endif diff --git a/initial.es b/initial.es index d102d9a..ed7bb9e 100644 --- a/initial.es +++ b/initial.es @@ -650,7 +650,36 @@ if {~ <=$&primitives writehistory} { fn-%batch-loop = $&batchloop fn-%is-interactive = $&isinteractive -fn-%parse = $&parse + +if {~ <=$&primitives readline} { + fn-%read-line = $&readline +} { + fn %read-line prompt { + echo -n $prompt + $&read + } +} + +fn %parse { + if %is-interactive { + let (in = (); p = $*(1)) + unwind-protect { + $&parse { + let (r = <={%read-line $p}) { + in = $in $r + p = $*(2) + result $r + } + } + } { + if {!~ $#fn-%write-history 0 && !~ $#in 0} { + %write-history <={%flatten \n $in} + } + } + } { + $&parse # fall back to built-in read with no prompt + } +} fn %interactive-loop { let (result = <=true) { @@ -686,45 +715,6 @@ fn %interactive-loop { } -# These functions use the new alternative to the existing $&parse -# primitive, $&newparse, and the new $&readline primitive. -# -# The difference between $&parse and $&newparse is that $&parse -# takes prompt arguments and reads from the shell all by itself, -# using entirely hardcoded logic, while $&newparse is given a -# command to run; the latter allows users to customize how input -# is read, and how history is written, to a much greater degree. - -#if {~ <=$&primitives readline} { -# fn-%read-line = $&readline -#} { -# fn %read-line prompt { -# echo -n $prompt -# $&read -# } -#} -# -#fn %parse { -# if %is-interactive { -# let (in = (); p = $*(1)) -# unwind-protect { -# $&newparse { -# let (r = <={%read-line $p}) { -# in = $in $r -# p = $*(2) -# result $r -# } -# } -# } { -# if {!~ $#fn-%write-history 0 && !~ $#in 0} { -# %write-history <={%flatten \n $in} -# } -# } -# } { -# $&newparse # fall back to built-in read with no prompt -# } -#} - # These functions are potentially passed to a REPL as the %dispatch # function. (For %eval-noprint, note that an empty list prepended # to a command just causes the command to be executed.) diff --git a/input.c b/input.c index 5a0006b..c591dc8 100644 --- a/input.c +++ b/input.c @@ -12,23 +12,11 @@ #define BUFSIZE ((size_t) 4096) /* buffer size to fill reads into */ -/* - * macros - */ - -#define ISEOF(in) ((in)->fill == eoffill) - - /* * globals */ static Input *input; -Boolean resetterminal = FALSE; /* TODO: localize when $&readline becomes a thing */ - -#if HAVE_READLINE -#include -#endif /* @@ -58,82 +46,36 @@ static void warn(Input *in, char *s) { * getting and ungetting characters */ -/* get -- get a character, filter out nulls */ -extern int get(Parser *p) { - if (p->ungot > 0) - return p->unget[--p->ungot]; - return p->input->get(p->input, p->reader); -} - -/* unget -- push back one character */ -extern void unget(Parser *p, int c) { - assert(p->ungot < MAXUNGET); - p->unget[p->ungot++] = c; -} - -static int getnormal(Input *in, List *reader) { - int c; - while ((c = (in->buf < in->bufend ? *in->buf++ : (*in->fill)(in, reader))) == '\0') - warn(in, "null character ignored"); - if (c != EOF) - addhistbuffer((char)c); - return c; -} - -/* getverbose -- get a character, print it to standard error */ -static int getverbose(Input *in, List *reader) { - int c = getnormal(in, reader); - if (c != EOF) { - char buf = c; - ewrite(2, &buf, 1); - } - return c; -} - -/* eoffill -- report eof when called to fill input buffer */ -static int eoffill(Input UNUSED *in, List UNUSED *reader) { - assert(in->fd == -1); - return EOF; -} - -#if HAVE_READLINE -/* callreadline -- readline wrapper */ -extern char *callreadline(char *prompt0) { - char *r; - Ref(char *volatile, prompt, prompt0); - if (prompt == NULL) - prompt = ""; /* bug fix for readline 2.0 */ - checkhistory(); - if (resetterminal) { - rl_reset_terminal(NULL); - resetterminal = FALSE; - } - if (RL_ISSTATE(RL_STATE_INITIALIZED)) - rl_reset_screen_size(); - if (!sigsetjmp(slowlabel, 1)) { - slow = TRUE; - r = readline(prompt); - } else { - r = NULL; - errno = EINTR; - } - slow = FALSE; - SIGCHK(); - RefEnd(prompt); - return r; +static void initbuf(Parser *p, const char *initial) { + p->buflen = initial == NULL ? BUFSIZE : strlen(initial); + p->bufbegin = p->buf = ealloc(p->buflen); + if (initial != NULL) { + memcpy(p->buf, initial, p->buflen); + p->bufend = p->bufbegin + p->buflen; + } else + p->bufend = p->bufbegin; } -#endif -/* cmdfill -- fill input buffer by running a command */ -static int cmdfill(Input *in, List *reader) { +/* fill -- fill input buffer by running a command */ +static int fill(Parser *p) { int oldfd; List *result; char *read; size_t nread; + Input *in = p->input; - assert(reader != NULL); - assert(in->buf == in->bufend); - assert(in->fd >= 0); + assert(p->buf == p->bufend); + + if (in->str != NULL) { + initbuf(p, in->str); + in->str = NULL; + return *p->buf++; + } + + if (in->fd < 0) { + in->eof = TRUE; + return EOF; + } oldfd = dup(0); if (dup2(in->fd, 0) == -1) { @@ -143,7 +85,10 @@ static int cmdfill(Input *in, List *reader) { ExceptionHandler - result = eval(reader, NULL, 0); + if (p->reader != NULL) + result = eval(p->reader, NULL, 0); + else + result = prim("read", NULL, 0); CatchException (e) @@ -155,93 +100,54 @@ static int cmdfill(Input *in, List *reader) { mvfd(oldfd, 0); if (result == NULL) { /* eof */ - if (!in->ignoreeof) { - close(in->fd); - in->fd = -1; - in->fill = eoffill; - in->runflags &= ~run_interactive; - } + in->eof = TRUE; return EOF; } read = str("%L\n", result, " "); - if ((nread = strlen(read)) > in->buflen) { - in->bufbegin = erealloc(in->bufbegin, nread); - in->buflen = nread; + if ((nread = strlen(read)) > p->buflen) { + p->bufbegin = erealloc(p->bufbegin, nread); + p->buflen = nread; } - memcpy(in->bufbegin, read, nread); + memcpy(p->bufbegin, read, nread); - in->buf = in->bufbegin; - in->bufend = &in->buf[nread]; + p->buf = p->bufbegin; + p->bufend = &p->buf[nread]; - return *in->buf++; + return *p->buf++; } -/* fdfill -- fill input buffer by reading from a file descriptor */ -static int fdfill(Input *in, List *reader) { - long nread; - - if (reader != NULL) - return cmdfill(in, reader); - - assert(in->buf == in->bufend); - assert(in->fd >= 0); - -#if HAVE_READLINE - if (in->runflags & run_interactive && in->fd == 0) { - char *rlinebuf = NULL; - do { - rlinebuf = callreadline(in->prompt); - } while (rlinebuf == NULL && errno == EINTR); - if (rlinebuf == NULL) - nread = 0; - else { - nread = strlen(rlinebuf) + 1; - if (in->buflen < (unsigned int)nread) { - while (in->buflen < (unsigned int)nread) - in->buflen *= 2; - in->bufbegin = erealloc(in->bufbegin, in->buflen); - } - memcpy(in->bufbegin, rlinebuf, nread - 1); - in->bufbegin[nread - 1] = '\n'; - efree(rlinebuf); - } - } else -#endif - do { - nread = read(in->fd, (char *) in->bufbegin, in->buflen); - SIGCHK(); - } while (nread == -1 && errno == EINTR); - - if (nread <= 0) { - if (!in->ignoreeof) { - close(in->fd); - in->fd = -1; - in->fill = eoffill; - in->runflags &= ~run_interactive; - } - if (nread == -1) - fail("$&parse", "%s: %s", in->name == NULL ? "es" : in->name, esstrerror(errno)); - return EOF; +/* get -- get a character, filter out nulls */ +extern int get(Parser *p) { + int c; + if (p->ungot > 0) + return p->unget[--p->ungot]; + while ((c = (p->buf < p->bufend ? *p->buf++ : fill(p))) == '\0') + warn(p->input, "null character ignored"); + if (c != EOF && p->input->runflags & run_echoinput) { + char buf = (char)c; + ewrite(2, &buf, 1); } - - in->buf = in->bufbegin; - in->bufend = &in->buf[nread]; - return *in->buf++; + return c; } +/* unget -- push back one character */ +extern void unget(Parser *p, int c) { + assert(p->ungot < MAXUNGET); + p->unget[p->ungot++] = c; +} /* - * the input loop + * parse -- call yyparse(), but disable garbage collection and catch errors */ - -/* parse -- call yyparse(), but disable garbage collection and catch errors */ -extern Tree *parse(List *reader, char *pr1, char *pr2) { +extern Tree *parse(List *reader) { int result; Parser p; void *oldpspace; - if (ISEOF(input)) + if (input->eof) { + input->eof = FALSE; throw(mklist(mkstr("eof"), NULL)); + } memzero(&p, sizeof (Parser)); p.input = input; @@ -251,21 +157,15 @@ extern Tree *parse(List *reader, char *pr1, char *pr2) { oldpspace = setpspace(p.space); inityy(&p); + initbuf(&p, NULL); p.tokenbuf = ealloc(p.bufsize); - RefAdd(pr2); - input->prompt = pr1 == NULL ? NULL : pstr("%s", pr1); - input->prompt2 = pr2 == NULL ? NULL : pstr("%s", pr2); - RefRemove(pr2); -#if !HAVE_READLINE - if (input->prompt != NULL) - eprint("%s", input->prompt); -#endif - result = yyparse(&p); RefRemove(p.reader); assert(p.ungot == 0); + if (p.bufbegin != NULL) + efree(p.bufbegin); if (p.tokenbuf != NULL) efree(p.tokenbuf); @@ -287,12 +187,26 @@ extern Tree *parse(List *reader, char *pr1, char *pr2) { RefReturn(tree); } + +/* + * the input loop + */ + +/* cleanup -- clean up after an input source */ +static void cleanup(Input *in) { + if (in->fd != -1) { + unregisterfd(&in->fd); + close(in->fd); + } +} + /* runinput -- run from an input source */ extern List *runinput(Input *in, int runflags) { volatile int flags = runflags; List * volatile result = NULL; List *repl, *dispatch; Push push; + Input *prev = input; const char *dispatcher[] = { "fn-%eval-noprint", "fn-%eval-print", @@ -302,8 +216,6 @@ extern List *runinput(Input *in, int runflags) { flags &= ~eval_inchild; in->runflags = flags; - in->get = (flags & run_echoinput) ? getverbose : getnormal; - in->prev = input; input = in; ExceptionHandler @@ -330,14 +242,14 @@ extern List *runinput(Input *in, int runflags) { CatchException (e) - (*input->cleanup)(input); - input = input->prev; + cleanup(input); + input = prev; throw(e); EndExceptionHandler - input = in->prev; - (*in->cleanup)(in); + cleanup(input); + input = prev; return result; } @@ -346,14 +258,6 @@ extern List *runinput(Input *in, int runflags) { * pushing new input sources */ -/* fdcleanup -- cleanup after running from a file descriptor */ -static void fdcleanup(Input *in) { - unregisterfd(&in->fd); - if (in->fd != -1) - close(in->fd); - efree(in->bufbegin); -} - /* runfd -- run commands from a file descriptor */ extern List *runfd(int fd, const char *name, int flags) { Input in; @@ -361,13 +265,8 @@ extern List *runfd(int fd, const char *name, int flags) { memzero(&in, sizeof (Input)); in.lineno = 1; - in.fill = fdfill; - in.cleanup = fdcleanup; in.fd = fd; registerfd(&in.fd, TRUE); - in.buflen = BUFSIZE; - in.bufbegin = in.buf = ealloc(in.buflen); - in.bufend = in.bufbegin; in.name = (name == NULL) ? str("fd %d", fd) : name; RefAdd(in.name); @@ -377,39 +276,23 @@ extern List *runfd(int fd, const char *name, int flags) { return result; } -/* stringcleanup -- cleanup after running from a string */ -static void stringcleanup(Input *in) { - efree(in->bufbegin); -} - -/* stringfill -- placeholder than turns into EOF right away */ -static int stringfill(Input *in, List UNUSED *reader) { - in->fill = eoffill; - return EOF; -} - /* runstring -- run commands from a string */ -extern List *runstring(const char *str, const char *name, int flags) { +extern List *runstring(const char *str, int flags) { Input in; List *result; - unsigned char *buf; assert(str != NULL); memzero(&in, sizeof (Input)); in.fd = -1; in.lineno = 1; - in.name = (name == NULL) ? str : name; - in.fill = stringfill; - in.buflen = strlen(str); - buf = ealloc(in.buflen + 1); - memcpy(buf, str, in.buflen); - in.bufbegin = in.buf = buf; - in.bufend = in.buf + in.buflen; - in.cleanup = stringcleanup; + in.name = str; + in.str = str; RefAdd(in.name); + RefAdd(in.str); result = runinput(&in, flags); + RefRemove(in.str); RefRemove(in.name); return result; } @@ -417,24 +300,23 @@ extern List *runstring(const char *str, const char *name, int flags) { /* parseinput -- turn an input source into a tree */ extern Tree *parseinput(Input *in) { Tree * volatile result = NULL; + Input *prev = input; - in->prev = input; in->runflags = 0; - in->get = getnormal; input = in; ExceptionHandler - result = parse(NULL, NULL, NULL); - if (getnormal(in, NULL) != EOF) + result = parse(NULL); + if (!in->eof) fail("$&parse", "more than one value in term"); CatchException (e) - (*input->cleanup)(input); - input = input->prev; + cleanup(input); + input = prev; throw(e); EndExceptionHandler - input = in->prev; - (*in->cleanup)(in); + cleanup(input); + input = prev; return result; } @@ -442,26 +324,19 @@ extern Tree *parseinput(Input *in) { extern Tree *parsestring(const char *str) { Input in; Tree *result; - unsigned char *buf; assert(str != NULL); - /* TODO: abstract out common code with runstring */ - memzero(&in, sizeof (Input)); in.fd = -1; in.lineno = 1; in.name = str; - in.fill = stringfill; - in.buflen = strlen(str); - buf = ealloc(in.buflen + 1); - memcpy(buf, str, in.buflen); - in.bufbegin = in.buf = buf; - in.bufend = in.buf + in.buflen; - in.cleanup = stringcleanup; + in.str = str; RefAdd(in.name); + RefAdd(in.str); result = parseinput(&in); + RefRemove(in.str); RefRemove(in.name); return result; } @@ -473,257 +348,5 @@ extern Boolean isinteractive(void) { /* isfromfd -- is the innermost input source reading from a file descriptor? */ extern Boolean isfromfd(void) { - return input == NULL ? FALSE : (input->fill == fdfill); -} - - -/* - * readline integration. - */ -#if HAVE_READLINE -/* quote -- teach readline how to quote a word during completion. - * prefix is prepended _before_ the quotes, such as: $'foo bar' */ -static char *quote(char *text, Boolean open, char *prefix, char *qp) { - char *quoted; - if (*qp != '\0' || strpbrk(text, rl_filename_quote_characters)) { - quoted = mprint("%s%#S", prefix, text); - if (open) - quoted[strlen(quoted)-1] = '\0'; - } else { - quoted = mprint("%s%s", prefix, text); - } - efree(text); - return quoted; -} - -/* unquote -- remove quotes from text and point *qp at the relevant quote char */ -static char *unquote(const char *text, char **qp) { - char *p, *r; - Boolean quoted = FALSE; - - p = r = ealloc(strlen(text) + 1); - while ((*p = *text++)) { - if (*p == '\'') { - if (quoted && *text == '\'') { - p++; - text++; - } else { - quoted = !quoted; - if (quoted && qp != NULL) - *qp = p; - } - } else if (!quoted && *p == '\\') { - /* anything else won't be handled correctly by the completer */ - if (*text == ' ' || *text == '\'') - *p++ = *text++; - } else - p++; - } - *p = '\0'; - if (!quoted && qp != NULL) - *qp = p; - return r; -} - -/* Unquote files to allow readline to detect which are directories. */ -static int unquote_for_stat(char **name) { - char *unquoted; - if (!strpbrk(*name, rl_filename_quote_characters)) - return 0; - - unquoted = unquote(*name, NULL); - efree(*name); - *name = unquoted; - return 1; -} - -/* Find the start of the word to complete. This uses the trick where we set rl_point - * to the start of the word to indicate the start of the word. For this to work, - * rl_basic_quote_characters must be the empty string or else this function's result - * is overwritten, and doing that means we have to reimplement basically all quoting - * behavior manually. */ -static char *completion_start(void) { - int i, start = 0; - Boolean quoted = FALSE, backslash = FALSE; - for (i = 0; i < rl_point; i++) { - char c = rl_line_buffer[i]; - if (backslash) { - backslash = FALSE; - continue; - } - if (c == '\'') - quoted = !quoted; - else if (!quoted && c == '\\') - backslash = TRUE; - else if (!quoted && strchr(rl_basic_word_break_characters, c)) - start = i; /* keep possible '$' char in term */ - } - rl_point = start; - return NULL; -} - -/* Basic function to use an es List created by gen() to generate readline matches. */ -static char *list_completion(const char *text, int state, List *(*gen)(const char *)) { - static char **matches = NULL; - static int i, len; - - if (!state) { - Vector *vm = vectorize(gen(text)); - matches = vm->vector; - len = vm->count; - i = 0; - } - - if (!matches || i >= len) - return NULL; - - return mprint("%s", matches[i++]); -} - -static char *var_completion(const char *text, int state) { - return list_completion(text, state, varswithprefix); -} - -static char *prim_completion(const char *text, int state) { - return list_completion(text, state, primswithprefix); -} - -static int matchcmp(const void *a, const void *b) { - return strcoll(*(const char **)a, *(const char **)b); -} - -/* Pick out a completion to perform based on the string's prefix */ -rl_compentry_func_t *select_completion(const char *text, char **prefix) { - if (*text == '$') { - switch (text[1]) { - case '&': - *prefix = "$&"; - return prim_completion; - case '^': *prefix = "$^"; break; - case '#': *prefix = "$#"; break; - default: *prefix = "$"; - } - return var_completion; - } else if (*text == '~' && !strchr(text, '/')) { - /* ~foo => username. ~foo/bar gets completed as a filename. */ - return rl_username_completion_function; - } - return rl_filename_completion_function; -} - -static rl_compentry_func_t *completion_func = NULL; - -/* Top-level completion function. If completion_func is set, performs that completion. - * Otherwise, performs a completion based on the prefix of the text. */ -char **builtin_completion(const char *text, int UNUSED start, int UNUSED end) { - char **matches = NULL, *qp = NULL, *prefix = ""; - /* Manually unquote the text, since we told readline not to. */ - char *t = unquote(text, &qp); - rl_compentry_func_t *completion; - - if (completion_func != NULL) { - completion = completion_func; - completion_func = NULL; - } else - completion = select_completion(text, &prefix); - - matches = rl_completion_matches(t+strlen(prefix), completion); - - /* Manually sort and then re-quote the matches. */ - if (matches != NULL) { - size_t i, n; - for (n = 1; matches[n]; n++) - ; - qsort(&matches[1], n - 1, sizeof(matches[0]), matchcmp); - matches[0] = quote(matches[0], n > 1, prefix, qp); - for (i = 1; i < n; i++) - matches[i] = quote(matches[i], FALSE, prefix, qp); - } - - efree(t); - - /* Since we had to sort and quote results ourselves, we disable the automatic - * filename completion and sorting. */ - rl_attempted_completion_over = 1; - rl_sort_completion_matches = 0; - return matches; -} - -/* Unquote matches when displaying in a menu. This wouldn't be necessary, if not for - * menu-complete. */ -static void display_matches(char **matches, int num, int max) { - int i; - char **unquoted; - - if (rl_completion_query_items > 0 && num >= rl_completion_query_items) { - int c; - rl_crlf(); - fprintf(rl_outstream, "Display all %d possibilities? (y or n)", num); - fflush(rl_outstream); - c = rl_read_key(); - if (c != 'y' && c != 'Y' && c != ' ') { - rl_crlf(); - rl_forced_update_display(); - return; - } - } - - unquoted = ealloc(sizeof(char *) * (num + 2)); - for (i = 0; matches[i]; i++) - unquoted[i] = unquote(matches[i], NULL); - unquoted[i] = NULL; - - rl_display_match_list(unquoted, num, max); - rl_forced_update_display(); - - for (i = 0; unquoted[i]; i++) - efree(unquoted[i]); - efree(unquoted); -} - -static int es_complete_filename(int UNUSED count, int UNUSED key) { - completion_func = rl_filename_completion_function; - return rl_complete_internal(rl_completion_mode(es_complete_filename)); -} - -static int es_complete_variable(int UNUSED count, int UNUSED key) { - completion_func = var_completion; - return rl_complete_internal(rl_completion_mode(es_complete_variable)); -} - -static int es_complete_primitive(int UNUSED count, int UNUSED key) { - completion_func = prim_completion; - return rl_complete_internal(rl_completion_mode(es_complete_primitive)); -} -#endif /* HAVE_READLINE */ - - -/* - * initialization - */ - -/* initinput -- called at dawn of time from main() */ -extern void initinput(void) { - input = NULL; - -#if HAVE_READLINE - rl_readline_name = "es"; - - /* this word_break_characters excludes '&' due to primitive completion */ - rl_basic_word_break_characters = " \t\n`$><=;|{()}"; - rl_filename_quote_characters = " \t\n\\`'$><=;|&{()}"; - rl_basic_quote_characters = ""; - rl_special_prefixes = "$"; - - rl_completion_word_break_hook = completion_start; - rl_filename_stat_hook = unquote_for_stat; - rl_attempted_completion_function = builtin_completion; - rl_completion_display_matches_hook = display_matches; - - rl_add_funmap_entry("es-complete-filename", es_complete_filename); - rl_add_funmap_entry("es-complete-variable", es_complete_variable); - rl_add_funmap_entry("es-complete-primitive", es_complete_primitive); - rl_bind_keyseq("\033/", es_complete_filename); - rl_bind_keyseq("\033$", es_complete_variable); -#endif + return input == NULL ? FALSE : input->fd >= 0; } diff --git a/input.h b/input.h index 3f4eae4..f96f72e 100644 --- a/input.h +++ b/input.h @@ -6,29 +6,11 @@ /* Input contains state that lasts longer than a $&parse. */ struct Input { - /* previous Input */ - Input *prev; - - /* functions used to pull from Input */ - int (*get)(Input *self, List *reader); - int (*fill)(Input *self, List *reader); - void (*cleanup)(Input *self); - - /* input buffer */ - size_t buflen; - unsigned char *buf, *bufend, *bufbegin; - - /* input metadata and flags */ - const char *name; + const char *name, *str; int lineno; int fd; + Boolean eof; int runflags; - - /* TODO: these belong in Parser, but it's a bit of work to do that - * with the current input design; it's fine to wait until the Bigger - * refactor to do this. */ - Boolean ignoreeof; - char *prompt, *prompt2; /* pspace-allocated */ }; typedef enum { NW, RW, KW } WordState; /* nonword, realword, keyword */ @@ -37,20 +19,23 @@ typedef enum { NW, RW, KW } WordState; /* nonword, realword, keyword */ struct Parser { Input *input; List *reader; - void *space; /* where the parse tree is kept in memory */ + void *space; /* pspace, for parser-related allocations */ /* these variables are all allocated in pspace */ Tree *tree; /* the final parse tree */ Here *hereq; /* pending here document queue */ const char *error; /* syntax error, if it exists */ + /* read buffer */ + size_t buflen; + unsigned char *buf, *bufend, *bufbegin; + /* token pushback buffer */ - int unget[MAXUNGET]; - int ungot; + int ungot, unget[MAXUNGET]; /* lexer state */ WordState ws; - Boolean newline, goterror, dollar; + Boolean dollar; size_t bufsize; char *tokenbuf; }; diff --git a/main.c b/main.c index f8f19ad..b576c42 100644 --- a/main.c +++ b/main.c @@ -177,8 +177,6 @@ int main(int argc, char **argv0) { ExceptionHandler roothandler = &_localhandler; /* unhygeinic */ - - initinput(); #if HAVE_READLINE inithistory(); #endif @@ -215,7 +213,7 @@ int main(int argc, char **argv0) { vardef("*", NULL, argp); vardef("0", NULL, mklist(mkstr(argv[0]), NULL)); if (cmd != NULL) - status = exitstatus(runstring(cmd, NULL, runflags)); + status = exitstatus(runstring(cmd, runflags)); else status = exitstatus(runfd(0, "stdin", runflags)); diff --git a/prim-etc.c b/prim-etc.c index 35c7295..2c91f8e 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -149,11 +149,11 @@ PRIM(var) { return list; } -PRIM(newparse) { +PRIM(parse) { List *result; Ref(List *, reader, list); Ref(Tree *, tree, NULL); - tree = parse(reader, NULL, NULL); + tree = parse(reader); result = (tree == NULL) ? NULL : mklist(mkterm(NULL, mkclosure(gcmk(nThunk, tree), NULL)), @@ -162,57 +162,6 @@ PRIM(newparse) { return result; } -static void loginput(char *input) { - char *c; - List *fn = varlookup("fn-%write-history", NULL); - if (!isinteractive() || !isfromfd() || fn == NULL) - return; - for (c = input;; c++) - switch (*c) { - case '#': case '\n': return; - case ' ': case '\t': break; - default: goto writeit; - } -writeit: - gcdisable(); - Ref(List *, list, append(fn, mklist(mkstr(input), NULL))); - gcenable(); - eval(list, NULL, 0); - RefEnd(list); -} - -PRIM(parse) { - List *result; - Ref(char *, prompt1, NULL); - Ref(char *, prompt2, NULL); - Ref(List *, lp, list); - if (lp != NULL) { - prompt1 = getstr(lp->term); - if ((lp = lp->next) != NULL) - prompt2 = getstr(lp->term); - } - RefEnd(lp); - newhistbuffer(); - - Ref(Tree *, tree, NULL); - ExceptionHandler - tree = parse(NULL, prompt1, prompt2); - CatchException (ex) - Ref(List *, e, ex); - loginput(dumphistbuffer()); - throw(e); - RefEnd(e); - EndExceptionHandler - - loginput(dumphistbuffer()); - result = (tree == NULL) - ? NULL - : mklist(mkterm(NULL, mkclosure(gcmk(nThunk, tree), NULL)), - NULL); - RefEnd3(tree, prompt2, prompt1); - return result; -} - PRIM(exitonfalse) { return eval(list, NULL, evalflags | eval_exitonfalse); } @@ -316,65 +265,6 @@ PRIM(setmaxevaldepth) { RefReturn(lp); } -#if HAVE_READLINE -PRIM(sethistory) { - if (list == NULL) { - sethistory(NULL); - return NULL; - } - Ref(List *, lp, list); - sethistory(getstr(lp->term)); - RefReturn(lp); -} - -PRIM(writehistory) { - if (list == NULL || list->next != NULL) - fail("$&writehistory", "usage: $&writehistory command"); - loghistory(getstr(list->term)); - return NULL; -} - -PRIM(setmaxhistorylength) { - char *s; - int n; - if (list == NULL) { - setmaxhistorylength(-1); /* unlimited */ - return NULL; - } - if (list->next != NULL) - fail("$&setmaxhistorylength", "usage: $&setmaxhistorylength [limit]"); - Ref(List *, lp, list); - n = (int)strtol(getstr(lp->term), &s, 0); - if (n < 0 || (s != NULL && *s != '\0')) - fail("$&setmaxhistorylength", "max-history-length must be set to a positive integer"); - setmaxhistorylength(n); - RefReturn(lp); -} - -PRIM(resetterminal) { - resetterminal = TRUE; - return ltrue; -} - - -PRIM(readline) { - char *line; - char *prompt = (list == NULL ? "" : getstr(list->term)); - if (list->next != NULL) - fail("$&readline", "usage: %read-line [prompt]"); - - do { - line = callreadline(prompt); - } while (line == NULL && errno == EINTR); - - if (line == NULL) - return NULL; - list = mklist(mkstr(str("%s", line)), NULL); - efree(line); - return list; -} -#endif - /* * initialization @@ -392,7 +282,6 @@ extern Dict *initprims_etc(Dict *primdict) { X(fsplit); X(var); X(parse); - X(newparse); X(batchloop); X(collect); X(home); @@ -404,12 +293,5 @@ extern Dict *initprims_etc(Dict *primdict) { X(exitonfalse); X(noreturn); X(setmaxevaldepth); -#if HAVE_READLINE - X(sethistory); - X(writehistory); - X(resetterminal); - X(setmaxhistorylength); - X(readline); -#endif return primdict; } diff --git a/prim-readline.c b/prim-readline.c new file mode 100644 index 0000000..422d6fd --- /dev/null +++ b/prim-readline.c @@ -0,0 +1,468 @@ +/* prim-readline.c -- control the history file ($Revision: 1.1.1.1 $) */ + +#include "es.h" +#include "prim.h" + + +/* + * globals + */ + +#if HAVE_READLINE +#include +#include + +static Boolean reloadhistory = FALSE; +static Boolean resetterminal = FALSE; +static char *history; + +#if 0 +/* These split history file entries by timestamp, which allows readline to pick up + * multi-line commands correctly across process boundaries. Disabled by default, + * because it leaves the history file itself kind of ugly. */ +static int history_write_timestamps = 1; +static char history_comment_char = '#'; +#endif + +/* + * history + */ + +static int sethistorylength = -1; /* unlimited */ + +extern void setmaxhistorylength(int len) { + sethistorylength = len; +} + +extern void loghistory(char *cmd) { + int err; + if (cmd == NULL) + return; + add_history(cmd); + if (history == NULL) + return; + + if ((err = append_history(1, history))) { + eprint("history(%s): %s\n", history, esstrerror(err)); + vardef("history", NULL, NULL); + } +} + +static int count_history(void) { + int i, n, count = 0, fd = eopen(history, oOpen); + char buf[4096]; + if (fd < 0) + return -1; + while ((n = read(fd, &buf, 4096)) != 0) { + if (n < 0) { + if (errno == EINTR) { + SIGCHK(); + continue; + } else { + close(fd); + return -1; + } + } + for (i = 0; i < n; i++) + if (buf[i] == '\n') + count++; + } + close(fd); + return count; +} + +static void reload_history(void) { + /* Attempt to populate readline history with new history file. */ + if (history != NULL) { + int n = count_history() - sethistorylength; + if (sethistorylength < 0 || n < 0) n = 0; + read_history_range(history, n, -1); + } + using_history(); + + reloadhistory = FALSE; +} + +extern void sethistory(char *file) { + if (reloadhistory) + reload_history(); + reloadhistory = TRUE; + history = file; +} + +extern void checkhistory(void) { + static int effectivelength = -1; + if (reloadhistory) + reload_history(); + if (sethistorylength != effectivelength) { + switch (sethistorylength) { + case -1: + unstifle_history(); + break; + case 0: + clear_history(); + FALLTHROUGH; + default: + stifle_history(sethistorylength); + } + effectivelength = sethistorylength; + } +} + + +/* + * readline + */ + +/* quote -- teach readline how to quote a word during completion. + * prefix is prepended _before_ the quotes, such as: $'foo bar' */ +static char *quote(char *text, Boolean open, char *prefix, char *qp) { + char *quoted; + if (*qp != '\0' || strpbrk(text, rl_filename_quote_characters)) { + quoted = mprint("%s%#S", prefix, text); + if (open) + quoted[strlen(quoted)-1] = '\0'; + } else { + quoted = mprint("%s%s", prefix, text); + } + efree(text); + return quoted; +} + +/* unquote -- remove quotes from text and point *qp at the relevant quote char */ +static char *unquote(const char *text, char **qp) { + char *p, *r; + Boolean quoted = FALSE; + + p = r = ealloc(strlen(text) + 1); + while ((*p = *text++)) { + if (*p == '\'') { + if (quoted && *text == '\'') { + p++; + text++; + } else { + quoted = !quoted; + if (quoted && qp != NULL) + *qp = p; + } + } else if (!quoted && *p == '\\') { + /* anything else won't be handled correctly by the completer */ + if (*text == ' ' || *text == '\'') + *p++ = *text++; + } else + p++; + } + *p = '\0'; + if (!quoted && qp != NULL) + *qp = p; + return r; +} + +/* Unquote files to allow readline to detect which are directories. */ +static int unquote_for_stat(char **name) { + char *unquoted; + if (!strpbrk(*name, rl_filename_quote_characters)) + return 0; + + unquoted = unquote(*name, NULL); + efree(*name); + *name = unquoted; + return 1; +} + +/* Find the start of the word to complete. This uses the trick where we set rl_point + * to the start of the word to indicate the start of the word. For this to work, + * rl_basic_quote_characters must be the empty string or else this function's result + * is overwritten, and doing that means we have to reimplement basically all quoting + * behavior manually. */ +static char *completion_start(void) { + int i, start = 0; + Boolean quoted = FALSE, backslash = FALSE; + for (i = 0; i < rl_point; i++) { + char c = rl_line_buffer[i]; + if (backslash) { + backslash = FALSE; + continue; + } + if (c == '\'') + quoted = !quoted; + else if (!quoted && c == '\\') + backslash = TRUE; + else if (!quoted && strchr(rl_basic_word_break_characters, c)) + start = i; /* keep possible '$' char in term */ + } + rl_point = start; + return NULL; +} + +/* Basic function to use an es List created by gen() to generate readline matches. */ +static char *list_completion(const char *text, int state, List *(*gen)(const char *)) { + static char **matches = NULL; + static int i, len; + + if (!state) { + Vector *vm = vectorize(gen(text)); + matches = vm->vector; + len = vm->count; + i = 0; + } + + if (!matches || i >= len) + return NULL; + + return mprint("%s", matches[i++]); +} + +static char *var_completion(const char *text, int state) { + return list_completion(text, state, varswithprefix); +} + +static char *prim_completion(const char *text, int state) { + return list_completion(text, state, primswithprefix); +} + +static int matchcmp(const void *a, const void *b) { + return strcoll(*(const char **)a, *(const char **)b); +} + +/* Pick out a completion to perform based on the string's prefix */ +rl_compentry_func_t *select_completion(const char *text, char **prefix) { + if (*text == '$') { + switch (text[1]) { + case '&': + *prefix = "$&"; + return prim_completion; + case '^': *prefix = "$^"; break; + case '#': *prefix = "$#"; break; + default: *prefix = "$"; + } + return var_completion; + } else if (*text == '~' && !strchr(text, '/')) { + /* ~foo => username. ~foo/bar gets completed as a filename. */ + return rl_username_completion_function; + } + return rl_filename_completion_function; +} + +static rl_compentry_func_t *completion_func = NULL; + +/* Top-level completion function. If completion_func is set, performs that completion. + * Otherwise, performs a completion based on the prefix of the text. */ +static char **builtin_completion(const char *text, int UNUSED start, int UNUSED end) { + char **matches = NULL, *qp = NULL, *prefix = ""; + /* Manually unquote the text, since we told readline not to. */ + char *t = unquote(text, &qp); + rl_compentry_func_t *completion; + + if (completion_func != NULL) { + completion = completion_func; + completion_func = NULL; + } else + completion = select_completion(text, &prefix); + + matches = rl_completion_matches(t+strlen(prefix), completion); + + /* Manually sort and then re-quote the matches. */ + if (matches != NULL) { + size_t i, n; + for (n = 1; matches[n]; n++) + ; + qsort(&matches[1], n - 1, sizeof(matches[0]), matchcmp); + matches[0] = quote(matches[0], n > 1, prefix, qp); + for (i = 1; i < n; i++) + matches[i] = quote(matches[i], FALSE, prefix, qp); + } + + efree(t); + + /* Since we had to sort and quote results ourselves, we disable the automatic + * filename completion and sorting. */ + rl_attempted_completion_over = 1; + rl_sort_completion_matches = 0; + return matches; +} + +/* Unquote matches when displaying in a menu. This wouldn't be necessary, if not for + * menu-complete. */ +static void display_matches(char **matches, int num, int max) { + int i; + char **unquoted; + + if (rl_completion_query_items > 0 && num >= rl_completion_query_items) { + int c; + rl_crlf(); + fprintf(rl_outstream, "Display all %d possibilities? (y or n)", num); + fflush(rl_outstream); + c = rl_read_key(); + if (c != 'y' && c != 'Y' && c != ' ') { + rl_crlf(); + rl_forced_update_display(); + return; + } + } + + unquoted = ealloc(sizeof(char *) * (num + 2)); + for (i = 0; matches[i]; i++) + unquoted[i] = unquote(matches[i], NULL); + unquoted[i] = NULL; + + rl_display_match_list(unquoted, num, max); + rl_forced_update_display(); + + for (i = 0; unquoted[i]; i++) + efree(unquoted[i]); + efree(unquoted); +} + +static int es_complete_filename(int UNUSED count, int UNUSED key) { + completion_func = rl_filename_completion_function; + return rl_complete_internal(rl_completion_mode(es_complete_filename)); +} + +static int es_complete_variable(int UNUSED count, int UNUSED key) { + completion_func = var_completion; + return rl_complete_internal(rl_completion_mode(es_complete_variable)); +} + +static int es_complete_primitive(int UNUSED count, int UNUSED key) { + completion_func = prim_completion; + return rl_complete_internal(rl_completion_mode(es_complete_primitive)); +} + +static void initreadline(void) { + rl_readline_name = "es"; + + /* this word_break_characters excludes '&' due to primitive completion */ + rl_basic_word_break_characters = " \t\n`$><=;|{()}"; + rl_filename_quote_characters = " \t\n\\`'$><=;|&{()}"; + rl_basic_quote_characters = ""; + rl_special_prefixes = "$"; + + rl_completion_word_break_hook = completion_start; + rl_filename_stat_hook = unquote_for_stat; + rl_attempted_completion_function = builtin_completion; + rl_completion_display_matches_hook = display_matches; + + rl_add_funmap_entry("es-complete-filename", es_complete_filename); + rl_add_funmap_entry("es-complete-variable", es_complete_variable); + rl_add_funmap_entry("es-complete-primitive", es_complete_primitive); + rl_bind_keyseq("\033/", es_complete_filename); + rl_bind_keyseq("\033$", es_complete_variable); +} + +static void preprl(void) { + static Boolean initialized = FALSE; + if (!initialized) { + initreadline(); + initialized = TRUE; + } + checkhistory(); + if (resetterminal) { + rl_reset_terminal(NULL); + resetterminal = FALSE; + } + if (RL_ISSTATE(RL_STATE_INITIALIZED)) + rl_reset_screen_size(); +} + +extern char *callreadline(char *prompt0) { + char *r; + Ref(char *volatile, prompt, prompt0); + preprl(); + if (prompt == NULL) + prompt = ""; /* bug fix for readline 2.0 */ + if (!sigsetjmp(slowlabel, 1)) { + slow = TRUE; + r = readline(prompt); + } else { + r = NULL; + errno = EINTR; + } + slow = FALSE; + SIGCHK(); + RefEnd(prompt); + return r; +} + + +/* + * primitive interface + */ + +PRIM(sethistory) { + if (list == NULL) { + sethistory(NULL); + return NULL; + } + Ref(List *, lp, list); + sethistory(getstr(lp->term)); + RefReturn(lp); +} + +PRIM(writehistory) { + if (list == NULL || list->next != NULL) + fail("$&writehistory", "usage: $&writehistory command"); + loghistory(getstr(list->term)); + return NULL; +} + +PRIM(setmaxhistorylength) { + char *s; + int n; + if (list == NULL) { + setmaxhistorylength(-1); /* unlimited */ + return NULL; + } + if (list->next != NULL) + fail("$&setmaxhistorylength", "usage: $&setmaxhistorylength [limit]"); + Ref(List *, lp, list); + n = (int)strtol(getstr(lp->term), &s, 0); + if (n < 0 || (s != NULL && *s != '\0')) + fail("$&setmaxhistorylength", "max-history-length must be set to a positive integer"); + setmaxhistorylength(n); + RefReturn(lp); +} + +PRIM(resetterminal) { + resetterminal = TRUE; + return ltrue; +} + +PRIM(readline) { + char *line; + char *prompt = (list == NULL ? "" : getstr(list->term)); + if (list->next != NULL) + fail("$&readline", "usage: %read-line [prompt]"); + + do { + line = callreadline(prompt); + } while (line == NULL && errno == EINTR); + + if (line == NULL) + return NULL; + list = mklist(mkstr(str("%s", line)), NULL); + efree(line); + return list; +} + + +/* + * initialization + */ + +extern Dict *initprims_readline(Dict *primdict) { + X(sethistory); + X(writehistory); + X(resetterminal); + X(setmaxhistorylength); + X(readline); + + return primdict; +} + +/* inithistory -- called at dawn of time from main() */ +extern void inithistory(void) { + /* declare the global roots */ + globalroot(&history); /* history file */ +} +#endif diff --git a/prim.c b/prim.c index aaf24c7..1dc5299 100644 --- a/prim.c +++ b/prim.c @@ -47,6 +47,9 @@ extern void initprims(void) { prims = initprims_sys(prims); prims = initprims_proc(prims); prims = initprims_access(prims); +#if HAVE_READLINE + prims = initprims_readline(prims); +#endif #define primdict prims X(primitives); diff --git a/prim.h b/prim.h index de11f81..8a7b241 100644 --- a/prim.h +++ b/prim.h @@ -16,6 +16,7 @@ typedef struct { List *(*prim)(List *, int); } Prim; extern Dict *initprims_controlflow(Dict *primdict); /* prim-ctl.c */ extern Dict *initprims_io(Dict *primdict); /* prim-io.c */ extern Dict *initprims_etc(Dict *primdict); /* prim-etc.c */ +extern Dict *initprims_readline(Dict *primdict); /* prim-readline.c */ extern Dict *initprims_sys(Dict *primdict); /* prim-sys.c */ extern Dict *initprims_proc(Dict *primdict); /* proc.c */ extern Dict *initprims_access(Dict *primdict); /* access.c */ diff --git a/token.c b/token.c index e6304d6..05de5d8 100644 --- a/token.c +++ b/token.c @@ -57,19 +57,11 @@ const char dnw[] = { }; -/* print_prompt2 -- called before all continuation lines */ -extern void print_prompt2(Parser *p) { - Input *in = p->input; - in->lineno++; - if ((p->input->runflags & run_interactive) && in->prompt2 != NULL) - eprint("%s", in->prompt2); -} - /* scanerror -- called for lexical errors */ static void scanerror(Parser *p, int c, char *s) { while (c != '\n' && c != EOF) c = get(p); - p->goterror = TRUE; + unget(p, '\n'); yyerror(p, s); } @@ -136,19 +128,9 @@ extern int yylex(YYSTYPE *y, Parser *p) { const char *meta; /* allow optimizing compilers like gcc to load these */ char *buf = p->tokenbuf; /* values into registers. */ - if (p->goterror) { - p->goterror = FALSE; - return NL; - } - /* rc variable-names may contain only alnum, '*' and '_', so use dnw if we are scanning one. */ meta = (p->dollar ? dnw : nw); p->dollar = FALSE; - if (p->newline) { - --p->input->lineno; /* slight space optimization; print_prompt2() always increments lineno */ - print_prompt2(p); - p->newline = FALSE; - } top: while ((c = get(p)) == ' ' || c == '\t') p->ws = NW; if (c == EOF) @@ -220,7 +202,7 @@ top: while ((c = get(p)) == ' ' || c == '\t') while ((c = get(p)) != '\'' || (c = get(p)) == '\'') { buf[i++] = c; if (c == '\n') - print_prompt2(p); + p->input->lineno++; if (c == EOF) { p->ws = NW; scanerror(p, c, "eof in quoted string"); @@ -235,7 +217,7 @@ top: while ((c = get(p)) == ' ' || c == '\t') return QWORD; case '\\': if ((c = get(p)) == '\n') { - print_prompt2(p); + p->input->lineno++; unget(p, ' '); goto top; /* Pretend it was just another space. */ } @@ -302,7 +284,6 @@ top: while ((c = get(p)) == ' ' || c == '\t') FALLTHROUGH; case '\n': p->input->lineno++; - p->newline = TRUE; p->ws = NW; return NL; case '(': @@ -398,7 +379,7 @@ top: while ((c = get(p)) == ' ' || c == '\t') } extern void inityy(Parser *p) { - p->newline = p->dollar = p->goterror = FALSE; + p->dollar = FALSE; p->ws = NW; p->bufsize = BUFSIZE; } From 365c7c45b7ae5a014254fa62e2ceba62c2b37165 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sun, 15 Mar 2026 10:29:42 -0700 Subject: [PATCH 4/7] Remove at-startup inithistory() --- es.h | 5 ----- main.c | 3 --- prim-readline.c | 17 ++++++++++------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/es.h b/es.h index cb48dd6..a291660 100644 --- a/es.h +++ b/es.h @@ -319,11 +319,6 @@ extern void initprims(void); extern List *primswithprefix(const char *prefix); -/* prim-readline.c */ - -extern void inithistory(void); - - /* split.c */ extern void startsplit(const char *sep, Boolean coalesce); diff --git a/main.c b/main.c index b576c42..3c8e941 100644 --- a/main.c +++ b/main.c @@ -177,9 +177,6 @@ int main(int argc, char **argv0) { ExceptionHandler roothandler = &_localhandler; /* unhygeinic */ -#if HAVE_READLINE - inithistory(); -#endif initprims(); initvars(); diff --git a/prim-readline.c b/prim-readline.c index 422d6fd..7d35611 100644 --- a/prim-readline.c +++ b/prim-readline.c @@ -14,7 +14,7 @@ static Boolean reloadhistory = FALSE; static Boolean resetterminal = FALSE; -static char *history; +static char *history = NULL; #if 0 /* These split history file entries by timestamp, which allows readline to pick up @@ -83,7 +83,16 @@ static void reload_history(void) { reloadhistory = FALSE; } +static void inithistory(void) { + static Boolean initialized = FALSE; + if (initialized) + return; + globalroot(&history); + initialized = TRUE; +} + extern void sethistory(char *file) { + inithistory(); if (reloadhistory) reload_history(); reloadhistory = TRUE; @@ -459,10 +468,4 @@ extern Dict *initprims_readline(Dict *primdict) { return primdict; } - -/* inithistory -- called at dawn of time from main() */ -extern void inithistory(void) { - /* declare the global roots */ - globalroot(&history); /* history file */ -} #endif From 705b2a4cebafcdb3c966a6c0bd6f311fe3d28455 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sun, 15 Mar 2026 15:45:14 -0700 Subject: [PATCH 5/7] Fix memory leak --- input.c | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/input.c b/input.c index 5ee137c..a014b93 100644 --- a/input.c +++ b/input.c @@ -46,16 +46,6 @@ static void warn(Input *in, char *s) { * getting and ungetting characters */ -static void initbuf(Parser *p, const char *initial) { - p->buflen = initial == NULL ? BUFSIZE : strlen(initial); - p->bufbegin = p->buf = ealloc(p->buflen); - if (initial != NULL) { - memcpy(p->buf, initial, p->buflen); - p->bufend = p->bufbegin + p->buflen; - } else - p->bufend = p->bufbegin; -} - /* fill -- fill input buffer by running a command */ static int fill(Parser *p) { int oldfd; @@ -66,12 +56,6 @@ static int fill(Parser *p) { assert(p->buf == p->bufend); - if (in->str != NULL) { - initbuf(p, in->str); - in->str = NULL; - return *p->buf++; - } - if (in->fd < 0) { in->eof = TRUE; return EOF; @@ -136,6 +120,17 @@ extern void unget(Parser *p, int c) { p->unget[p->ungot++] = c; } +static void initbuf(Parser *p) { + const char *initial = p->input->str; + p->buflen = initial == NULL ? BUFSIZE : strlen(initial); + p->bufbegin = p->buf = ealloc(p->buflen); + if (initial != NULL) { + memcpy(p->buf, initial, p->buflen); + p->bufend = p->bufbegin + p->buflen; + } else + p->bufend = p->bufbegin; +} + /* * parse -- call yyparse(), but disable garbage collection and catch errors */ @@ -157,7 +152,7 @@ extern Tree *parse(List *reader) { oldpspace = setpspace(p.space); inityy(&p); - initbuf(&p, NULL); + initbuf(&p); p.tokenbuf = ealloc(p.bufsize); result = yyparse(&p); From b2d82a2be4b83bf30418663c8ac43130222591d9 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Sun, 15 Mar 2026 16:00:14 -0700 Subject: [PATCH 6/7] Fix segfault in $&readline --- prim-readline.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prim-readline.c b/prim-readline.c index 7d35611..f332143 100644 --- a/prim-readline.c +++ b/prim-readline.c @@ -440,7 +440,7 @@ PRIM(resetterminal) { PRIM(readline) { char *line; char *prompt = (list == NULL ? "" : getstr(list->term)); - if (list->next != NULL) + if (list != NULL && list->next != NULL) fail("$&readline", "usage: %read-line [prompt]"); do { From 417b44b1fb34536823312529f89ad793a860d711 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Wed, 18 Mar 2026 07:39:41 -0700 Subject: [PATCH 7/7] Fix fd behavior for $&parse and $&readline This makes $&parse redirect shell input to stdin as if it's an actual redirection operator, and has $&readline actually use the stdin and stdout the shell has given it. --- input.c | 12 ++++-------- prim-readline.c | 24 ++++++++++++++++++++---- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/input.c b/input.c index a014b93..9ec77fc 100644 --- a/input.c +++ b/input.c @@ -48,7 +48,7 @@ static void warn(Input *in, char *s) { /* fill -- fill input buffer by running a command */ static int fill(Parser *p) { - int oldfd; + int ticket = UNREGISTERED; List *result; char *read; size_t nread; @@ -61,11 +61,7 @@ static int fill(Parser *p) { return EOF; } - oldfd = dup(0); - if (dup2(in->fd, 0) == -1) { - close(oldfd); - fail("$&parse", "dup2: %s", esstrerror(errno)); - } + ticket = defer_mvfd(TRUE, dup(in->fd), 0); ExceptionHandler @@ -76,12 +72,12 @@ static int fill(Parser *p) { CatchException (e) - mvfd(oldfd, 0); + undefer(ticket); throw(e); EndExceptionHandler - mvfd(oldfd, 0); + undefer(ticket); if (result == NULL) { /* eof */ in->eof = TRUE; diff --git a/prim-readline.c b/prim-readline.c index f332143..4ae0bdc 100644 --- a/prim-readline.c +++ b/prim-readline.c @@ -1,4 +1,4 @@ -/* prim-readline.c -- control the history file ($Revision: 1.1.1.1 $) */ +/* prim-readline.c -- readline integration, including history */ #include "es.h" #include "prim.h" @@ -443,9 +443,25 @@ PRIM(readline) { if (list != NULL && list->next != NULL) fail("$&readline", "usage: %read-line [prompt]"); - do { - line = callreadline(prompt); - } while (line == NULL && errno == EINTR); + rl_instream = fdopen(dup(fdmap(0)), "r"); + rl_outstream = fdopen(dup(fdmap(1)), "w"); + + ExceptionHandler + + do { + line = callreadline(prompt); + } while (line == NULL && errno == EINTR); + + CatchException (e) + + fclose(rl_instream); + fclose(rl_outstream); + throw(e); + + EndExceptionHandler + + fclose(rl_instream); + fclose(rl_outstream); if (line == NULL) return NULL;