From 30fce093b5fdc7e2b68096cf5928d8a6d2373c7c Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 27 Nov 2023 17:24:11 -0800 Subject: [PATCH 01/16] Buffer history in-memory until yyparse() has gotten a whole command. This produces nicer behavior with multi-line commands, especially when using readline. Where before you always have to cobble together multi- line commands from history with multiple searches, now you can hit up and get the whole multi-line command at once. Also moves history logic into a separate history.c file. This is just because input.c is already huge and sprawling. --- Makefile.in | 4 +- es.h | 13 +++++- heredoc.c | 5 -- history.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++ input.c | 73 ++++------------------------- input.h | 1 - main.c | 1 + 7 files changed, 156 insertions(+), 73 deletions(-) create mode 100644 history.c diff --git a/Makefile.in b/Makefile.in index 14b23c53..f9d91665 100644 --- a/Makefile.in +++ b/Makefile.in @@ -52,12 +52,12 @@ VPATH = $(srcdir) HFILES = config.h es.h gc.h input.h prim.h print.h sigmsgs.h \ stdenv.h syntax.h term.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 list.c main.c match.c open.c opt.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 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 list.o main.o match.o open.o opt.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 diff --git a/es.h b/es.h index a8f6f733..d52a1d44 100644 --- a/es.h +++ b/es.h @@ -282,7 +282,6 @@ extern Boolean streq2(const char *s, const char *t1, const char *t2); extern char *prompt, *prompt2; extern Tree *parse(char *esprompt1, char *esprompt2); extern Tree *parsestring(const char *str); -extern void sethistory(char *file); extern Boolean isinteractive(void); extern void initinput(void); extern void resetparser(void); @@ -302,6 +301,18 @@ extern Boolean resetterminal; #endif +/* history.c */ +extern void inithistory(void); + +extern Boolean pendinghistory(void); +extern void addhistbuf(char *line, size_t len); +extern char *dumphistbuf(void); +extern void discardhistbuf(void); + +extern void sethistory(char *file); +extern void loghistory(char *cmd); + + /* opt.c */ extern void esoptbegin(List *list, const char *caller, const char *usage, Boolean throws); diff --git a/heredoc.c b/heredoc.c index 7cdcd97c..4544c44b 100644 --- a/heredoc.c +++ b/heredoc.c @@ -43,7 +43,6 @@ extern Tree *snarfheredoc(const char *eof, Boolean quoted) { yyerror("here document eof-marker contains a newline"); return NULL; } - disablehistory = TRUE; for (tree = NULL, tailp = &tree, buf = openbuffer(0);;) { int c; @@ -63,7 +62,6 @@ extern Tree *snarfheredoc(const char *eof, Boolean quoted) { if (c == EOF) { yyerror("incomplete here document"); freebuffer(buf); - disablehistory = FALSE; return NULL; } if (c == '$' && !quoted && (c = GETC()) != '$') { @@ -78,7 +76,6 @@ extern Tree *snarfheredoc(const char *eof, Boolean quoted) { var = getherevar(); if (var == NULL) { freebuffer(buf); - disablehistory = FALSE; return NULL; } *tailp = treecons(var, NULL); @@ -92,7 +89,6 @@ extern Tree *snarfheredoc(const char *eof, Boolean quoted) { } } - disablehistory = FALSE; return tree->CDR == NULL ? tree->CAR : tree; } @@ -142,5 +138,4 @@ extern Boolean queueheredoc(Tree *t) { extern void emptyherequeue(void) { hereq = NULL; - disablehistory = FALSE; } diff --git a/history.c b/history.c new file mode 100644 index 00000000..2ba013d9 --- /dev/null +++ b/history.c @@ -0,0 +1,132 @@ +/* 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 char *history; +static int historyfd = -1; +static Buffer *histbuffer = NULL; + +#if READLINE +extern void add_history(char *); +extern int read_history(char *); +extern void stifle_history(int); +#endif + + +/* + * histbuffer -- build the history line during input and dump it as a gc-string + */ + +extern Boolean pendinghistory() { + return histbuffer != NULL; +} + +extern void addhistbuf(char *line, size_t len) { + if (line == NULL || len == 0) + return; + if (histbuffer == NULL) + histbuffer = openbuffer(BUFSIZE); + histbuffer = bufncat(histbuffer, line, len); +} + +extern char *dumphistbuf() { + char *s, *p; + size_t len; + if (histbuffer == NULL) + return NULL; + + s = sealcountedbuffer(histbuffer); + histbuffer = NULL; + + len = strlen(s); + if (len > 0 && s[len - 1] == '\n') + s[len - 1] = '\0'; + + for (p = s; *p != '\0'; p++) + switch(*p) { + case '#': case '\n': goto retnull; + case ' ': case '\t': break; + default: goto retreal; + } +retnull: + return NULL; +retreal: + return s; +} + +extern void discardhistbuf() { + if (histbuffer == NULL) + return; + freebuffer(histbuffer); + histbuffer = NULL; +} + + +/* + * history file + */ + +/* loghistory -- write the last command out to a file */ +extern void loghistory(char *cmd) { + size_t len; + if (cmd == NULL) + return; +#if READLINE + add_history(cmd); +#endif + if (history == NULL) + return; + if (historyfd == -1) { + historyfd = eopen(history, oAppend); + if (historyfd == -1) { + eprint("history(%s): %s\n", history, esstrerror(errno)); + vardef("history", NULL, NULL); + return; + } + } + len = strlen(cmd); + ewrite(historyfd, cmd, len); + ewrite(historyfd, "\n", 1); +} + +/* sethistory -- change the file for the history log */ +extern void sethistory(char *file) { + if (historyfd != -1) { + close(historyfd); + historyfd = -1; + } +#if READLINE + /* Attempt to populate readline history with new history file. */ + stifle_history(50000); /* Keep memory usage within sane-ish bounds. */ + read_history(file); +#endif + history = file; +} + + +/* + * initialization + */ + +/* inithistory -- called at dawn of time from main() */ +extern void inithistory(void) { + /* declare the global roots */ + globalroot(&history); /* history file */ + + /* mark the historyfd as a file descriptor to hold back from forked children */ + registerfd(&historyfd, TRUE); +} diff --git a/input.c b/input.c index 31d525ae..0c2e9fa2 100644 --- a/input.c +++ b/input.c @@ -26,17 +26,10 @@ Input *input; char *prompt, *prompt2; -Boolean disablehistory = FALSE; Boolean resetterminal = FALSE; -static char *history; -static int historyfd = -1; #if READLINE #include -extern void add_history(char *); -extern int read_history(char *); -extern void stifle_history(int); - #if ABUSED_GETENV static char *stdgetenv(const char *); static char *esgetenv(const char *); @@ -75,55 +68,6 @@ static void warn(char *s) { } -/* - * history - */ - -/* loghistory -- write the last command out to a file */ -static void loghistory(const char *cmd, size_t len) { - const char *s, *end; - if (history == NULL || disablehistory) - return; - if (historyfd == -1) { - historyfd = eopen(history, oAppend); - if (historyfd == -1) { - eprint("history(%s): %s\n", history, esstrerror(errno)); - vardef("history", NULL, NULL); - return; - } - } - /* skip empty lines and comments in history */ - for (s = cmd, end = s + len; s < end; s++) - switch (*s) { - case '#': case '\n': return; - case ' ': case '\t': break; - default: goto writeit; - } - writeit: - ; - /* - * Small unix hack: since read() reads only up to a newline - * from a terminal, then presumably this write() will write at - * most only one input line at a time. - */ - ewrite(historyfd, cmd, len); -} - -/* sethistory -- change the file for the history log */ -extern void sethistory(char *file) { - if (historyfd != -1) { - close(historyfd); - historyfd = -1; - } -#if READLINE - /* Attempt to populate readline history with new history file. */ - stifle_history(50000); /* Keep memory usage within sane-ish bounds. */ - read_history(file); -#endif - history = file; -} - - /* * unget -- character pushback */ @@ -301,11 +245,8 @@ static int fdfill(Input *in) { if (in->runflags & run_interactive && in->fd == 0) { char *rlinebuf = callreadline(prompt); if (rlinebuf == NULL) - nread = 0; else { - if (*rlinebuf != '\0') - add_history(rlinebuf); nread = strlen(rlinebuf) + 1; if (in->buflen < (unsigned int)nread) { while (in->buflen < (unsigned int)nread) @@ -334,7 +275,7 @@ static int fdfill(Input *in) { } if (in->runflags & run_interactive) - loghistory((char *) in->bufbegin, nread); + addhistbuf((char *) in->bufbegin, nread); in->buf = in->bufbegin; in->bufend = &in->buf[nread]; @@ -350,6 +291,7 @@ static int fdfill(Input *in) { extern Tree *parse(char *pr1, char *pr2) { int result; assert(error == NULL); + assert(!pendinghistory()); inityy(); emptyherequeue(); @@ -375,8 +317,14 @@ extern Tree *parse(char *pr1, char *pr2) { assert(error != NULL); e = error; error = NULL; + discardhistbuf(); fail("$&parse", "%s", e); } + + if (input->runflags & run_interactive) + loghistory(dumphistbuf()); + else discardhistbuf(); + #if LISPTREES if (input->runflags & run_lisptrees) eprint("%B\n", parsetree); @@ -386,6 +334,7 @@ extern Tree *parse(char *pr1, char *pr2) { /* resetparser -- clear parser errors in the signal handler */ extern void resetparser(void) { + discardhistbuf(); error = NULL; } @@ -681,14 +630,10 @@ extern void initinput(void) { input = NULL; /* declare the global roots */ - globalroot(&history); /* history file */ globalroot(&error); /* parse errors */ globalroot(&prompt); /* main prompt */ globalroot(&prompt2); /* secondary prompt */ - /* mark the historyfd as a file descriptor to hold back from forked children */ - registerfd(&historyfd, TRUE); - /* call the parser's initialization */ initparse(); diff --git a/input.h b/input.h index 1fb1cd4c..08f39e2d 100644 --- a/input.h +++ b/input.h @@ -27,7 +27,6 @@ struct Input { extern Input *input; extern void unget(Input *in, int c); -extern Boolean disablehistory; extern void yyerror(char *s); diff --git a/main.c b/main.c index 3007c1af..9242f2c6 100644 --- a/main.c +++ b/main.c @@ -174,6 +174,7 @@ int main(int argc, char **argv) { roothandler = &_localhandler; /* unhygeinic */ initinput(); + inithistory(); initprims(); initvars(); From 36d18713a4c1ccce313c96e1ce289c16ab6b0f3b Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 27 Nov 2023 19:20:26 -0800 Subject: [PATCH 02/16] Use readline history functions when using readline. This allows, for example, the use of `history_write_timestamps`, which inserts a `#` and a timestamp before each command written to history. This then allows multi-line commands to be properly read from the history file. That feature is left disabled, though, because it writes incompatible history files. --- history.c | 50 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/history.c b/history.c index 2ba013d9..0b5d492e 100644 --- a/history.c +++ b/history.c @@ -16,14 +16,25 @@ * globals */ -static char *history; -static int historyfd = -1; static Buffer *histbuffer = NULL; +static char *history; #if READLINE extern void add_history(char *); extern int read_history(char *); extern void stifle_history(int); +extern int append_history(int, const char *); +extern void using_history(void); + +#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 +#else +static int historyfd = -1; #endif @@ -80,16 +91,32 @@ extern void discardhistbuf() { * history file */ -/* loghistory -- write the last command out to a file */ +#if READLINE extern void loghistory(char *cmd) { - size_t len; + int err; if (cmd == NULL) return; -#if READLINE add_history(cmd); -#endif if (history == NULL) return; + + if ((err = append_history(1, history))) { + eprint("history(%s): %s\n", history, esstrerror(err)); + vardef("history", NULL, NULL); + } +} + +extern void sethistory(char *file) { + /* Attempt to populate readline history with new history file. */ + stifle_history(50000); /* Keep memory usage within sane-ish bounds. */ + read_history(file); + history = file; +} +#else /* !READLINE */ +extern void loghistory(char *cmd) { + size_t len; + if (cmd == NULL || history == NULL) + return; if (historyfd == -1) { historyfd = eopen(history, oAppend); if (historyfd == -1) { @@ -103,19 +130,14 @@ extern void loghistory(char *cmd) { ewrite(historyfd, "\n", 1); } -/* sethistory -- change the file for the history log */ extern void sethistory(char *file) { if (historyfd != -1) { close(historyfd); historyfd = -1; } -#if READLINE - /* Attempt to populate readline history with new history file. */ - stifle_history(50000); /* Keep memory usage within sane-ish bounds. */ - read_history(file); -#endif history = file; } +#endif /* @@ -127,6 +149,10 @@ extern void inithistory(void) { /* declare the global roots */ globalroot(&history); /* history file */ +#if READLINE + using_history(); +#else /* mark the historyfd as a file descriptor to hold back from forked children */ registerfd(&historyfd, TRUE); +#endif } From c8228b3d32c89859f9e2ba2691b896da65ad1a9e Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 27 Nov 2023 21:35:07 -0800 Subject: [PATCH 03/16] Move history writing to es script. This change makes %parse output the typed line(s) in addition to the parsed command. This is then passed to the new %write-history hook function, which is set to either $&writehistory if readline support is included, or an es-native function otherwise. --- doc/es.1 | 19 ++++++++++++++----- es.h | 8 +++++--- history.c | 38 +++----------------------------------- initial.es | 31 +++++++++++++++++++++++-------- input.c | 5 +---- main.c | 2 ++ prim-etc.c | 48 +++++++++++++++++++++++++++++++----------------- 7 files changed, 79 insertions(+), 72 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index a62a492e..ff694893 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2470,7 +2470,8 @@ Reads input from the current input source, printing before reading anything and .I prompt2 before reading continued lines. -Returns a code fragment suitable for execution. +Returns a code fragment suitable for execution as the first value and the input +string which generated the code fragment as the second. Raises the exception .Cr eof on end of input. @@ -2690,7 +2691,7 @@ The following primitives implement the similarly named settor functions: .ta 1.75i 3.5i .Ds .ft \*(Cf -sethistory setnoexport setsignals +setnoexport setsignals .ft R .De .PP @@ -2706,24 +2707,32 @@ limit limit readfrom %readfrom time time writeto %writeto +writehistory %write-history .ft R .De .PP -The primitive +The primitives .Cr resetterminal -is if +and +.Cr sethistory +are if .I es is compiled with support for the .I readline or .I editline libraries. -It is used in the implementation of settor functions of the +The former is used in the implementation of settor functions of the .Cr TERM and .Cr TERMCAP variables to notify the line editing packages that the terminal configuration has changed. +The latter is used for the +.Cr set-history +settor variable when +.I readline +support is compiled in. .PP Several primitives are not directly associated with other function. They are: diff --git a/es.h b/es.h index d52a1d44..c3a297ed 100644 --- a/es.h +++ b/es.h @@ -302,16 +302,18 @@ extern Boolean resetterminal; /* history.c */ +#if READLINE extern void inithistory(void); +extern void sethistory(char *file); +extern void loghistory(char *cmd); +#endif + extern Boolean pendinghistory(void); extern void addhistbuf(char *line, size_t len); extern char *dumphistbuf(void); extern void discardhistbuf(void); -extern void sethistory(char *file); -extern void loghistory(char *cmd); - /* opt.c */ diff --git a/history.c b/history.c index 0b5d492e..c08d2e89 100644 --- a/history.c +++ b/history.c @@ -17,7 +17,6 @@ */ static Buffer *histbuffer = NULL; -static char *history; #if READLINE extern void add_history(char *); @@ -26,6 +25,8 @@ extern void stifle_history(int); extern int append_history(int, const char *); extern void using_history(void); +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, @@ -33,8 +34,6 @@ extern void using_history(void); static int history_write_timestamps = 1; static char history_comment_char = '#'; #endif -#else -static int historyfd = -1; #endif @@ -112,32 +111,6 @@ extern void sethistory(char *file) { read_history(file); history = file; } -#else /* !READLINE */ -extern void loghistory(char *cmd) { - size_t len; - if (cmd == NULL || history == NULL) - return; - if (historyfd == -1) { - historyfd = eopen(history, oAppend); - if (historyfd == -1) { - eprint("history(%s): %s\n", history, esstrerror(errno)); - vardef("history", NULL, NULL); - return; - } - } - len = strlen(cmd); - ewrite(historyfd, cmd, len); - ewrite(historyfd, "\n", 1); -} - -extern void sethistory(char *file) { - if (historyfd != -1) { - close(historyfd); - historyfd = -1; - } - history = file; -} -#endif /* @@ -148,11 +121,6 @@ extern void sethistory(char *file) { extern void inithistory(void) { /* declare the global roots */ globalroot(&history); /* history file */ - -#if READLINE using_history(); -#else - /* mark the historyfd as a file descriptor to hold back from forked children */ - registerfd(&historyfd, TRUE); -#endif } +#endif diff --git a/initial.es b/initial.es index af775b55..ab7eff91 100644 --- a/initial.es +++ b/initial.es @@ -626,9 +626,20 @@ if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} # 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 + +if {~ <=$&primitives writehistory} { + fn-%write-history = $&writehistory +} { + fn %write-history cmd { + if {!~ $history ()} { + echo $cmd >> $history + } + } +} + +fn-%is-interactive = $&isinteractive +fn-%batch-loop = $&batchloop fn %interactive-loop { let (result = <=true) { @@ -653,8 +664,9 @@ fn %interactive-loop { if {!~ $#fn-%prompt 0} { %prompt } - let (code = <={%parse $prompt}) { + let ((code line) = <={%parse $prompt}) { if {!~ $#code 0} { + %write-history $line result = <={$fn-%dispatch $code} } } @@ -706,14 +718,17 @@ set-PATH = @ { local (set-path = ) path = <={%fsplit : $*}; result $* } # These settor functions call primitives to set data structures used # inside of es. -set-history = $&sethistory set-signals = $&setsignals set-noexport = $&setnoexport set-max-eval-depth = $&setmaxevaldepth -# If the primitive $&resetterminal is defined (meaning that readline -# or editline is being used), setting the variables $TERM or $TERMCAP -# should notify the line editor library. +# If the primitives $&sethistory or $&resetterminal are defined (meaning +# that readline or editline is being used), setting the variables $TERM, +# $TERMCAP, or $history should notify the line editor library. + +if {~ <=$&primitives sethistory} { + set-history = $&sethistory +} if {~ <=$&primitives resetterminal} { set-TERM = @ { $&resetterminal; result $* } diff --git a/input.c b/input.c index 0c2e9fa2..0d6fb41f 100644 --- a/input.c +++ b/input.c @@ -321,10 +321,6 @@ extern Tree *parse(char *pr1, char *pr2) { fail("$&parse", "%s", e); } - if (input->runflags & run_interactive) - loghistory(dumphistbuf()); - else discardhistbuf(); - #if LISPTREES if (input->runflags & run_lisptrees) eprint("%B\n", parsetree); @@ -474,6 +470,7 @@ extern Tree *parseinput(Input *in) { ExceptionHandler result = parse(NULL, NULL); + discardhistbuf(); if (get(in) != EOF) fail("$&parse", "more than one value in term"); CatchException (e) diff --git a/main.c b/main.c index 9242f2c6..477ad3d2 100644 --- a/main.c +++ b/main.c @@ -174,7 +174,9 @@ int main(int argc, char **argv) { roothandler = &_localhandler; /* unhygeinic */ initinput(); +#if READLINE inithistory(); +#endif initprims(); initvars(); diff --git a/prim-etc.c b/prim-etc.c index 73638d62..cd0a159d 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -148,21 +148,13 @@ PRIM(var) { return list; } -PRIM(sethistory) { - if (list == NULL) { - sethistory(NULL); - return NULL; - } - Ref(List *, lp, list); - sethistory(getstr(lp->term)); - RefReturn(lp); -} - PRIM(parse) { - List *result; + List *result = NULL; Tree *tree; + char *h; Ref(char *, prompt1, NULL); Ref(char *, prompt2, NULL); + Ref(List *, hist, NULL); Ref(List *, lp, list); if (lp != NULL) { prompt1 = getstr(lp->term); @@ -171,11 +163,15 @@ PRIM(parse) { } RefEnd(lp); tree = parse(prompt1, prompt2); - result = (tree == NULL) - ? NULL - : mklist(mkterm(NULL, mkclosure(mk(nThunk, tree), NULL)), - NULL); - RefEnd2(prompt2, prompt1); + + if ((h = dumphistbuf()) != NULL) { + hist = mklist(mkterm(h, NULL), NULL); + } + + if (tree != NULL) + result = mklist(mkterm(NULL, mkclosure(mk(nThunk, tree), NULL)), hist); + + RefEnd3(hist, prompt2, prompt1); return result; } @@ -280,6 +276,23 @@ PRIM(setmaxevaldepth) { } #if 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(resetterminal) { resetterminal = TRUE; return true; @@ -299,7 +312,6 @@ extern Dict *initprims_etc(Dict *primdict) { X(dot); X(flatten); X(whatis); - X(sethistory); X(split); X(fsplit); X(var); @@ -316,6 +328,8 @@ extern Dict *initprims_etc(Dict *primdict) { X(noreturn); X(setmaxevaldepth); #if READLINE + X(sethistory); + X(writehistory); X(resetterminal); #endif return primdict; From 7ae493f7fce711021d50fa6b230065b7682539fe Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 27 Nov 2023 22:25:13 -0800 Subject: [PATCH 04/16] Add dependency line in Makefile.in for history.c --- Makefile.in | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.in b/Makefile.in index f9d91665..49580af4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -126,6 +126,7 @@ 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 heredoc.o : heredoc.c es.h config.h stdenv.h gc.h input.h syntax.h +history.o : history.c es.h config.h stdenv.h gc.h input.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 From 26f6adc1522fd8314c5a33e9033c22e1574deee9 Mon Sep 17 00:00:00 2001 From: jpco Date: Tue, 28 Nov 2023 18:10:16 -0800 Subject: [PATCH 05/16] Update the big REPL comment in initial.es --- initial.es | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/initial.es b/initial.es index ab7eff91..d895f0c0 100644 --- a/initial.es +++ b/initial.es @@ -599,12 +599,12 @@ if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} # %batch-loop is used. # # The function %parse can be used to call the parser, which returns -# an es command. %parse takes two arguments, which are used as the -# main and secondary prompts, respectively. %parse typically returns -# one line of input, but es allows commands (notably those with braces -# or backslash continuations) to continue across multiple lines; in -# that case, the complete command and not just one physical line is -# returned. +# an es command and the input string which was parsed to generate the +# command. %parse takes two arguments, which are used as the main and +# secondary prompts, respectively. %parse typically returns one line of +# input, but es allows commands (notably those with braces or backslash +# continuations) to continue across multiple lines; in that case, the +# complete command and not just one physical line is returned. # # By convention, the REPL must pass commands to the fn %dispatch, # which has the actual responsibility for executing the command. @@ -613,6 +613,12 @@ if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} # it is used for implementing the -e, -n, and -x options. # Typically, fn %dispatch is locally bound. # +# The input line which is returned as the second value from %parse is +# passed to the %write-history function. If the $&writehistory primitive +# is available (when readline support is compiled in), %write-history is +# set by default to that. Otherwise, %write-history appends the input +# line to $history, if set. +# # The %parse function raises the eof exception when it encounters # an end-of-file on input. You can probably simulate the C shell's # ignoreeof by restarting appropriately in this circumstance. From ef60437f39b50a19de7038f6ce5ddbeee6e4b024 Mon Sep 17 00:00:00 2001 From: jpco Date: Thu, 30 Nov 2023 23:09:09 -0800 Subject: [PATCH 06/16] Write syntax errors out to history. Typically when I write a syntax error my instinct is to hit up and then try to correct it; that requires writing syntax errors to history in order to happen. --- initial.es | 4 ++++ input.c | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/initial.es b/initial.es index d895f0c0..64f1396f 100644 --- a/initial.es +++ b/initial.es @@ -655,6 +655,10 @@ fn %interactive-loop { } {~ $e exit} { throw $e $type $msg } {~ $e error} { + if {~ $type '$&parse' && ~ $msg(1) 'syntax error'} { + %write-history $msg(2) + msg = $msg(1) + } echo >[1=2] $msg $fn-%dispatch false } {~ $e signal} { diff --git a/input.c b/input.c index 0d6fb41f..64818acd 100644 --- a/input.c +++ b/input.c @@ -313,12 +313,21 @@ extern Tree *parse(char *pr1, char *pr2) { gcenable(); if (result || error != NULL) { - char *e; + char *e, *h; assert(error != NULL); e = error; error = NULL; - discardhistbuf(); - fail("$&parse", "%s", e); + gcdisable(); + h = dumphistbuf(); + Ref(List *, ex, mklist(mkstr("error"), + mklist(mkstr("$&parse"), + mklist(mkstr(e), + (h == NULL + ? NULL + : mklist(mkstr(h), NULL)))))); + gcenable(); + throw(ex); + RefEnd(ex); } #if LISPTREES From ef9bf3f5d230d8f51cdc0e54e451a1575f2e71e4 Mon Sep 17 00:00:00 2001 From: jpco Date: Tue, 17 Sep 2024 11:10:58 -0700 Subject: [PATCH 07/16] Refactor how $&parse returns its input. The $&parse primitive now always returns the input it collected as the first element of the return value, and includes the input it collected as part of any error exceptions it throws. This extra element is "consumed" and handled by %parse, which may call %write-history with it. --- doc/es.1 | 46 ++++++++++++++++++++++++--------- es.h | 7 +++-- history.c | 38 +++++++-------------------- initial.es | 76 +++++++++++++++++++++++++++++++++++++----------------- input.c | 27 +++++-------------- prim-etc.c | 56 ++++++++++++++++++++++++++++++++-------- 6 files changed, 151 insertions(+), 99 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index e81476ac..a6085237 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2487,8 +2487,7 @@ Reads input from the current input source, printing before reading anything and .I prompt2 before reading continued lines. -Returns a code fragment suitable for execution as the first value and the input -string which generated the code fragment as the second. +Returns a code fragment suitable for execution. Raises the exception .Cr eof on end of input. @@ -2672,8 +2671,8 @@ apids here read close home run count newfd seq dup openfile split -flatten parse var -fsplit pipe whatis +flatten var fsplit +pipe whatis .ft R .De .PP @@ -2689,12 +2688,25 @@ batchloop exitonfalse isinteractive .De .PP The +.Cr parse +primitive is used to implement the +.Cr %parse +hook function, but it returns the input which it used to parse the +command as the first value of its result. It also includes the input as +the first element of the +.I message +of any +.Cr error +exceptions it throws. +.PP +The .Cr background primitive is used to implement the .Cr %background hook function, but does not print the process ID of the background process or set .Cr $apid . +.PP The .Cr backquote primitive is used to implement the @@ -2724,14 +2736,14 @@ limit limit readfrom %readfrom time time writeto %writeto -writehistory %write-history .ft R .De .PP The primitives -.Cr resetterminal +.Cr resetterminal , +.Cr sethistory , and -.Cr sethistory +.Cr writehistory are if .I es is compiled with support for the @@ -2739,17 +2751,27 @@ is compiled with support for the or .I editline libraries. -The former is used in the implementation of settor functions of the +.Cr resetterminal +is used in the implementation of settor functions of the .Cr TERM and .Cr TERMCAP variables to notify the line editing packages that the terminal configuration has changed. -The latter is used for the +.Cr sethistory +is used in the .Cr set-history -settor variable when -.I readline -support is compiled in. +settor function to signal to line editing packages which history file is +being used. +.Cr writehistory +is used in the +.Cr %write-history +function to log a new line to the line editing packages' internal +command log, and write to the +.Cr history +file if that variable is set via the +.Cr sethistory +primitive. .PP Several primitives are not directly associated with other function. They are: diff --git a/es.h b/es.h index 433a9f1b..d87a0add 100644 --- a/es.h +++ b/es.h @@ -314,10 +314,9 @@ extern void sethistory(char *file); extern void loghistory(char *cmd); #endif -extern Boolean pendinghistory(void); -extern void addhistbuf(char *line, size_t len); -extern char *dumphistbuf(void); -extern void discardhistbuf(void); +extern void newhistbuffer(void); +extern void addhistbuffer(char c); +extern char *dumphistbuffer(void); /* opt.c */ diff --git a/history.c b/history.c index c08d2e89..85aa971d 100644 --- a/history.c +++ b/history.c @@ -41,23 +41,22 @@ static char history_comment_char = '#'; * histbuffer -- build the history line during input and dump it as a gc-string */ -extern Boolean pendinghistory() { - return histbuffer != NULL; + +extern void newhistbuffer() { + assert(histbuffer == NULL); + histbuffer = openbuffer(BUFSIZE); } -extern void addhistbuf(char *line, size_t len) { - if (line == NULL || len == 0) - return; +extern void addhistbuffer(char c) { if (histbuffer == NULL) - histbuffer = openbuffer(BUFSIZE); - histbuffer = bufncat(histbuffer, line, len); + return; + histbuffer = bufputc(histbuffer, c); } -extern char *dumphistbuf() { - char *s, *p; +extern char *dumphistbuffer() { + char *s; size_t len; - if (histbuffer == NULL) - return NULL; + assert(histbuffer != NULL); s = sealcountedbuffer(histbuffer); histbuffer = NULL; @@ -65,26 +64,9 @@ extern char *dumphistbuf() { len = strlen(s); if (len > 0 && s[len - 1] == '\n') s[len - 1] = '\0'; - - for (p = s; *p != '\0'; p++) - switch(*p) { - case '#': case '\n': goto retnull; - case ' ': case '\t': break; - default: goto retreal; - } -retnull: - return NULL; -retreal: return s; } -extern void discardhistbuf() { - if (histbuffer == NULL) - return; - freebuffer(histbuffer); - histbuffer = NULL; -} - /* * history file diff --git a/initial.es b/initial.es index 64f1396f..4bf5653b 100644 --- a/initial.es +++ b/initial.es @@ -599,10 +599,9 @@ if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} # %batch-loop is used. # # The function %parse can be used to call the parser, which returns -# an es command and the input string which was parsed to generate the -# command. %parse takes two arguments, which are used as the main and -# secondary prompts, respectively. %parse typically returns one line of -# input, but es allows commands (notably those with braces or backslash +# an es command. %parse takes two arguments, which are used as the main +# and secondary prompts, respectively. %parse typically returns one line +# of input, but es allows commands (notably those with braces or backslash # continuations) to continue across multiple lines; in that case, the # complete command and not just one physical line is returned. # @@ -613,12 +612,6 @@ if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} # it is used for implementing the -e, -n, and -x options. # Typically, fn %dispatch is locally bound. # -# The input line which is returned as the second value from %parse is -# passed to the %write-history function. If the $&writehistory primitive -# is available (when readline support is compiled in), %write-history is -# set by default to that. Otherwise, %write-history appends the input -# line to $history, if set. -# # The %parse function raises the eof exception when it encounters # an end-of-file on input. You can probably simulate the C shell's # ignoreeof by restarting appropriately in this circumstance. @@ -632,21 +625,63 @@ if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} # 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-%is-interactive = $&isinteractive +fn-%batch-loop = $&batchloop + + +# The first element of the $&parse primitive's return value is the input +# that it read in order to produce its parsed command. In addition, any +# error exceptions coming from $&parse include the input such that instead +# of the typical set of +# +# e type msg +# +# terms, those exceptions contain +# +# e type input msg +# +# Both of these are "consumed" by %parse, which only returns the parsed +# command and only throws the `e type msg' terms. + +fn %parse { + catch @ e type msg { + if {~ $e error} { + if {%is-interactive && !~ $#fn-%write-history 0} { + %write-history $msg(1) + } + msg = $msg(2 ...) + } + throw $e $type $msg + } { + let ((line code) = <={$&parse $*}) { + if {%is-interactive && !~ $#fn-%write-history 0} { + %write-history $line + } + result $code + } + } +} if {~ <=$&primitives writehistory} { - fn-%write-history = $&writehistory + fn %write-history input { + if {!~ $input ''} { + $&writehistory $input + } + } } { - fn %write-history cmd { + fn %write-history input { if {!~ $history ()} { - echo $cmd >> $history + if {access -w $history} { + if {!~ $input () && !~ $input ''} { + echo $input >> $history + } + } { + history = () + } } } } -fn-%is-interactive = $&isinteractive -fn-%batch-loop = $&batchloop - fn %interactive-loop { let (result = <=true) { catch @ e type msg { @@ -655,10 +690,6 @@ fn %interactive-loop { } {~ $e exit} { throw $e $type $msg } {~ $e error} { - if {~ $type '$&parse' && ~ $msg(1) 'syntax error'} { - %write-history $msg(2) - msg = $msg(1) - } echo >[1=2] $msg $fn-%dispatch false } {~ $e signal} { @@ -674,9 +705,8 @@ fn %interactive-loop { if {!~ $#fn-%prompt 0} { %prompt } - let ((code line) = <={%parse $prompt}) { + let (code = <={%parse $prompt}) { if {!~ $#code 0} { - %write-history $line result = <={$fn-%dispatch $code} } } diff --git a/input.c b/input.c index a355678e..9a82c4b4 100644 --- a/input.c +++ b/input.c @@ -94,9 +94,7 @@ extern void unget(Input *in, int c) { if (in->ungot > 0) { assert(in->ungot < MAXUNGET); in->unget[in->ungot++] = c; - } else if (in->bufbegin < in->buf && in->buf[-1] == c && (input->runflags & run_echoinput) == 0) - --in->buf; - else { + } else { assert(in->rfill == NULL); in->rfill = in->fill; in->fill = ungetfill; @@ -117,8 +115,11 @@ extern void unget(Input *in, int c) { /* get -- get a character, filter out nulls */ static int get(Input *in) { int c; + Boolean uf = (in->fill == ungetfill); while ((c = (in->buf < in->bufend ? *in->buf++ : (*in->fill)(in))) == '\0') warn("null character ignored"); + if (!uf && c != EOF) + addhistbuffer((char)c); return c; } @@ -281,9 +282,6 @@ static int fdfill(Input *in) { return EOF; } - if (in->runflags & run_interactive) - addhistbuf((char *) in->bufbegin, nread); - in->buf = in->bufbegin; in->bufend = &in->buf[nread]; return *in->buf++; @@ -298,7 +296,6 @@ static int fdfill(Input *in) { extern Tree *parse(char *pr1, char *pr2) { int result; assert(error == NULL); - assert(!pendinghistory()); inityy(); emptyherequeue(); @@ -320,21 +317,11 @@ extern Tree *parse(char *pr1, char *pr2) { gcenable(); if (result || error != NULL) { - char *e, *h; + char *e; assert(error != NULL); e = error; error = NULL; - gcdisable(); - h = dumphistbuf(); - Ref(List *, ex, mklist(mkstr("error"), - mklist(mkstr("$&parse"), - mklist(mkstr(e), - (h == NULL - ? NULL - : mklist(mkstr(h), NULL)))))); - gcenable(); - throw(ex); - RefEnd(ex); + fail("$&parse", "%s", e); } #if LISPTREES @@ -346,7 +333,6 @@ extern Tree *parse(char *pr1, char *pr2) { /* resetparser -- clear parser errors in the signal handler */ extern void resetparser(void) { - discardhistbuf(); error = NULL; } @@ -488,7 +474,6 @@ extern Tree *parseinput(Input *in) { ExceptionHandler result = parse(NULL, NULL); - discardhistbuf(); if (get(in) != EOF) fail("$&parse", "more than one value in term"); CatchException (e) diff --git a/prim-etc.c b/prim-etc.c index cd0a159d..e26d9bc4 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -151,27 +151,47 @@ PRIM(var) { PRIM(parse) { List *result = NULL; Tree *tree; - char *h; + Ref(char *, prompt1, NULL); Ref(char *, prompt2, NULL); - Ref(List *, hist, NULL); Ref(List *, lp, list); if (lp != NULL) { prompt1 = getstr(lp->term); if ((lp = lp->next) != NULL) prompt2 = getstr(lp->term); } + newhistbuffer(); RefEnd(lp); - tree = parse(prompt1, prompt2); - if ((h = dumphistbuf()) != NULL) { - hist = mklist(mkterm(h, NULL), NULL); - } + ExceptionHandler + + tree = parse(prompt1, prompt2); + CatchException (e) + + char *h = dumphistbuffer(); + + if (termeq(e->term, "error")) { + gcdisable(); + e = mklist(e->term, + mklist(e->next->term, + mklist(mkstr(h), + e->next->next))); + gcenable(); + } + + throw(e); + + EndExceptionHandler + + gcdisable(); if (tree != NULL) - result = mklist(mkterm(NULL, mkclosure(mk(nThunk, tree), NULL)), hist); + result = mklist(mkterm(NULL, mkclosure(mk(nThunk, tree), NULL)), NULL); - RefEnd3(hist, prompt2, prompt1); + result = mklist(mkstr(dumphistbuffer()), result); + gcenable(); + + RefEnd2(prompt2, prompt1); return result; } @@ -190,9 +210,23 @@ PRIM(batchloop) { for (;;) { List *parser, *cmd; parser = varlookup("fn-%parse", NULL); - cmd = (parser == NULL) - ? prim("parse", NULL, NULL, 0) - : eval(parser, NULL, 0); + if (parser == NULL) { + ExceptionHandler + cmd = prim("parse", NULL, NULL, 0); + cmd = cmd->next; + CatchException(e) + if (termeq(e->term, "error")) { + gcdisable(); + e = mklist(e->term, + mklist(e->next->term, + e->next->next->next)); + gcenable(); + } + throw(e); + EndExceptionHandler + + } else + cmd = eval(parser, NULL, 0); SIGCHK(); dispatch = varlookup("fn-%dispatch", NULL); if (cmd != NULL) { From 547485e6f7c95b4779330b572ed226c1ddaa2825 Mon Sep 17 00:00:00 2001 From: jpco Date: Tue, 17 Sep 2024 11:23:52 -0700 Subject: [PATCH 08/16] Ref() the result variable from prim_parse --- prim-etc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prim-etc.c b/prim-etc.c index e26d9bc4..dec8d406 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -149,9 +149,9 @@ PRIM(var) { } PRIM(parse) { - List *result = NULL; - Tree *tree; + Tree *tree = NULL; + Ref(List *, result, NULL); Ref(char *, prompt1, NULL); Ref(char *, prompt2, NULL); Ref(List *, lp, list); @@ -192,7 +192,7 @@ PRIM(parse) { gcenable(); RefEnd2(prompt2, prompt1); - return result; + RefReturn(result); } PRIM(exitonfalse) { From baacbafc1add8411a7ba40430754d7ca98c51cf8 Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 9 Nov 2024 08:03:07 -0800 Subject: [PATCH 09/16] Only buffer input lines in $&parse if the -i flag was given. --- doc/es.1 | 16 +++++++++------- es.h | 2 +- initial.es | 42 ++++++++++++++++++++---------------------- input.c | 6 ++++-- prim-etc.c | 53 +++++++++++++++++++++++++++++------------------------ 5 files changed, 63 insertions(+), 56 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index f54d7778..60794986 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2685,13 +2685,15 @@ The .Cr parse primitive is used to implement the .Cr %parse -hook function, but it returns the input which it used to parse the -command as the first value of its result. It also includes the input as -the first element of the -.I message -of any -.Cr error -exceptions it throws. +hook function. +If given the +.Cr -i +flag, it returns the input it collected as the first element of its return +value, and includes the input in exceptions that it throws. +The +.Cr -- +flag can be used to prevent the interpretation of +.Cr -i . .PP The .Cr background diff --git a/es.h b/es.h index d079fe19..5b6d3f64 100644 --- a/es.h +++ b/es.h @@ -280,7 +280,7 @@ extern Boolean streq2(const char *s, const char *t1, const char *t2); /* input.c */ extern char *prompt, *prompt2; -extern Tree *parse(char *esprompt1, char *esprompt2); +extern Tree *parse(Boolean hist, char *esprompt1, char *esprompt2); extern Tree *parsestring(const char *str); extern Boolean isinteractive(void); #if HAVE_READLINE diff --git a/initial.es b/initial.es index 0d6a001a..ae27b851 100644 --- a/initial.es +++ b/initial.es @@ -626,35 +626,32 @@ if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} # result gets set to zero when it should not be. fn-%is-interactive = $&isinteractive -fn-%batch-loop = $&batchloop +fn-%parse = $&parse -- +fn %batch-loop {local (fn-%parse = $&parse --) $&batchloop $*} -# The first element of the $&parse primitive's return value is the input -# that it read in order to produce its parsed command. In addition, any -# error exceptions coming from $&parse include the input such that instead -# of the typical set of -# -# e type msg -# -# terms, those exceptions contain -# -# e type input msg + +# If run with the -i flag as the first argument, then the $&parse +# primitive includes its input in its output. Upon normal return, the +# input is made the first element of the return value, while upon any +# exception, instead of the typical (e type msg), $&parse throws an +# exception of the form (e type input msg). (To avoid ambiguity, +# exceptions like eof, which do not specify a type, do not include their +# input either.) # -# Both of these are "consumed" by %parse, which only returns the parsed -# command and only throws the `e type msg' terms. +# In general it is expected that any caller of $&parse -i will "consume" +# these extra values itself and present the "normal" $&parse output, as is +# done in %interactive-parse here. -fn %parse { - catch @ e type msg { - if {~ $e error} { - if {%is-interactive && !~ $#fn-%write-history 0} { - %write-history $msg(1) - } - msg = $msg(2 ...) +fn %interactive-parse { + catch @ e type input msg { + if {!~ $#fn-%write-history 0} { + %write-history $input } throw $e $type $msg } { - let ((line code) = <={$&parse $*}) { - if {%is-interactive && !~ $#fn-%write-history 0} { + let ((line code) = <={$&parse -i $*}) { + if {!~ $#fn-%write-history 0} { %write-history $line } result $code @@ -683,6 +680,7 @@ if {~ <=$&primitives writehistory} { } fn %interactive-loop { + local (fn-%parse = $fn-%interactive-parse) let (result = <=true) { catch @ e type msg { if {~ $e eof} { diff --git a/input.c b/input.c index 3dc99396..76a35f36 100644 --- a/input.c +++ b/input.c @@ -26,6 +26,7 @@ Input *input; char *prompt, *prompt2; Boolean ignoreeof = FALSE; +Boolean buildhistbuffer = FALSE; Boolean resetterminal = FALSE; #if HAVE_READLINE @@ -295,12 +296,13 @@ 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(Boolean hist, char *pr1, char *pr2) { int result; assert(error == NULL); inityy(); emptyherequeue(); + buildhistbuffer = hist; if (ISEOF(input)) throw(mklist(mkstr("eof"), NULL)); @@ -475,7 +477,7 @@ extern Tree *parseinput(Input *in) { input = in; ExceptionHandler - result = parse(NULL, NULL); + result = parse(FALSE, NULL, NULL); if (get(in) != EOF) fail("$&parse", "more than one value in term"); CatchException (e) diff --git a/prim-etc.c b/prim-etc.c index 4c94a771..22cee93b 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -149,6 +149,7 @@ PRIM(var) { } PRIM(parse) { + Boolean hist = FALSE; Tree *tree = NULL; Ref(List *, result, NULL); @@ -156,22 +157,39 @@ PRIM(parse) { Ref(char *, prompt2, NULL); Ref(List *, lp, list); if (lp != NULL) { - prompt1 = getstr(lp->term); - if ((lp = lp->next) != NULL) + char *first = getstr(lp->term); + if (streq(first, "--")) { + first = NULL; + lp = lp->next; + } else if (!hist && streq(first, "-i")) { + first = NULL; + hist = TRUE; + lp = lp->next; + } + prompt1 = (first != NULL) + ? first + : (lp != NULL) ? getstr(lp->term) : NULL; + if (lp != NULL && (lp = lp->next) != NULL) prompt2 = getstr(lp->term); } - newhistbuffer(); RefEnd(lp); + if (hist) + newhistbuffer(); ExceptionHandler - tree = parse(prompt1, prompt2); + tree = parse(hist, prompt1, prompt2); CatchException (e) - char *h = dumphistbuffer(); + char *h; + + if (!hist) + throw(e); + + h = dumphistbuffer(); - if (termeq(e->term, "error")) { + if (e != NULL && e->next != NULL) { gcdisable(); e = mklist(e->term, mklist(e->next->term, @@ -188,7 +206,8 @@ PRIM(parse) { if (tree != NULL) result = mklist(mkterm(NULL, mkclosure(mk(nThunk, tree), NULL)), NULL); - result = mklist(mkstr(dumphistbuffer()), result); + if (hist) + result = mklist(mkstr(dumphistbuffer()), result); gcenable(); RefEnd2(prompt2, prompt1); @@ -210,23 +229,9 @@ PRIM(batchloop) { for (;;) { List *parser, *cmd; parser = varlookup("fn-%parse", NULL); - if (parser == NULL) { - ExceptionHandler - cmd = prim("parse", NULL, NULL, 0); - cmd = cmd->next; - CatchException(e) - if (termeq(e->term, "error")) { - gcdisable(); - e = mklist(e->term, - mklist(e->next->term, - e->next->next->next)); - gcenable(); - } - throw(e); - EndExceptionHandler - - } else - cmd = eval(parser, NULL, 0); + cmd = (parser == NULL) + ? prim("parse", NULL, NULL, 0) + : eval(parser, NULL, 0); SIGCHK(); dispatch = varlookup("fn-%dispatch", NULL); if (cmd != NULL) { From c2051270160d5ffbdc362c9ed22f95ba652d5f13 Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 9 Nov 2024 09:39:51 -0800 Subject: [PATCH 10/16] Clean up hist variable a bit --- prim-etc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prim-etc.c b/prim-etc.c index 22cee93b..53a21fa2 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -149,7 +149,7 @@ PRIM(var) { } PRIM(parse) { - Boolean hist = FALSE; + volatile Boolean hist = FALSE; Tree *tree = NULL; Ref(List *, result, NULL); @@ -161,7 +161,7 @@ PRIM(parse) { if (streq(first, "--")) { first = NULL; lp = lp->next; - } else if (!hist && streq(first, "-i")) { + } else if (streq(first, "-i")) { first = NULL; hist = TRUE; lp = lp->next; From 0a7114d344d6ee615be1991fae50260216087fb6 Mon Sep 17 00:00:00 2001 From: jpco Date: Tue, 21 Jan 2025 12:09:45 -0800 Subject: [PATCH 11/16] Call %write-history from $&parse itself --- es.h | 3 +- initial.es | 44 +++--------------------------- input.c | 11 +++++--- prim-etc.c | 80 ++++++++++++++++++++---------------------------------- 4 files changed, 43 insertions(+), 95 deletions(-) diff --git a/es.h b/es.h index 5b6d3f64..77b2df44 100644 --- a/es.h +++ b/es.h @@ -280,9 +280,10 @@ extern Boolean streq2(const char *s, const char *t1, const char *t2); /* input.c */ extern char *prompt, *prompt2; -extern Tree *parse(Boolean hist, char *esprompt1, char *esprompt2); +extern Tree *parse(char *esprompt1, char *esprompt2); extern Tree *parsestring(const char *str); extern Boolean isinteractive(void); +extern Boolean isfromfd(void); #if HAVE_READLINE #if ABUSED_GETENV extern void initgetenv(void); diff --git a/initial.es b/initial.es index ae27b851..26765471 100644 --- a/initial.es +++ b/initial.es @@ -626,52 +626,17 @@ if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} # result gets set to zero when it should not be. fn-%is-interactive = $&isinteractive -fn-%parse = $&parse -- +fn-%parse = $&parse +fn-%batch-loop = $&batchloop -fn %batch-loop {local (fn-%parse = $&parse --) $&batchloop $*} - - -# If run with the -i flag as the first argument, then the $&parse -# primitive includes its input in its output. Upon normal return, the -# input is made the first element of the return value, while upon any -# exception, instead of the typical (e type msg), $&parse throws an -# exception of the form (e type input msg). (To avoid ambiguity, -# exceptions like eof, which do not specify a type, do not include their -# input either.) -# -# In general it is expected that any caller of $&parse -i will "consume" -# these extra values itself and present the "normal" $&parse output, as is -# done in %interactive-parse here. - -fn %interactive-parse { - catch @ e type input msg { - if {!~ $#fn-%write-history 0} { - %write-history $input - } - throw $e $type $msg - } { - let ((line code) = <={$&parse -i $*}) { - if {!~ $#fn-%write-history 0} { - %write-history $line - } - result $code - } - } -} if {~ <=$&primitives writehistory} { - fn %write-history input { - if {!~ $input ''} { - $&writehistory $input - } - } + fn-%write-history = $&writehistory } { fn %write-history input { if {!~ $history ()} { if {access -w $history} { - if {!~ $input () && !~ $input ''} { - echo $input >> $history - } + echo $input >> $history } { history = () } @@ -680,7 +645,6 @@ if {~ <=$&primitives writehistory} { } fn %interactive-loop { - local (fn-%parse = $fn-%interactive-parse) let (result = <=true) { catch @ e type msg { if {~ $e eof} { diff --git a/input.c b/input.c index 76a35f36..89fd77fa 100644 --- a/input.c +++ b/input.c @@ -26,7 +26,6 @@ Input *input; char *prompt, *prompt2; Boolean ignoreeof = FALSE; -Boolean buildhistbuffer = FALSE; Boolean resetterminal = FALSE; #if HAVE_READLINE @@ -296,13 +295,12 @@ static int fdfill(Input *in) { */ /* parse -- call yyparse(), but disable garbage collection and catch errors */ -extern Tree *parse(Boolean hist, char *pr1, char *pr2) { +extern Tree *parse(char *pr1, char *pr2) { int result; assert(error == NULL); inityy(); emptyherequeue(); - buildhistbuffer = hist; if (ISEOF(input)) throw(mklist(mkstr("eof"), NULL)); @@ -477,7 +475,7 @@ extern Tree *parseinput(Input *in) { input = in; ExceptionHandler - result = parse(FALSE, NULL, NULL); + result = parse(NULL, NULL); if (get(in) != EOF) fail("$&parse", "more than one value in term"); CatchException (e) @@ -524,6 +522,11 @@ extern Boolean isinteractive(void) { return input == NULL ? FALSE : ((input->runflags & run_interactive) != 0); } +/* isfromfd -- is the innermost input source reading from a file descriptor? */ +extern Boolean isfromfd(void) { + return input == NULL ? FALSE : (input->fill == fdfill); +} + /* * readline integration. diff --git a/prim-etc.c b/prim-etc.c index 53a21fa2..4f7cce90 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -148,70 +148,50 @@ PRIM(var) { return list; } -PRIM(parse) { - volatile Boolean hist = FALSE; - Tree *tree = NULL; +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: + Ref(List *, list, append(fn, mklist(mkstr(input), NULL))); + eval(list, NULL, 0); + RefEnd(list); +} - Ref(List *, result, NULL); +PRIM(parse) { + List *result; + Tree *tree; Ref(char *, prompt1, NULL); Ref(char *, prompt2, NULL); Ref(List *, lp, list); if (lp != NULL) { - char *first = getstr(lp->term); - if (streq(first, "--")) { - first = NULL; - lp = lp->next; - } else if (streq(first, "-i")) { - first = NULL; - hist = TRUE; - lp = lp->next; - } - prompt1 = (first != NULL) - ? first - : (lp != NULL) ? getstr(lp->term) : NULL; - if (lp != NULL && (lp = lp->next) != NULL) + prompt1 = getstr(lp->term); + if ((lp = lp->next) != NULL) prompt2 = getstr(lp->term); } RefEnd(lp); - if (hist) - newhistbuffer(); - + newhistbuffer(); ExceptionHandler - - tree = parse(hist, prompt1, prompt2); - + tree = parse(prompt1, prompt2); CatchException (e) - - char *h; - - if (!hist) - throw(e); - - h = dumphistbuffer(); - - if (e != NULL && e->next != NULL) { - gcdisable(); - e = mklist(e->term, - mklist(e->next->term, - mklist(mkstr(h), - e->next->next))); - gcenable(); - } - + loginput(dumphistbuffer()); throw(e); - EndExceptionHandler - gcdisable(); - if (tree != NULL) - result = mklist(mkterm(NULL, mkclosure(mk(nThunk, tree), NULL)), NULL); - - if (hist) - result = mklist(mkstr(dumphistbuffer()), result); - gcenable(); - + loginput(dumphistbuffer()); + result = (tree == NULL) + ? NULL + : mklist(mkterm(NULL, mkclosure(mk(nThunk, tree), NULL)), + NULL); RefEnd2(prompt2, prompt1); - RefReturn(result); + return result; } PRIM(exitonfalse) { From 2cddd685f02f1c5165c9b7d8cf9afdab016fafe4 Mon Sep 17 00:00:00 2001 From: jpco Date: Tue, 21 Jan 2025 16:15:47 -0800 Subject: [PATCH 12/16] Fix GCed data problems --- prim-etc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prim-etc.c b/prim-etc.c index 4f7cce90..72d81db4 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -160,7 +160,9 @@ static void loginput(char *input) { default: goto writeit; } writeit: + gcdisable(); Ref(List *, list, append(fn, mklist(mkstr(input), NULL))); + gcenable(); eval(list, NULL, 0); RefEnd(list); } From 72cb42cab07a353aa275f1c23a6e55141fbed811 Mon Sep 17 00:00:00 2001 From: jpco Date: Tue, 21 Jan 2025 16:17:52 -0800 Subject: [PATCH 13/16] Fix man page --- doc/es.1 | 8 -------- 1 file changed, 8 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index 33bbdfdf..748f484c 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2711,14 +2711,6 @@ The primitive is used to implement the .Cr %parse hook function. -If given the -.Cr -i -flag, it returns the input it collected as the first element of its return -value, and includes the input in exceptions that it throws. -The -.Cr -- -flag can be used to prevent the interpretation of -.Cr -i . .PP The .Cr background From 4bfb0bc02102d554d561f5aa242ce3d4924de471 Mon Sep 17 00:00:00 2001 From: jpco Date: Tue, 21 Jan 2025 16:36:41 -0800 Subject: [PATCH 14/16] initial.es cleanup --- initial.es | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/initial.es b/initial.es index 7ecad927..a813803f 100644 --- a/initial.es +++ b/initial.es @@ -579,6 +579,28 @@ fn %pathsearch name { access -n $name -1e -xf $path } if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} +# The %write-history hook is used in interactive contexts to write +# command input to the history file (and/or readline's in-memory +# history log). By default, $&writehistory (which is available if +# readline is compiled in) will write to readline's history log if +# $max-history-length allows, and will write to the file designated +# by $history if that variable is set and the file it points to +# exists and is writeable. + +if {~ <=$&primitives writehistory} { + fn-%write-history = $&writehistory +} { + fn %write-history input { + if {!~ $history ()} { + if {access -w $history} { + echo $input >> $history + } { + history = () + } + } + } +} + # # Read-eval-print loops @@ -625,24 +647,9 @@ if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} # The parsed code is executed only if it is non-empty, because otherwise # result gets set to zero when it should not be. -fn-%is-interactive = $&isinteractive fn-%parse = $&parse fn-%batch-loop = $&batchloop - - -if {~ <=$&primitives writehistory} { - fn-%write-history = $&writehistory -} { - fn %write-history input { - if {!~ $history ()} { - if {access -w $history} { - echo $input >> $history - } { - history = () - } - } - } -} +fn-%is-interactive = $&isinteractive fn %interactive-loop { let (result = <=true) { From e8095327ea5f40a96608ae9aa4e40b547b7a07ed Mon Sep 17 00:00:00 2001 From: jpco Date: Sun, 16 Feb 2025 10:30:19 -0800 Subject: [PATCH 15/16] man page updates for %write-history --- doc/es.1 | 68 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index 748f484c..53586062 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2559,6 +2559,22 @@ For each named returns the pathname, primitive, lambda, or code fragment which would be run if the program appeared as the first word of a command. .TP +.Cr "%write-history \fIinput\fP" +Called at the end of +.Cr %parse +to write the +.I input +to the file given in +.Cr $history , +if such a file exists and can be written. +Also appends the +.I input +to the in-memory history log if +.I readline +support is compiled in. +(For more on this, see +.Cr max-history-length .) +.TP .Cr "%writeto \fIvar output cmd\fP" Runs .I cmd @@ -2752,43 +2768,37 @@ writeto %writeto .ft R .De .PP -The primitives -.Cr resetterminal , -.Cr sethistory , -and -.Cr writehistory -are if +The following primitives in particular are included if .I es is compiled with support for the .I readline -library. -It is used in the implementation of settor functions of the +library: +.ta 2i +.Ds +.ft \*(Cf +resetterminal sethistory +setmaxhistorylength writehistory +.ft R +.De +.PP +.Cr sethistory +and +.Cr setmaxhistorylength +are used as settor functions for the +.Cr history +and +.Cr max-history-length +variables. +.Cr resetterminal +is used in the settor functions for the .Cr TERM and .Cr TERMCAP -variables to notify the line editing packages that the terminal -configuration has changed. -.Cr sethistory -is used in the -.Cr set-history -settor function to signal to line editing packages which history file is -being used. +variables. .Cr writehistory -is used in the +is used as the initial implementation of the .Cr %write-history -function to log a new line to the line editing packages' internal -command log, and write to the -.Cr history -file if that variable is set via the -.Cr sethistory -primitive. -.Cr setmaxhistorylength -is used as the settor function for the -.Cr max-history-length -variable, which signals to the -.I readline -library how much of the history file should be read into the in-memory -history log. +function. .PP Several primitives are not directly associated with other function. They are: From 8110e6712af99e798b43107316674e388324e88b Mon Sep 17 00:00:00 2001 From: jpco Date: Wed, 26 Mar 2025 10:16:06 -0700 Subject: [PATCH 16/16] Ref(tree), which otherwise might get mangled by a GC during %write-history --- prim-etc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/prim-etc.c b/prim-etc.c index 72d81db4..880a2016 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -169,7 +169,6 @@ static void loginput(char *input) { PRIM(parse) { List *result; - Tree *tree; Ref(char *, prompt1, NULL); Ref(char *, prompt2, NULL); Ref(List *, lp, list); @@ -180,6 +179,8 @@ PRIM(parse) { } RefEnd(lp); newhistbuffer(); + + Ref(Tree *, tree, NULL); ExceptionHandler tree = parse(prompt1, prompt2); CatchException (e) @@ -192,7 +193,7 @@ PRIM(parse) { ? NULL : mklist(mkterm(NULL, mkclosure(mk(nThunk, tree), NULL)), NULL); - RefEnd2(prompt2, prompt1); + RefEnd3(tree, prompt2, prompt1); return result; }