From d4e750d4e210f19228747fcb449b63ce3de0e2e3 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Wed, 31 May 2023 17:11:58 -0700 Subject: [PATCH 01/28] Experimental "put everything in es script" change. This moves the bulk of the interesting stuff in main() to es:main. It also defines %dot and %batch-loop in es script. It does this by defining a new variable $runflags which operates like a modifiable version of $- in other shells. --- Makefile.in | 5 +- dump.c | 7 +- es.h | 18 +-- initial.es | 107 ++---------------- input.c | 190 +++++++++++++++---------------- main.c | 139 +++++------------------ prim-dump.c | 36 ++++++ prim-etc.c | 89 +++------------ prim-io.c | 11 ++ prim.c | 4 + prim.h | 1 + runtime.es | 320 ++++++++++++++++++++++++++++++++++++++++++++++++++++ signal.c | 11 +- 13 files changed, 540 insertions(+), 398 deletions(-) create mode 100644 prim-dump.c create mode 100644 runtime.es diff --git a/Makefile.in b/Makefile.in index 14b23c53..929ce775 100644 --- a/Makefile.in +++ b/Makefile.in @@ -53,12 +53,12 @@ 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 \ - prim-ctl.c prim-etc.c prim-io.c prim-sys.c prim.c print.c proc.c \ + prim-ctl.c prim-etc.c prim-io.c prim-sys.c prim-dump.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 \ - prim-ctl.o prim-etc.o prim-io.o prim-sys.o prim.o print.o proc.o \ + prim-ctl.o prim-etc.o prim-io.o prim-sys.o prim-dump.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 @@ -136,6 +136,7 @@ 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 prim-io.o : prim-io.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 +prim-dump.o : prim-dump.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 signal.o : signal.c es.h config.h stdenv.h sigmsgs.h diff --git a/dump.c b/dump.c index 5d388421..029dbe7f 100644 --- a/dump.c +++ b/dump.c @@ -3,6 +3,7 @@ #include "es.h" #include "var.h" #include "term.h" +#include "prim.h" #define MAXVARNAME 20 @@ -247,8 +248,10 @@ static void printheader(List *title) { } extern void runinitial(void) { - List *title = runfd(0, "initial.es", 0); - + initdumpprims(); + + List *title = runfd(0, "initial.es", mklist(mkstr("$&batchloop"), NULL)); + gcdisable(); cvars = mkdict(); diff --git a/es.h b/es.h index d6719b54..da245ae7 100644 --- a/es.h +++ b/es.h @@ -286,15 +286,16 @@ extern Boolean isinteractive(void); extern void initinput(void); extern void resetparser(void); -extern List *runfd(int fd, const char *name, int flags); -extern List *runstring(const char *str, const char *name, int flags); +extern void setrunflags(int flags); +extern List *runflags_from_int(int); +extern int runflags_to_int(List *); +extern List *runinput(char *name, List *cmd); +extern List *runfd(int fd, const char *name, List *cmd); /* eval_* flags are also understood as runflags */ -#define run_interactive 4 /* -i or $0[0] = '-' */ -#define run_noexec 8 /* -n */ -#define run_echoinput 16 /* -v */ -#define run_printcmds 32 /* -x */ -#define run_lisptrees 64 /* -L and defined(LISPTREES) */ +#define run_interactive 4 /* -i or $0[0] = '-' */ +#define run_echoinput 8 /* -v */ +#define run_lisptrees 16 /* -L and defined(LISPTREES) */ #if READLINE extern Boolean resetterminal; @@ -313,6 +314,7 @@ extern List *esoptend(void); extern List *prim(char *s, List *list, Binding *binding, int evalflags); extern void initprims(void); +extern void initdumpprims(void); /* split.c */ @@ -337,7 +339,7 @@ extern Sigeffect esignal(int sig, Sigeffect effect); extern void setsigeffects(const Sigeffect effects[]); extern void getsigeffects(Sigeffect effects[]); extern List *mksiglist(void); -extern void initsignals(Boolean interactive, Boolean allowdumps); +extern void initsignals(); extern Atomic slow, interrupted; extern jmp_buf slowlabel; extern Boolean sigint_newline; diff --git a/initial.es b/initial.es index af775b55..68f9285e 100644 --- a/initial.es +++ b/initial.es @@ -63,7 +63,6 @@ # These builtin functions are straightforward calls to primitives. # See the manual page for details on what they do. -fn-. = $&dot fn-access = $&access fn-break = $&break fn-catch = $&catch @@ -580,100 +579,6 @@ fn %pathsearch name { access -n $name -1e -xf $path } if {~ <=$&primitives execfailure} {fn-%exec-failure = $&execfailure} -# -# Read-eval-print loops -# - -# In es, the main read-eval-print loop (REPL) can lie outside the -# shell itself. Es can be run in one of two modes, interactive or -# batch, and there is a hook function for each form. It is the -# responsibility of the REPL to call the parser for reading commands, -# hand those commands to an appropriate dispatch function, and handle -# any exceptions that may be raised. The function %is-interactive -# can be used to determine whether the most closely binding REPL is -# interactive or batch. -# -# The REPLs are invoked by the shell's main() routine or the . or -# eval builtins. If the -i flag is used or the shell determines that -# it's input is interactive, %interactive-loop is invoked; otherwise -# %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. -# -# By convention, the REPL must pass commands to the fn %dispatch, -# which has the actual responsibility for executing the command. -# Whatever routine invokes the REPL (internal, for now) has -# the responsibility of setting up fn %dispatch appropriately; -# it is used for implementing the -e, -n, and -x options. -# Typically, fn %dispatch is locally bound. -# -# 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. -# Other than eof, %interactive-loop does not exit on exceptions, -# where %batch-loop does. -# -# The looping construct forever is used rather than while, because -# while catches the break exception, which would make it difficult -# to print ``break outside of loop'' errors. -# -# 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 %interactive-loop { - let (result = <=true) { - catch @ e type msg { - if {~ $e eof} { - return $result - } {~ $e exit} { - throw $e $type $msg - } {~ $e error} { - echo >[1=2] $msg - $fn-%dispatch false - } {~ $e signal} { - if {!~ $type sigint sigterm sigquit} { - echo >[1=2] caught unexpected signal: $type - } - } { - echo >[1=2] uncaught exception: $e $type $msg - } - throw retry # restart forever loop - } { - forever { - if {!~ $#fn-%prompt 0} { - %prompt - } - let (code = <={%parse $prompt}) { - if {!~ $#code 0} { - result = <={$fn-%dispatch $code} - } - } - } - } - } -} - -# 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.) - -fn %eval-noprint # -fn %eval-print { echo $* >[1=2]; $* } # -x -fn %noeval-noprint { } # -n -fn %noeval-print { echo $* >[1=2] } # -n -x -fn-%exit-on-false = $&exitonfalse # -e - - # # Settor functions # @@ -744,8 +649,18 @@ max-eval-depth = 640 # is does. fn-%dispatch is really only important to the current # interpreter loop. -noexport = noexport pid signals apid bqstatus fn-%dispatch path home matchexpr +noexport = noexport pid signals apid bqstatus path home matchexpr + +fn %batch-loop { + forever { + <=$&parse + } +} + +fn-. = $&runinput %batch-loop +# source the runtime init script. +. ./runtime.es # # Title diff --git a/input.c b/input.c index d6ca931e..ed01189e 100644 --- a/input.c +++ b/input.c @@ -77,6 +77,50 @@ static void warn(char *s) { eprint("warning: %s\n", locate(input, s)); } +#define NRUNFLAGS 4 +static struct{ + int mask; + char *name; +} flagarr[NRUNFLAGS] = { + {eval_exitonfalse, "exitonfalse"}, + {run_interactive, "interactive"}, + {run_echoinput, "echoinput"}, + {run_lisptrees, "lisptrees"} +}; + +extern List *runflags_from_int(int flags) { + int len = 0; + char *flagstrs[NRUNFLAGS]; + + int i; + for (i = 0; i < NRUNFLAGS; i++) { + if (flags & flagarr[i].mask) { + flagstrs[len++] = flagarr[i].name; + } + } + + return listify(len, flagstrs); +} + +extern int runflags_to_int(List *list) { + char *s; + int flags = 0; + Ref(List *, lp, list); + for (; lp != NULL; lp = lp->next) { + s = getstr(lp->term); + if (streq(s, "interactive")) + flags |= run_interactive; + else if (streq(s, "echoinput")) + flags |= run_echoinput; +#if LISPTREES + else if (streq(s, "lisptrees")) + flags |= run_lisptrees; +#endif + } + RefEnd(lp); + return flags; +} + /* * history @@ -344,6 +388,13 @@ static int fdfill(Input *in) { return *in->buf++; } +extern void setrunflags(int flags) { + if (input != NULL) { + input->runflags = flags; + input->get = (flags & run_echoinput) ? getverbose : get; + } +} + /* * the input loop @@ -392,63 +443,6 @@ extern void resetparser(void) { error = NULL; } -/* 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; - const char *dispatcher[] = { - "fn-%eval-noprint", - "fn-%eval-print", - "fn-%noeval-noprint", - "fn-%noeval-print", - }; - - flags &= ~eval_inchild; - in->runflags = flags; - in->get = (flags & run_echoinput) ? getverbose : get; - in->prev = input; - input = in; - - ExceptionHandler - - dispatch - = varlookup(dispatcher[((flags & run_printcmds) ? 1 : 0) - + ((flags & run_noexec) ? 2 : 0)], - NULL); - if (flags & eval_exitonfalse) - dispatch = mklist(mkstr("%exit-on-false"), dispatch); - varpush(&push, "fn-%dispatch", dispatch); - - repl = varlookup((flags & run_interactive) - ? "fn-%interactive-loop" - : "fn-%batch-loop", - NULL); - result = (repl == NULL) - ? prim("batchloop", NULL, NULL, flags) - : eval(repl, NULL, flags); - - varpop(&push); - - CatchException (e) - - (*input->cleanup)(input); - input = input->prev; - throw(e); - - EndExceptionHandler - - input = in->prev; - (*in->cleanup)(in); - return result; -} - - -/* - * pushing new input sources - */ - /* fdcleanup -- cleanup after running from a file descriptor */ static void fdcleanup(Input *in) { unregisterfd(&in->fd); @@ -457,8 +451,16 @@ static void fdcleanup(Input *in) { efree(in->bufbegin); } -/* runfd -- run commands from a file descriptor */ -extern List *runfd(int fd, const char *name, int flags) { +/* runinput -- run a command with a file as input */ +extern List *runinput(char *name, List *cmd) { + int fd = 0; + if (name != NULL && (fd = eopen(name, oOpen)) == -1) + fail("$&runinput", "%s: %s\n", name, esstrerror(errno)); + + return runfd(fd, name, cmd); +} + +extern List *runfd(int fd, const char *name, List *cmd) { Input in; List *result; @@ -467,19 +469,46 @@ extern List *runfd(int fd, const char *name, int flags) { in.fill = fdfill; in.cleanup = fdcleanup; in.fd = fd; + if (input != NULL) + in.runflags = input->runflags; + else + in.runflags = runflags_to_int(varlookup("runflags", NULL)); 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; + in.name = (name == NULL) ? "stdin" : name; - RefAdd(in.name); - result = runinput(&in, flags); - RefRemove(in.name); + int flags = in.runflags; + result = NULL; + + flags &= ~eval_inchild; + in.get = (flags & run_echoinput) ? getverbose : get; + in.prev = input; + input = ∈ + + ExceptionHandler + + result = eval(cmd, NULL, flags); + + CatchException (e) + + (*input->cleanup)(input); + input = input->prev; + throw(e); + + EndExceptionHandler + input = in.prev; + (*in.cleanup)(&in); return result; } + +/* + * pushing new input sources + */ + /* stringcleanup -- cleanup after running from a string */ static void stringcleanup(Input *in) { efree(in->bufbegin); @@ -491,32 +520,6 @@ static int stringfill(Input *in) { return EOF; } -/* runstring -- run commands from a string */ -extern List *runstring(const char *str, const char *name, 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; - - RefAdd(in.name); - result = runinput(&in, flags); - RefRemove(in.name); - return result; -} - /* parseinput -- turn an input source into a tree */ extern Tree *parseinput(Input *in) { Tree * volatile result = NULL; @@ -549,8 +552,6 @@ extern Tree *parsestring(const char *str) { assert(str != NULL); - /* TODO: abstract out common code with runstring */ - memzero(&in, sizeof (Input)); in.fd = -1; in.lineno = 1; @@ -569,11 +570,6 @@ extern Tree *parsestring(const char *str) { return result; } -/* isinteractive -- is the innermost input source interactive? */ -extern Boolean isinteractive(void) { - return input == NULL ? FALSE : ((input->runflags & run_interactive) != 0); -} - /* * initialization @@ -597,6 +593,6 @@ extern void initinput(void) { #if READLINE rl_basic_word_break_characters=" \t\n\\'`$><=;|&{()}"; - rl_completer_quote_characters="'"; + rl_completer_quote_characters="'"; #endif } diff --git a/main.c b/main.c index ab7ebd3a..f0b98755 100644 --- a/main.c +++ b/main.c @@ -20,21 +20,11 @@ extern char *optarg; extern char **environ; -/* checkfd -- open /dev/null on an fd if it is closed */ -static void checkfd(int fd, OpenKind r) { - int new; - new = dup(fd); - if (new != -1) - close(new); - else if (errno == EBADF && (new = eopen("/dev/null", r)) != -1) - mvfd(new, fd); -} - /* initpath -- set $path based on the configuration default */ static void initpath(void) { int i; static const char * const path[] = { INITIAL_PATH }; - + Ref(List *, list, NULL); for (i = arraysize(path); i-- > 0;) { Term *t = mkstr((char *) path[i]); @@ -49,27 +39,6 @@ static void initpid(void) { vardef("pid", NULL, mklist(mkstr(str("%d", getpid())), NULL)); } -/* runesrc -- run the user's profile, if it exists */ -static void runesrc(void) { - char *esrc = str("%L/.esrc", varlookup("home", NULL), "\001"); - int fd = eopen(esrc, oOpen); - if (fd != -1) { - ExceptionHandler - runfd(fd, esrc, 0); - CatchException (e) - if (termeq(e->term, "exit")) - exit(exitstatus(e->next)); - else if (termeq(e->term, "error")) - eprint("%L\n", - e->next == NULL ? NULL : e->next->next, - " "); - else if (!issilentsignal(e)) - eprint("uncaught exception: %L\n", e, " "); - return; - EndExceptionHandler - } -} - /* usage -- print usage message and die */ static noreturn usage(void) { eprint( @@ -102,113 +71,65 @@ static noreturn usage(void) { /* main -- initialize, parse command arguments, and start running */ int main(int argc, char **argv) { int c; - volatile int ac; - char **volatile av; - - volatile int runflags = 0; /* -[einvxL] */ - volatile Boolean protected = FALSE; /* -p */ - volatile Boolean allowquit = FALSE; /* -d */ - volatile Boolean cmd_stdin = FALSE; /* -s */ - volatile Boolean loginshell = FALSE; /* -l or $0[0] == '-' */ - Boolean keepclosed = FALSE; /* -o */ - const char *volatile cmd = NULL; /* -c */ + volatile Boolean protected = FALSE; initgc(); initconv(); - if (argc == 0) { - argc = 1; - argv = ealloc(2 * sizeof (char *)); - argv[0] = "es"; - argv[1] = NULL; - } - if (*argv[0] == '-') - loginshell = TRUE; - while ((c = getopt(argc, argv, "eilxvnpodsc:?GIL")) != EOF) switch (c) { - case 'c': cmd = optarg; break; - case 'e': runflags |= eval_exitonfalse; break; - case 'i': runflags |= run_interactive; break; - case 'n': runflags |= run_noexec; break; - case 'v': runflags |= run_echoinput; break; - case 'x': runflags |= run_printcmds; break; -#if LISPTREES - case 'L': runflags |= run_lisptrees; break; -#endif - case 'l': loginshell = TRUE; break; case 'p': protected = TRUE; break; - case 'o': keepclosed = TRUE; break; - case 'd': allowquit = TRUE; break; - case 's': cmd_stdin = TRUE; goto getopt_done; #if GCVERBOSE case 'G': gcverbose = TRUE; break; #endif #if GCINFO case 'I': gcinfo = TRUE; break; #endif + case 's': goto getopt_done; + case 'c': /* All the remaining cases are vestigial, while */ + case 'e': /* argument parsing is moved to es:main */ + case 'i': + case 'n': + case 'v': + case 'x': +#if LISPTREES + case 'L': +#endif + case 'l': + case 'o': + case 'd': break; default: usage(); } getopt_done: - if (cmd_stdin && cmd != NULL) { - eprint("es: -s and -c are incompatible\n"); - exit(1); - } - - if (!keepclosed) { - checkfd(0, oOpen); - checkfd(1, oCreate); - checkfd(2, oCreate); - } - - if ( - cmd == NULL - && (optind == argc || cmd_stdin) - && (runflags & run_interactive) == 0 - && isatty(0) - ) - runflags |= run_interactive; - - ac = argc; - av = argv; - ExceptionHandler roothandler = &_localhandler; /* unhygeinic */ initinput(); initprims(); initvars(); - + runinitial(); - + initpath(); initpid(); - initsignals(runflags & run_interactive, allowquit); + initsignals(); hidevariables(); initenv(environ, protected); - - if (loginshell) - runesrc(); - - if (cmd == NULL && !cmd_stdin && optind < ac) { - int fd; - char *file = av[optind++]; - if ((fd = eopen(file, oOpen)) == -1) { - eprint("%s: %s\n", file, esstrerror(errno)); - return 1; - } - vardef("*", NULL, listify(ac - optind, av + optind)); - vardef("0", NULL, mklist(mkstr(file), NULL)); - return exitstatus(runfd(fd, file, runflags)); + + Ref(List *, args, listify(argc, argv)); + + Ref(List *, esmain, varlookup("es:main", NULL)); + if (esmain == NULL) { + eprint("es:main not set\n"); + return 1; } - - vardef("*", NULL, listify(ac - optind, av + optind)); - vardef("0", NULL, mklist(mkstr(av[0]), NULL)); - if (cmd != NULL) - return exitstatus(runstring(cmd, NULL, runflags)); - return exitstatus(runfd(0, "stdin", runflags)); + + esmain = append(esmain, args); + return exitstatus(eval(esmain, NULL, 0)); + + RefEnd2(esmain, args); CatchException (e) diff --git a/prim-dump.c b/prim-dump.c new file mode 100644 index 00000000..ff2fe273 --- /dev/null +++ b/prim-dump.c @@ -0,0 +1,36 @@ +/* prim-etc.c -- miscellaneous primitives ($Revision: 1.2 $) */ + +#include "es.h" +#include "prim.h" + +PRIM(batchloop) { + Ref(List *, result, true); + + ExceptionHandler + + for (;;) { + List *cmd = prim("parse", NULL, NULL, 0); + result = eval(cmd, NULL, evalflags); + SIGCHK(); + } + + CatchException (e) + + if (!termeq(e->term, "eof")) + throw(e); + if (result == true) + result = true; + RefReturn(result); + + EndExceptionHandler +} + + +/* + * initialization + */ + +extern Dict *initprims_dump(Dict *primdict) { + X(batchloop); + return primdict; +} diff --git a/prim-etc.c b/prim-etc.c index 86da2efc..60fc0937 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -40,41 +40,23 @@ PRIM(exec) { return eval(list, NULL, evalflags | eval_inchild); } -PRIM(dot) { - int c, fd; - Push zero, star; - volatile int runflags = (evalflags & eval_inchild); - const char * const usage = ". [-einvx] file [arg ...]"; - - esoptbegin(list, "$&dot", usage); - while ((c = esopt("einvx")) != EOF) - switch (c) { - case 'e': runflags |= eval_exitonfalse; break; - case 'i': runflags |= run_interactive; break; - case 'n': runflags |= run_noexec; break; - case 'v': runflags |= run_echoinput; break; - case 'x': runflags |= run_printcmds; break; - } +PRIM(setrunflags) { + setrunflags(runflags_to_int(list)); + return list; +} +PRIM(runinput) { + if (list == NULL) + fail("$&runinput", "usage: $&runinput command [file]"); Ref(List *, result, NULL); - Ref(List *, lp, esoptend()); - if (lp == NULL) - fail("$&dot", "usage: %s", usage); + Ref(List *, cmd, mklist(list->term, NULL)); - Ref(char *, file, getstr(lp->term)); - lp = lp->next; - fd = eopen(file, oOpen); - if (fd == -1) - fail("$&dot", "%s: %s", file, esstrerror(errno)); + result = runinput((list->next == NULL + ? NULL + : getstr(list->next->term)), + cmd); - varpush(&star, "*", lp); - varpush(&zero, "0", mklist(mkstr(file), NULL)); - - result = runfd(fd, file, runflags); - - varpop(&zero); - varpop(&star); - RefEnd2(file, lp); + RefEnd(cmd); RefReturn(result); } @@ -183,42 +165,6 @@ PRIM(exitonfalse) { return eval(list, NULL, evalflags | eval_exitonfalse); } -PRIM(batchloop) { - Ref(List *, result, true); - Ref(List *, dispatch, NULL); - - SIGCHK(); - - ExceptionHandler - - for (;;) { - List *parser, *cmd; - parser = varlookup("fn-%parse", NULL); - cmd = (parser == NULL) - ? prim("parse", NULL, NULL, 0) - : eval(parser, NULL, 0); - SIGCHK(); - dispatch = varlookup("fn-%dispatch", NULL); - if (cmd != NULL) { - if (dispatch != NULL) - cmd = append(dispatch, cmd); - result = eval(cmd, NULL, evalflags); - SIGCHK(); - } - } - - CatchException (e) - - if (!termeq(e->term, "eof")) - throw(e); - RefEnd(dispatch); - if (result == true) - result = true; - RefReturn(result); - - EndExceptionHandler -} - PRIM(collect) { gc(); return true; @@ -242,10 +188,6 @@ PRIM(internals) { return listvars(TRUE); } -PRIM(isinteractive) { - return isinteractive() ? true : false; -} - PRIM(noreturn) { if (list == NULL) fail("$&noreturn", "usage: $&noreturn lambda args ..."); @@ -296,7 +238,8 @@ extern Dict *initprims_etc(Dict *primdict) { X(count); X(version); X(exec); - X(dot); + X(setrunflags); + X(runinput); X(flatten); X(whatis); X(sethistory); @@ -304,14 +247,12 @@ extern Dict *initprims_etc(Dict *primdict) { X(fsplit); X(var); X(parse); - X(batchloop); X(collect); X(home); X(setnoexport); X(vars); X(internals); X(result); - X(isinteractive); X(exitonfalse); X(noreturn); X(setmaxevaldepth); diff --git a/prim-io.c b/prim-io.c index d2061d26..4eadb3bd 100644 --- a/prim-io.c +++ b/prim-io.c @@ -419,6 +419,16 @@ PRIM(read) { } } +PRIM(isatty) { + int fd; + if (list == NULL) + fail("$&isatty", "usage: $&isatty fd"); + fd = getnumber(getstr(list->term)); + if (isatty(fd)) + return true; + return false; +} + extern Dict *initprims_io(Dict *primdict) { X(openfile); X(close); @@ -432,5 +442,6 @@ extern Dict *initprims_io(Dict *primdict) { X(writeto); #endif X(read); + X(isatty); return primdict; } diff --git a/prim.c b/prim.c index 8b098a27..8017eacf 100644 --- a/prim.c +++ b/prim.c @@ -23,6 +23,10 @@ PRIM(primitives) { return primlist; } +extern void initdumpprims(void) { + prims = initprims_dump(prims); +} + extern void initprims(void) { prims = mkdict(); globalroot(&prims); diff --git a/prim.h b/prim.h index 24ded497..3717a59e 100644 --- a/prim.h +++ b/prim.h @@ -13,5 +13,6 @@ 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_sys(Dict *primdict); /* prim-sys.c */ +extern Dict *initprims_dump(Dict *primdict); /* prim-dump.c */ extern Dict *initprims_proc(Dict *primdict); /* proc.c */ extern Dict *initprims_access(Dict *primdict); /* access.c */ diff --git a/runtime.es b/runtime.es new file mode 100644 index 00000000..5460ea37 --- /dev/null +++ b/runtime.es @@ -0,0 +1,320 @@ +# runtime.es -- set up initial interpreter runtime ($Revision: 1.1.1.1 $) + +# +# Read-eval-print loops +# + +# In es, the main read-eval-print loop (REPL) can lie outside the +# shell itself. Es can be run in one of two modes, interactive or +# batch, and there is a hook function for each form. It is the +# responsibility of the REPL to call the parser for reading commands, +# hand those commands to an appropriate dispatch function, and handle +# any exceptions that may be raised. The function %is-interactive +# can be used to determine whether the most closely binding REPL is +# interactive or batch. +# +# The REPLs are invoked by the shell's es:main function or the . or +# eval builtins. If the -i flag is used or the shell determines that +# it's input is interactive, %interactive-loop is invoked; otherwise +# %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. +# +# By convention, the REPL must pass commands to the fn %dispatch, +# which has the actual responsibility for executing the command. +# Whatever routine invokes the REPL (internal, for now) has +# the responsibility of setting up fn %dispatch appropriately; +# it is used for implementing the -e, -n, and -x options. +# Typically, fn %dispatch is locally bound. +# +# 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. +# Other than eof, %interactive-loop does not exit on exceptions, +# where %batch-loop does. +# +# The looping construct forever is used rather than while, because +# while catches the break exception, which would make it difficult +# to print ``break outside of loop'' errors. +# +# 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 { + let (result = <=true) { + catch @ e rest { + if {~ $e eof} { + return $result + } { + throw $e $rest + } + } { + forever { + let (cmd = <=%parse) { + if {!~ $#cmd 0} { + result = <={$fn-%dispatch $cmd(1)} + } + } + } + } + } +} + +fn %interactive-loop { + let (result = <=true) { + catch @ e type msg { + if {~ $e eof} { + return $result + } {~ $e exit} { + throw $e $type $msg + } {~ $e error} { + echo >[1=2] $msg + $fn-%dispatch false + } {~ $e signal} { + if {!~ $type sigint sigterm sigquit} { + echo >[1=2] caught unexpected signal: $type + } + } { + echo >[1=2] uncaught exception: $e $type $msg + } + throw retry # restart forever loop + } { + forever { + if {!~ $#fn-%prompt 0} { + %prompt + } + let (code = <={%parse $prompt}) { + if {!~ $#code 0} { + result = <={$fn-%dispatch $code} + } + } + } + } + } +} + +# +# Es entry points +# + +# Es execution is configured with the $runflags variable, which is +# similar to `$-` in POSIX shells. Unlike `$-`, the value of +# $runflags may be modified, and will (typically) modify the shell's +# behavior when changed. +# +# $runflags may contain arbitrary words, but only 'exitonfalse', +# 'interactive', 'noexec', 'echoinput', 'printcmds', and 'lisptrees' +# (if support is compiled in) do anything by default. +# +# In several of the following functions, we call '$fn-' instead +# of the usual ''. This is to prevent these infrastructural +# functions from mangling the expected value of '$0'. + +fn %is-interactive { + ~ $runflags interactive +} + +set-runflags = @ new { + let (nf = ()) { + for (flag = $new) { + if {!~ $nf $flag} {nf = $nf $flag} + } + let ( + dp-p = <={if {~ $nf printcmds} {result 'print'} {result 'noprint'}} + dp-e = <={if {~ $nf noexec} {result 'noeval'} {result 'eval'}} + dp-f = <={if {~ $nf exitonfalse} {result '%exit-on-false'} {result ()}} + ) fn-%dispatch = $dp-f $(fn-%^$(dp-e)^-^$(dp-p)) + $&setrunflags $nf + } +} + + +# 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.) + +fn %eval-noprint # +fn %eval-print { echo $* >[1=2]; $* } # -x +fn %noeval-noprint { } # -n +fn %noeval-print { echo $* >[1=2] } # -n -x +fn-%exit-on-false = $&exitonfalse # -e + +noexport = $noexport fn-%dispatch runflags + +# %run-input wraps the '$&runinput' primitive with a default command +# (which calls one of the REPL functions defined above). When called +# on its own, the function is a worse (but technically passable) +# version of %dot. + +fn %run-input file { + $&runinput { + if %is-interactive { + $fn-%interactive-loop + } { + $fn-%batch-loop + } + } $file +} + + +# %dot is the engine for running outside scripts in the current shell. +# It manages runflags based on args passed to it, sets $0 and $*, and +# calls `%run-input`. + +fn %dot args { + let ( + fn usage { + throw error %dot 'usage: . [-einvx] file [arg ...]' + } + flags = () + ) { + for (a = $args) { + if {!~ $a -*} { + break + } + args = $args(2 ...) + if {~ $a --} { + break + } + for (f = <={%fsplit '' <={~~ $a -*}}) { + if ( + {~ $f e} {flags = $flags exitonfalse} + {~ $f i} {flags = $flags interactive} + {~ $f n} {flags = $flags noexec} + {~ $f v} {flags = $flags echoinput} + {~ $f x} {flags = $flags printcmds} + {usage} + ) + } + } + if {~ $#args 0} {usage} + local ( + 0 = $args(1) + * = $args(2 ...) + runflags = $flags + ) $fn-%run-input $args(1) + } +} + +fn-. = %dot + + +# +# es:main is the entry point to the shell. It parses the binary's argv, +# initializes runflags, runs .esrc (if appropriate), and starts the correct +# run loop. +# + +es:main = @ argv { + es:main = () + + let ( + es = es + flags = () + cmd = () + keepclosed = false + stdin = false + allowdumps = false + ) { + if {!~ $#argv 0} { + (es argv) = $argv + } + if {~ $es -*} { + flags = $flags login + } + + for (a = $argv) { + if {!~ $a -*} { + break + } + argv = $argv(2 ...) + if {~ $a --} { + break + } + for (f = <={%fsplit '' <={~~ $a -*}}) { + if ( + {~ $f c} {(cmd argv) = $argv} + {~ $f e} {flags = $flags exitonfalse} + {~ $f i} {flags = $flags interactive} + {~ $f v} {flags = $flags echoinput} + {~ $f L} {flags = $flags lisptrees} + {~ $f x} {flags = $flags printcmds} + {~ $f n} {flags = $flags noexec} + {~ $f l} {flags = $flags login} + {~ $f o} {keepclosed = true} + {~ $f d} {allowdumps = true} + {~ $f s} {stdin = true; break} + ) + } + } + + if {!$keepclosed} { + if {!access /dev/stdin} {exec {< /dev/null}} + if {!access /dev/stdout} {exec {> /dev/null}} + if {!access /dev/stderr} {exec {>[2] /dev/null}} + } + + if {$stdin && !~ $cmd ()} { + echo >[1=2] 'es: -s and -c are incompatible' + exit 1 + } + + if {~ $cmd () && {$stdin || ~ $#argv 0} && $&isatty 0} { + flags = $flags interactive + } + runflags = $flags + + let (s = $signals) { + if {~ $runflags interactive || !~ $s sigint} { + s = $s .sigint + } + if {!$allowdumps} { + if {~ $runflags interactive} { + s = $s /sigterm + } + if {~ $runflags interactive || !~ $signals sigquit} { + s = $s /sigquit + } + } + signals = $s + } + + if {~ $runflags login} { + catch @ e type msg { + if {~ $e exit} { + throw $e $type $msg + } {~ $e error} { + echo >[1=2] $msg + } { + echo >[1=2] uncaught exception: $e $type $msg + } + } { + . ~/.esrc + } + } + + if {~ $cmd () && !$stdin && !~ $#argv 0} { + local ((0 *) = $argv) + $fn-%run-input $0 + } {!~ $cmd ()} { + if {!~ $#argv 0} { + local ((0 *) = $argv) + $fn-eval $cmd + } { + local ((0 *) = $es) + $fn-eval $cmd + } + } { + local ((0 *) = $es $argv) + $fn-%run-input + } + } +} diff --git a/signal.c b/signal.c index 527dd93c..11709bfc 100644 --- a/signal.c +++ b/signal.c @@ -156,7 +156,7 @@ extern void getsigeffects(Sigeffect effects[]) { * initialization */ -extern void initsignals(Boolean interactive, Boolean allowdumps) { +extern void initsignals() { int sig; Push settor; @@ -191,15 +191,6 @@ extern void initsignals(Boolean interactive, Boolean allowdumps) { ); } - if (interactive || sigeffect[SIGINT] == sig_default) - esignal(SIGINT, sig_special); - if (!allowdumps) { - if (interactive) - esignal(SIGTERM, sig_noop); - if (interactive || sigeffect[SIGQUIT] == sig_default) - esignal(SIGQUIT, sig_noop); - } - /* here's the end-run around set-signals */ varpush(&settor, "set-signals", NULL); vardef("signals", NULL, mksiglist()); From ea50881ef9af367171cb34dea781de1a49f7ca49 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Wed, 31 May 2023 18:01:25 -0700 Subject: [PATCH 02/28] Use fancy "match" syntax --- runtime.es | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/runtime.es b/runtime.es index 5460ea37..87ea0bde 100644 --- a/runtime.es +++ b/runtime.es @@ -185,13 +185,13 @@ fn %dot args { break } for (f = <={%fsplit '' <={~~ $a -*}}) { - if ( - {~ $f e} {flags = $flags exitonfalse} - {~ $f i} {flags = $flags interactive} - {~ $f n} {flags = $flags noexec} - {~ $f v} {flags = $flags echoinput} - {~ $f x} {flags = $flags printcmds} - {usage} + match $f ( + e {flags = $flags exitonfalse} + i {flags = $flags interactive} + n {flags = $flags noexec} + v {flags = $flags echoinput} + x {flags = $flags printcmds} + * {usage} ) } } @@ -240,18 +240,18 @@ es:main = @ argv { break } for (f = <={%fsplit '' <={~~ $a -*}}) { - if ( - {~ $f c} {(cmd argv) = $argv} - {~ $f e} {flags = $flags exitonfalse} - {~ $f i} {flags = $flags interactive} - {~ $f v} {flags = $flags echoinput} - {~ $f L} {flags = $flags lisptrees} - {~ $f x} {flags = $flags printcmds} - {~ $f n} {flags = $flags noexec} - {~ $f l} {flags = $flags login} - {~ $f o} {keepclosed = true} - {~ $f d} {allowdumps = true} - {~ $f s} {stdin = true; break} + match $f ( + c {(cmd argv) = $argv} + e {flags = $flags exitonfalse} + i {flags = $flags interactive} + v {flags = $flags echoinput} + L {flags = $flags lisptrees} + x {flags = $flags printcmds} + n {flags = $flags noexec} + l {flags = $flags login} + o {keepclosed = true} + d {allowdumps = true} + s {stdin = true; break} ) } } From c38d1a58a7192b24cf8fecf8b58266d4a71bf431 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Wed, 31 May 2023 18:35:05 -0700 Subject: [PATCH 03/28] move -p out of main as well --- es.h | 2 +- main.c | 6 ++---- prim-etc.c | 8 ++++++++ runtime.es | 6 ++++++ var.c | 7 +++---- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/es.h b/es.h index da245ae7..3fba7452 100644 --- a/es.h +++ b/es.h @@ -187,7 +187,7 @@ extern List *extractmatches(List *subjects, List *patterns, StrList *quotes); /* var.c */ extern void initvars(void); -extern void initenv(char **envp, Boolean protected); +extern void importenv(char **envp, Boolean funcs); extern void hidevariables(void); extern void validatevar(const char *var); extern List *varlookup(const char *name, Binding *binding); diff --git a/main.c b/main.c index f0b98755..fea6d8d7 100644 --- a/main.c +++ b/main.c @@ -16,7 +16,6 @@ Boolean gcinfo = FALSE; /* -I */ extern int optind; extern char *optarg; -/* extern int isatty(int fd); */ extern char **environ; @@ -71,14 +70,12 @@ static noreturn usage(void) { /* main -- initialize, parse command arguments, and start running */ int main(int argc, char **argv) { int c; - volatile Boolean protected = FALSE; initgc(); initconv(); while ((c = getopt(argc, argv, "eilxvnpodsc:?GIL")) != EOF) switch (c) { - case 'p': protected = TRUE; break; #if GCVERBOSE case 'G': gcverbose = TRUE; break; #endif @@ -88,6 +85,7 @@ int main(int argc, char **argv) { case 's': goto getopt_done; case 'c': /* All the remaining cases are vestigial, while */ case 'e': /* argument parsing is moved to es:main */ + case 'p': case 'i': case 'n': case 'v': @@ -116,7 +114,7 @@ int main(int argc, char **argv) { initpid(); initsignals(); hidevariables(); - initenv(environ, protected); + importenv(environ, FALSE); Ref(List *, args, listify(argc, argv)); diff --git a/prim-etc.c b/prim-etc.c index 60fc0937..15d03d0b 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -221,6 +221,13 @@ PRIM(setmaxevaldepth) { RefReturn(lp); } +extern char **environ; + +PRIM(importenvfuncs) { + importenv(environ, TRUE); + return true; +} + #if READLINE PRIM(resetterminal) { resetterminal = TRUE; @@ -256,6 +263,7 @@ extern Dict *initprims_etc(Dict *primdict) { X(exitonfalse); X(noreturn); X(setmaxevaldepth); + X(importenvfuncs); #if READLINE X(resetterminal); #endif diff --git a/runtime.es b/runtime.es index 87ea0bde..c9c84b4e 100644 --- a/runtime.es +++ b/runtime.es @@ -223,6 +223,7 @@ es:main = @ argv { keepclosed = false stdin = false allowdumps = false + protected = false ) { if {!~ $#argv 0} { (es argv) = $argv @@ -251,6 +252,7 @@ es:main = @ argv { l {flags = $flags login} o {keepclosed = true} d {allowdumps = true} + p {protected = true} s {stdin = true; break} ) } @@ -287,6 +289,10 @@ es:main = @ argv { signals = $s } + if {!$protected} { + $&importenvfuncs + } + if {~ $runflags login} { catch @ e type msg { if {~ $e exit} { diff --git a/var.c b/var.c index 9e4feb3b..fd2655e1 100644 --- a/var.c +++ b/var.c @@ -409,8 +409,8 @@ static void importvar(char *name0, char *value) { } -/* initenv -- load variables from the environment */ -extern void initenv(char **envp, Boolean protected) { +/* importenv -- load variables from the environment */ +extern void importenv(char **envp, Boolean funcs) { char *envstr; size_t bufsize = 1024; char *buf = ealloc(bufsize); @@ -435,8 +435,7 @@ extern void initenv(char **envp, Boolean protected) { memcpy(buf, envstr, nlen); buf[nlen] = '\0'; name = str(ENV_DECODE, buf); - if (!protected - || (!hasprefix(name, "fn-") && !hasprefix(name, "set-"))) + if (funcs == (hasprefix(name, "fn-") || hasprefix(name, "set-"))) importvar(name, eq); } From 96617cf318d19265707e8fd961c5f06573b6f167 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Wed, 31 May 2023 19:01:24 -0700 Subject: [PATCH 04/28] Move GC flag parsing out of main() --- Makefile.in | 2 +- main.c | 65 ----------------------------------------------------- prim-etc.c | 26 +++++++++++++++++++++ runtime.es | 22 ++++++++++++++++++ 4 files changed, 49 insertions(+), 66 deletions(-) diff --git a/Makefile.in b/Makefile.in index 929ce775..6c2f0f99 100644 --- a/Makefile.in +++ b/Makefile.in @@ -102,7 +102,7 @@ y.tab.c y.tab.h : parse.y token.h : y.tab.h -cmp -s y.tab.h token.h || cp y.tab.h token.h -initial.c : esdump $(srcdir)/initial.es +initial.c : esdump $(srcdir)/initial.es $(srcdir)/runtime.es ./esdump < $(srcdir)/initial.es > initial.c sigmsgs.c : mksignal $(SIGFILES) diff --git a/main.c b/main.c index fea6d8d7..97d2c183 100644 --- a/main.c +++ b/main.c @@ -2,13 +2,6 @@ #include "es.h" -#if GCVERBOSE -Boolean gcverbose = FALSE; /* -G */ -#endif -#if GCINFO -Boolean gcinfo = FALSE; /* -I */ -#endif - /* #if 0 && !HPUX && !defined(linux) && !defined(sgi) */ /* extern int getopt (int argc, char **argv, const char *optstring); */ /* #endif */ @@ -38,69 +31,11 @@ static void initpid(void) { vardef("pid", NULL, mklist(mkstr(str("%d", getpid())), NULL)); } -/* usage -- print usage message and die */ -static noreturn usage(void) { - eprint( - "usage: es [-c command] [-silevxnpo] [file [args ...]]\n" - " -c cmd execute argument\n" - " -s read commands from standard input; stop option parsing\n" - " -i interactive shell\n" - " -l login shell\n" - " -e exit if any command exits with false status\n" - " -v print input to standard error\n" - " -x print commands to standard error before executing\n" - " -n just parse; don't execute\n" - " -p don't load functions from the environment\n" - " -o don't open stdin, stdout, and stderr if they were closed\n" - " -d don't ignore SIGQUIT or SIGTERM\n" -#if GCINFO - " -I print garbage collector information\n" -#endif -#if GCVERBOSE - " -G print verbose garbage collector information\n" -#endif -#if LISPTREES - " -L print parser results in LISP format\n" -#endif - ); - exit(1); -} - - /* main -- initialize, parse command arguments, and start running */ int main(int argc, char **argv) { - int c; - initgc(); initconv(); - while ((c = getopt(argc, argv, "eilxvnpodsc:?GIL")) != EOF) - switch (c) { -#if GCVERBOSE - case 'G': gcverbose = TRUE; break; -#endif -#if GCINFO - case 'I': gcinfo = TRUE; break; -#endif - case 's': goto getopt_done; - case 'c': /* All the remaining cases are vestigial, while */ - case 'e': /* argument parsing is moved to es:main */ - case 'p': - case 'i': - case 'n': - case 'v': - case 'x': -#if LISPTREES - case 'L': -#endif - case 'l': - case 'o': - case 'd': break; - default: - usage(); - } - -getopt_done: ExceptionHandler roothandler = &_localhandler; /* unhygeinic */ diff --git a/prim-etc.c b/prim-etc.c index 15d03d0b..5e931d45 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -40,7 +40,33 @@ PRIM(exec) { return eval(list, NULL, evalflags | eval_inchild); } +#if GCVERBOSE +Boolean gcverbose = FALSE; +#endif +#if GCINFO +Boolean gcinfo = FALSE; +#endif + PRIM(setrunflags) { +#if GCVERBOSE || GCINFO + Boolean gcv = FALSE; + Boolean gci = FALSE; + Ref(List *, lp, list); + for (lp = list; lp != NULL; lp = lp->next) { + if (termeq(lp->term, "gcverbose")) + gcv = TRUE; + else if (termeq(lp->term, "gcinfo")) + gci = TRUE; + } + RefEnd(lp); + +#if GCVERBOSE + gcverbose = gcv; +#endif +#if GCINFO + gcinfo = gci; +#endif +#endif setrunflags(runflags_to_int(list)); return list; } diff --git a/runtime.es b/runtime.es index c9c84b4e..04e8d0c0 100644 --- a/runtime.es +++ b/runtime.es @@ -224,6 +224,25 @@ es:main = @ argv { stdin = false allowdumps = false protected = false + + fn-usage = { +echo 'usage: es [-c command] [-silevxnpo] [file [args ...]]' +echo ' -c cmd execute argument' +echo ' -s read commands from standard input; stop option parsing' +echo ' -i interactive shell' +echo ' -l login shell' +echo ' -e exit if any command exits with false status' +echo ' -v print input to standard error' +echo ' -x print commands to standard error before executing' +echo ' -n just parse; don''t execute' +echo ' -p don''t load functions from the environment' +echo ' -o don''t open stdin, stdout, and stderr if they were closed' +echo ' -d don''t ignore SIGQUIT or SIGTERM' +echo ' -I print garbage collector information (if compiled in)' +echo ' -G print verbose garbage collector information (if compiled in)' +echo ' -L print parser results in LISP format (if compiled in)' +exit 1 +} ) { if {!~ $#argv 0} { (es argv) = $argv @@ -250,10 +269,13 @@ es:main = @ argv { x {flags = $flags printcmds} n {flags = $flags noexec} l {flags = $flags login} + G {flags = $flags gcverbose} + I {flags = $flags gcinfo} o {keepclosed = true} d {allowdumps = true} p {protected = true} s {stdin = true; break} + * {usage} ) } } From 5464e3d91d92f6ffeaa4b7c1f3bf641a4b3c47d1 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Thu, 1 Jun 2023 13:31:25 -0700 Subject: [PATCH 05/28] Cleanup and fix %dispatch for -c --- initial.es | 2 +- prim-dump.c | 2 +- runtime.es | 73 ++++++++++++++++++++++++++++------------------------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/initial.es b/initial.es index 68f9285e..ae426cf7 100644 --- a/initial.es +++ b/initial.es @@ -384,7 +384,7 @@ fn-%or = $&noreturn @ first rest { fn %background cmd { let (pid = <={$&background $cmd}) { - if {%is-interactive} { + if {~ $runflags interactive} { echo >[1=2] $pid } apid = $pid diff --git a/prim-dump.c b/prim-dump.c index ff2fe273..d320c0d9 100644 --- a/prim-dump.c +++ b/prim-dump.c @@ -1,4 +1,4 @@ -/* prim-etc.c -- miscellaneous primitives ($Revision: 1.2 $) */ +/* prim-dump.c -- primitives for the esdump step ($Revision: 1.0 $) */ #include "es.h" #include "prim.h" diff --git a/runtime.es b/runtime.es index 04e8d0c0..8ae3a892 100644 --- a/runtime.es +++ b/runtime.es @@ -9,9 +9,9 @@ # batch, and there is a hook function for each form. It is the # responsibility of the REPL to call the parser for reading commands, # hand those commands to an appropriate dispatch function, and handle -# any exceptions that may be raised. The function %is-interactive -# can be used to determine whether the most closely binding REPL is -# interactive or batch. +# any exceptions that may be raised. The 'interactive' runflag can be +# used to determine whether the most closely binding REPL is interactive +# or batch. # # The REPLs are invoked by the shell's es:main function or the . or # eval builtins. If the -i flag is used or the shell determines that @@ -28,10 +28,9 @@ # # By convention, the REPL must pass commands to the fn %dispatch, # which has the actual responsibility for executing the command. -# Whatever routine invokes the REPL (internal, for now) has -# the responsibility of setting up fn %dispatch appropriately; -# it is used for implementing the -e, -n, and -x options. -# Typically, fn %dispatch is locally bound. +# Whatever routine invokes the REPL has the responsibility of setting up +# fn %dispatch appropriately; it is used for implementing the -e, -n, and +# -x options. Typically, fn %dispatch is locally bound. # # The %parse function raises the eof exception when it encounters # an end-of-file on input. You can probably simulate the C shell's @@ -110,18 +109,22 @@ fn %interactive-loop { # $runflags may be modified, and will (typically) modify the shell's # behavior when changed. # -# $runflags may contain arbitrary words, but only 'exitonfalse', -# 'interactive', 'noexec', 'echoinput', 'printcmds', and 'lisptrees' -# (if support is compiled in) do anything by default. +# $runflags may contain arbitrary words. The only words that have an +# effect by default are +# - exitonfalse +# - interactive +# - noexec +# - echoinput +# - printcmds +# and, if support is compiled in, +# - lisptrees +# - gcinfo +# - gcverbose # # In several of the following functions, we call '$fn-' instead # of the usual ''. This is to prevent these infrastructural # functions from mangling the expected value of '$0'. -fn %is-interactive { - ~ $runflags interactive -} - set-runflags = @ new { let (nf = ()) { for (flag = $new) { @@ -156,7 +159,7 @@ noexport = $noexport fn-%dispatch runflags fn %run-input file { $&runinput { - if %is-interactive { + if {~ $runflags interactive} { $fn-%interactive-loop } { $fn-%batch-loop @@ -225,24 +228,24 @@ es:main = @ argv { allowdumps = false protected = false - fn-usage = { -echo 'usage: es [-c command] [-silevxnpo] [file [args ...]]' -echo ' -c cmd execute argument' -echo ' -s read commands from standard input; stop option parsing' -echo ' -i interactive shell' -echo ' -l login shell' -echo ' -e exit if any command exits with false status' -echo ' -v print input to standard error' -echo ' -x print commands to standard error before executing' -echo ' -n just parse; don''t execute' -echo ' -p don''t load functions from the environment' -echo ' -o don''t open stdin, stdout, and stderr if they were closed' -echo ' -d don''t ignore SIGQUIT or SIGTERM' -echo ' -I print garbage collector information (if compiled in)' -echo ' -G print verbose garbage collector information (if compiled in)' -echo ' -L print parser results in LISP format (if compiled in)' -exit 1 -} + fn usage { + echo 'usage: es [-c command] [-silevxnpo] [file [args ...]]' + echo ' -c cmd execute argument' + echo ' -s read commands from standard input; stop option parsing' + echo ' -i interactive shell' + echo ' -l login shell' + echo ' -e exit if any command exits with false status' + echo ' -v print input to standard error' + echo ' -x print commands to standard error before executing' + echo ' -n just parse; don''t execute' + echo ' -p don''t load functions from the environment' + echo ' -o don''t open stdin, stdout, and stderr if they were closed' + echo ' -d don''t ignore SIGQUIT or SIGTERM' + echo ' -I print garbage collector information (if compiled in)' + echo ' -G print verbose garbage collector information (if compiled in)' + echo ' -L print parser results in LISP format (if compiled in)' + exit 1 + } ) { if {!~ $#argv 0} { (es argv) = $argv @@ -335,10 +338,10 @@ exit 1 } {!~ $cmd ()} { if {!~ $#argv 0} { local ((0 *) = $argv) - $fn-eval $cmd + $fn-%dispatch '{'^$^cmd^'}' } { local ((0 *) = $es) - $fn-eval $cmd + $fn-%dispatch '{'^$^cmd^'}' } } { local ((0 *) = $es $argv) From 59aa719b00b674898ce7f6b8191dec935c8fc330 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Thu, 1 Jun 2023 16:42:33 -0700 Subject: [PATCH 06/28] Man page update --- doc/es.1 | 79 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index a62a492e..8cf1c2d1 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -1729,6 +1729,40 @@ in .Cr $signals ; other values will be on the list if the shell starts up with some signals ignored. +.TP +.Cr runflags +Contains a list of flags which control the execution of the +.I es +interpreter context. +Several of these correspond with shell options. +Any word can belong to the value of +.Cr $runflags , +but only certain words have meaning to the +.I es +interpreter. +These words are +.IS +.TP +.Cr exitonfalse +Exit if any command returns a non-zero status. +Corresponds with the -e option. +.TP +.Cr interactive +Run as an interactive shell. +Corresponds with the -i option. +.TP +.Cr noexec +Turn off execution of commands. +Corresponds with the -n option. +.TP +.Cr echoinput +Echo input to standard error. +Corresponds with the -v option. +.TP +.Cr printcmds +Print commands to standard error before executing them. +Corresponds with the -x option. +.IE .PP The values of .Cr path @@ -2238,8 +2272,9 @@ Runs the command in the background. The shell variable .Cr apid contains the process ID of the background process, -which is printed if the shell is interactive (according to -.Cr %is-interactive ). +which is printed if the shell is interactive (according to the +.Cr interactive +runflag. .TP .Cr "%backquote \fIseparator cmd\fP" Runs the command in a child process and returns its @@ -2549,16 +2584,6 @@ Repeated instances of separator characters cause null strings to appear in the result. (This function is used by some builtin settor functions.) .TP -.Cr "%is-interactive" -Returns true if the current interpreter context is interactive; -that is, if shell command input is currently coming from an -interactive user. -More precisely, this is true if the innermost enclosing read-eval-print -loop is -.Cr %interactive-loop -rather than -.Cr %batch-loop . -.TP .Cr "%newfd" Returns a file descriptor that the shell thinks is not currently in use. .TP @@ -2571,6 +2596,16 @@ This builtin can be used to set (by convention, the name of the program) to something other than file name. .TP +.Cr "%run-input \fI[file]\fP" +Runs either +.Cr %interactive-loop +or +.Cr %batch-loop +using +.I file +as input. +stdin is used as input if no file is given. +.TP .Cr "%split \fIseparator \fR[\fPargs ...\fR]" Splits its arguments into separate strings at every occurrence of any of the characters in the string @@ -2623,12 +2658,6 @@ exit result .ft R .De .PP -In addition, the primitive -.Cr dot -implements the -.Rc `` . '' -builtin function. -.PP The .Cr cd primitive is used in the implementation of the @@ -2655,7 +2684,8 @@ close home run count newfd seq dup openfile split flatten parse var -fsplit pipe whatis +fsplit pipe +whatis isatty .ft R .De .PP @@ -2666,7 +2696,7 @@ prefixes and internal hyphens: .ta 1.75i 3.5i .Ds .ft \*(Cf -batchloop exitonfalse isinteractive +exitonfalse runinput .ft R .De .PP @@ -2690,7 +2720,8 @@ The following primitives implement the similarly named settor functions: .ta 1.75i 3.5i .Ds .ft \*(Cf -sethistory setnoexport setsignals +sethistory setnoexport +setsignals setrunflags .ft R .De .PP @@ -2735,6 +2766,12 @@ The garbage collector in runs rather frequently; there should be no reason for a user to issue this command. .TP +.Cr "$&importenvfuncs" +Imports functions and settor functions from the environment. +This is used during +.I es +startup and should really never be used after that. +.TP .Cr "$&noreturn \fIlambda args ...\fP" Call the .IR lambda , From de1cafd5552cdfd0b507e406269bde77a718b503 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Thu, 1 Jun 2023 17:52:48 -0700 Subject: [PATCH 07/28] Tweak $&batchloop --- prim-dump.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/prim-dump.c b/prim-dump.c index d320c0d9..8bdfd0ea 100644 --- a/prim-dump.c +++ b/prim-dump.c @@ -18,11 +18,10 @@ PRIM(batchloop) { if (!termeq(e->term, "eof")) throw(e); - if (result == true) - result = true; - RefReturn(result); EndExceptionHandler + + RefReturn(result); } From 65b30711e53a0748804d921ce1e72f762732ded6 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Thu, 1 Jun 2023 17:57:20 -0700 Subject: [PATCH 08/28] remove last vestiges of getopt :) --- main.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/main.c b/main.c index 97d2c183..7c24f98d 100644 --- a/main.c +++ b/main.c @@ -2,10 +2,6 @@ #include "es.h" -/* #if 0 && !HPUX && !defined(linux) && !defined(sgi) */ -/* extern int getopt (int argc, char **argv, const char *optstring); */ -/* #endif */ - extern int optind; extern char *optarg; From ba242e9f42b1a04f79bde3d5c4fbcf0767ddbb12 Mon Sep 17 00:00:00 2001 From: Jack Conger Date: Fri, 4 Aug 2023 14:21:18 -0700 Subject: [PATCH 09/28] Remove last last bits of getopt from main.c --- main.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/main.c b/main.c index 7c24f98d..8a745b66 100644 --- a/main.c +++ b/main.c @@ -2,12 +2,8 @@ #include "es.h" -extern int optind; -extern char *optarg; - extern char **environ; - /* initpath -- set $path based on the configuration default */ static void initpath(void) { int i; From efbfdc9274a4fa83ce64ad43269758859bc6f6c1 Mon Sep 17 00:00:00 2001 From: jpco Date: Sun, 3 Dec 2023 10:54:00 -0800 Subject: [PATCH 10/28] Get a little kludgey, but make trip.es pass --- es.h | 3 +- initial.es | 2 +- input.c | 119 +++++++++++++++++++++++++++++++++++------------------ prim-etc.c | 36 ++++++++++++---- runtime.es | 35 +++++++++------- 5 files changed, 131 insertions(+), 64 deletions(-) diff --git a/es.h b/es.h index cc278d6a..bace5772 100644 --- a/es.h +++ b/es.h @@ -290,8 +290,9 @@ extern void resetparser(void); extern void setrunflags(int flags); extern List *runflags_from_int(int); extern int runflags_to_int(List *); -extern List *runinput(char *name, List *cmd); + extern List *runfd(int fd, const char *name, List *cmd); +extern List *runstring(const char *str, List *cmd); /* eval_* flags are also understood as runflags */ #define run_interactive 4 /* -i or $0[0] = '-' */ diff --git a/initial.es b/initial.es index ae426cf7..9e9d2456 100644 --- a/initial.es +++ b/initial.es @@ -657,7 +657,7 @@ fn %batch-loop { } } -fn-. = $&runinput %batch-loop +fn-. = $&runfile %batch-loop # source the runtime init script. . ./runtime.es diff --git a/input.c b/input.c index a6904564..7ec2a192 100644 --- a/input.c +++ b/input.c @@ -74,6 +74,11 @@ static void warn(char *s) { eprint("warning: %s\n", locate(input, s)); } + +/* + * runflags + */ + #define NRUNFLAGS 4 static struct{ int mask; @@ -440,6 +445,42 @@ extern void resetparser(void) { error = NULL; } +/* runinput -- run from an input source */ +extern List *runinput(Input *in, List *cmd) { + List * volatile result = NULL; + + if (input != NULL) + in->runflags = input->runflags; + else + in->runflags = runflags_to_int(varlookup("runflags", NULL)); + + in->runflags &= ~eval_inchild; + in->get = (in->runflags & run_echoinput) ? getverbose : get; + in->prev = input; + input = in; + + ExceptionHandler + + result = eval(cmd, NULL, in->runflags); + + CatchException (e) + + (*input->cleanup)(input); + input = input->prev; + throw(e); + + EndExceptionHandler + + input = in->prev; + (*in->cleanup)(in); + return result; +} + + +/* + * pushing new input sources + */ + /* fdcleanup -- cleanup after running from a file descriptor */ static void fdcleanup(Input *in) { unregisterfd(&in->fd); @@ -448,15 +489,7 @@ static void fdcleanup(Input *in) { efree(in->bufbegin); } -/* runinput -- run a command with a file as input */ -extern List *runinput(char *name, List *cmd) { - int fd = 0; - if (name != NULL && (fd = eopen(name, oOpen)) == -1) - fail("$&runinput", "%s: %s\n", name, esstrerror(errno)); - - return runfd(fd, name, cmd); -} - +/* runfd -- run commands from a file descriptor */ extern List *runfd(int fd, const char *name, List *cmd) { Input in; List *result; @@ -466,46 +499,19 @@ extern List *runfd(int fd, const char *name, List *cmd) { in.fill = fdfill; in.cleanup = fdcleanup; in.fd = fd; - if (input != NULL) - in.runflags = input->runflags; - else - in.runflags = runflags_to_int(varlookup("runflags", NULL)); registerfd(&in.fd, TRUE); in.buflen = BUFSIZE; in.bufbegin = in.buf = ealloc(in.buflen); in.bufend = in.bufbegin; - in.name = (name == NULL) ? "stdin" : name; - - int flags = in.runflags; - result = NULL; - - flags &= ~eval_inchild; - in.get = (flags & run_echoinput) ? getverbose : get; - in.prev = input; - input = ∈ - - ExceptionHandler + in.name = (name == NULL) ? str("fd %d", fd) : name; - result = eval(cmd, NULL, flags); - - CatchException (e) - - (*input->cleanup)(input); - input = input->prev; - throw(e); - - EndExceptionHandler + RefAdd(in.name); + result = runinput(&in, cmd); + RefRemove(in.name); - input = in.prev; - (*in.cleanup)(&in); return result; } - -/* - * pushing new input sources - */ - /* stringcleanup -- cleanup after running from a string */ static void stringcleanup(Input *in) { efree(in->bufbegin); @@ -517,6 +523,32 @@ static int stringfill(Input *in) { return EOF; } +/* runstring -- run commands from a string */ +extern List *runstring(const char *str, List *cmd) { + Input in; + List *result; + unsigned char *buf; + + assert(str != NULL); + + 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; + + RefAdd(in.name); + result = runinput(&in, cmd); + RefRemove(in.name); + return result; +} + /* parseinput -- turn an input source into a tree */ extern Tree *parseinput(Input *in) { Tree * volatile result = NULL; @@ -549,6 +581,8 @@ extern Tree *parsestring(const char *str) { assert(str != NULL); + /* TODO: abstract out common code with runstring */ + memzero(&in, sizeof (Input)); in.fd = -1; in.lineno = 1; @@ -567,6 +601,11 @@ extern Tree *parsestring(const char *str) { return result; } +/* isinteractive -- is the innermost input source interactive? */ +extern Boolean isinteractive(void) { + return input == NULL ? FALSE : ((input->runflags & run_interactive) != 0); +} + /* * readline integration. diff --git a/prim-etc.c b/prim-etc.c index 5e931d45..50de8c8f 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -71,17 +71,38 @@ PRIM(setrunflags) { return list; } -PRIM(runinput) { - if (list == NULL) +PRIM(runfile) { + if (list == NULL || (list->next != NULL && list->next->next != NULL)) fail("$&runinput", "usage: $&runinput command [file]"); Ref(List *, result, NULL); Ref(List *, cmd, mklist(list->term, NULL)); + Ref(char *, file, "stdin"); + + int fd = 0; + + if (list->next != NULL) { + file = getstr(list->next->term); + fd = eopen(file, oOpen); + if (fd == -1) + fail("$&runfile", "%s: %s", file, esstrerror(errno)); + } + + result = runfd(fd, file, cmd); + + RefEnd2(file, cmd); + RefReturn(result); +} + +PRIM(runstring) { + if (list == NULL || list->next == NULL || list->next->next != NULL) + fail("$&runstring", "usage: $&runstring command string"); + Ref(List *, result, NULL); + Ref(List *, cmd, mklist(list->term, NULL)); + char *string = mprint(getstr(list->next->term)); - result = runinput((list->next == NULL - ? NULL - : getstr(list->next->term)), - cmd); + result = runstring(string, cmd); + efree(string); RefEnd(cmd); RefReturn(result); } @@ -272,7 +293,8 @@ extern Dict *initprims_etc(Dict *primdict) { X(version); X(exec); X(setrunflags); - X(runinput); + X(runfile); + X(runstring); X(flatten); X(whatis); X(sethistory); diff --git a/runtime.es b/runtime.es index 8ae3a892..6a00822b 100644 --- a/runtime.es +++ b/runtime.es @@ -152,13 +152,13 @@ fn-%exit-on-false = $&exitonfalse # -e noexport = $noexport fn-%dispatch runflags -# %run-input wraps the '$&runinput' primitive with a default command +# %run-file wraps the '$&runfile' primitive with a default command # (which calls one of the REPL functions defined above). When called # on its own, the function is a worse (but technically passable) # version of %dot. -fn %run-input file { - $&runinput { +fn %run-file file { + $&runfile { if {~ $runflags interactive} { $fn-%interactive-loop } { @@ -170,7 +170,7 @@ fn %run-input file { # %dot is the engine for running outside scripts in the current shell. # It manages runflags based on args passed to it, sets $0 and $*, and -# calls `%run-input`. +# calls `%run-file`. fn %dot args { let ( @@ -203,7 +203,7 @@ fn %dot args { 0 = $args(1) * = $args(2 ...) runflags = $flags - ) $fn-%run-input $args(1) + ) $fn-%run-file $args(1) } } @@ -223,6 +223,7 @@ es:main = @ argv { es = es flags = () cmd = () + runcmd = false keepclosed = false stdin = false allowdumps = false @@ -264,7 +265,7 @@ es:main = @ argv { } for (f = <={%fsplit '' <={~~ $a -*}}) { match $f ( - c {(cmd argv) = $argv} + c {runcmd = true; (cmd argv) = $argv} e {flags = $flags exitonfalse} i {flags = $flags interactive} v {flags = $flags echoinput} @@ -283,6 +284,10 @@ es:main = @ argv { } } + if {$runcmd && ~ $cmd ()} { + usage >[1=2] + } + if {!$keepclosed} { if {!access /dev/stdin} {exec {< /dev/null}} if {!access /dev/stdout} {exec {> /dev/null}} @@ -334,18 +339,18 @@ es:main = @ argv { if {~ $cmd () && !$stdin && !~ $#argv 0} { local ((0 *) = $argv) - $fn-%run-input $0 + $fn-%run-file $0 } {!~ $cmd ()} { - if {!~ $#argv 0} { - local ((0 *) = $argv) - $fn-%dispatch '{'^$^cmd^'}' - } { - local ((0 *) = $es) - $fn-%dispatch '{'^$^cmd^'}' - } + local ((0 *) = $es $argv) + $&runstring { + let (c = <=%parse) { + # echo >[1=2] $c + $fn-%dispatch $c + } + } $cmd } { local ((0 *) = $es $argv) - $fn-%run-input + $fn-%run-file } } } From 68da27591e9239ccbe72ba02c9bf303f71a50908 Mon Sep 17 00:00:00 2001 From: jpco Date: Sun, 3 Dec 2023 11:10:50 -0800 Subject: [PATCH 11/28] Cleanup debug stuff --- runtime.es | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/runtime.es b/runtime.es index 6a00822b..d68412df 100644 --- a/runtime.es +++ b/runtime.es @@ -342,12 +342,7 @@ es:main = @ argv { $fn-%run-file $0 } {!~ $cmd ()} { local ((0 *) = $es $argv) - $&runstring { - let (c = <=%parse) { - # echo >[1=2] $c - $fn-%dispatch $c - } - } $cmd + $&runstring {$fn-%dispatch <=%parse} $cmd } { local ((0 *) = $es $argv) $fn-%run-file From 571c1ed6e8af606dd66ab0d6c0d7d18f3d771d01 Mon Sep 17 00:00:00 2001 From: jpco Date: Sun, 14 Jan 2024 12:11:37 -0800 Subject: [PATCH 12/28] Move more things out of the main es binary. --- es.h | 1 - initial.es | 9 ++------- main.c | 41 ++++++++++------------------------------- prim-dump.c | 41 +++++++++++++++++++++++++++++++++++++++++ runtime.es | 52 +++++++++++++++++++++++++++------------------------- signal.c | 6 ------ 6 files changed, 80 insertions(+), 70 deletions(-) diff --git a/es.h b/es.h index bace5772..2123ad41 100644 --- a/es.h +++ b/es.h @@ -347,7 +347,6 @@ extern Atomic slow, interrupted; extern jmp_buf slowlabel; extern Boolean sigint_newline; extern void sigchk(void); -extern Boolean issilentsignal(List *e); extern void setsigdefaults(void); extern void blocksignals(void); extern void unblocksignals(void); diff --git a/initial.es b/initial.es index 9e9d2456..516b8c7c 100644 --- a/initial.es +++ b/initial.es @@ -637,6 +637,7 @@ home = / ifs = ' ' \t \n prompt = '; ' '' max-eval-depth = 640 +path = <=$&defaultpath # noexport lists the variables that are not exported. It is not # exported, because none of the variables that it refers to are @@ -651,13 +652,7 @@ max-eval-depth = 640 noexport = noexport pid signals apid bqstatus path home matchexpr -fn %batch-loop { - forever { - <=$&parse - } -} - -fn-. = $&runfile %batch-loop +fn-. = $&runfile $&batchloop # source the runtime init script. . ./runtime.es diff --git a/main.c b/main.c index 8a745b66..d6c5967d 100644 --- a/main.c +++ b/main.c @@ -4,20 +4,6 @@ extern char **environ; -/* initpath -- set $path based on the configuration default */ -static void initpath(void) { - int i; - static const char * const path[] = { INITIAL_PATH }; - - Ref(List *, list, NULL); - for (i = arraysize(path); i-- > 0;) { - Term *t = mkstr((char *) path[i]); - list = mklist(t, list); - } - vardef("path", NULL, list); - RefEnd(list); -} - /* initpid -- set $pid for this shell */ static void initpid(void) { vardef("pid", NULL, mklist(mkstr(str("%d", getpid())), NULL)); @@ -28,20 +14,19 @@ int main(int argc, char **argv) { initgc(); initconv(); - ExceptionHandler - roothandler = &_localhandler; /* unhygeinic */ + initinput(); + initprims(); + initvars(); - initinput(); - initprims(); - initvars(); + runinitial(); - runinitial(); + initpid(); + initsignals(); + hidevariables(); + importenv(environ, FALSE); - initpath(); - initpid(); - initsignals(); - hidevariables(); - importenv(environ, FALSE); + ExceptionHandler + roothandler = &_localhandler; /* unhygeinic */ Ref(List *, args, listify(argc, argv)); @@ -60,12 +45,6 @@ int main(int argc, char **argv) { if (termeq(e->term, "exit")) return exitstatus(e->next); - else if (termeq(e->term, "error")) - eprint("%L\n", - e->next == NULL ? NULL : e->next->next, - " "); - else if (!issilentsignal(e)) - eprint("uncaught exception: %L\n", e, " "); return 1; EndExceptionHandler diff --git a/prim-dump.c b/prim-dump.c index 8bdfd0ea..3aea6297 100644 --- a/prim-dump.c +++ b/prim-dump.c @@ -24,6 +24,45 @@ PRIM(batchloop) { RefReturn(result); } +PRIM(defaultpath) { + int i; + static const char * const path[] = { INITIAL_PATH }; + + Ref(List *, list, NULL); + for (i = arraysize(path); i-- > 0;) { + Term *t = mkstr((char *) path[i]); + list = mklist(t, list); + } + RefReturn(list); +} + +PRIM(usage) { + static char *usage = + "usage: es [-c command] [-silevxnpo] [file [args ...]]\n" + " -c cmd execute argument\n" + " -s read commands from standard input; stop option parsing\n" + " -i interactive shell\n" + " -l login shell\n" + " -e exit if any command exits with false status\n" + " -v print input to standard error\n" + " -x print commands to standard error before executing\n" + " -n just parse; don't execute\n" + " -p don't load functions from the environment\n" + " -o don't open stdin, stdout, and stderr if they were closed\n" + " -d don't ignore SIGQUIT or SIGTERM" +#if GCINFO + "\n -I print garbage collector information" +#endif +#if GCVERBOSE + "\n -G print verbose garbage collector information" +#endif +#if LISPTREES + "\n -L print parser results in LISP format" +#endif + ; + return mklist(mkstr(usage), NULL); +} + /* * initialization @@ -31,5 +70,7 @@ PRIM(batchloop) { extern Dict *initprims_dump(Dict *primdict) { X(batchloop); + X(defaultpath); + X(usage); return primdict; } diff --git a/runtime.es b/runtime.es index d68412df..563422a3 100644 --- a/runtime.es +++ b/runtime.es @@ -216,9 +216,10 @@ fn-. = %dot # run loop. # +let ( + usage = <=$&usage +) es:main = @ argv { - es:main = () - let ( es = es flags = () @@ -230,21 +231,7 @@ es:main = @ argv { protected = false fn usage { - echo 'usage: es [-c command] [-silevxnpo] [file [args ...]]' - echo ' -c cmd execute argument' - echo ' -s read commands from standard input; stop option parsing' - echo ' -i interactive shell' - echo ' -l login shell' - echo ' -e exit if any command exits with false status' - echo ' -v print input to standard error' - echo ' -x print commands to standard error before executing' - echo ' -n just parse; don''t execute' - echo ' -p don''t load functions from the environment' - echo ' -o don''t open stdin, stdout, and stderr if they were closed' - echo ' -d don''t ignore SIGQUIT or SIGTERM' - echo ' -I print garbage collector information (if compiled in)' - echo ' -G print verbose garbage collector information (if compiled in)' - echo ' -L print parser results in LISP format (if compiled in)' + echo $usage exit 1 } ) { @@ -337,15 +324,30 @@ es:main = @ argv { } } - if {~ $cmd () && !$stdin && !~ $#argv 0} { - local ((0 *) = $argv) - $fn-%run-file $0 - } {!~ $cmd ()} { - local ((0 *) = $es $argv) - $&runstring {$fn-%dispatch <=%parse} $cmd + catch @ e type msg { + if {~ $e exit} { + result $type + } {~ $e error} { + echo >[1=2] $msg + result 1 + } {~ $e signal && ~ $type sigint} { + # sigint: the silent signal + result 1 + } { + echo >[1=2] uncaught exception: $e $type $msg + result 1 + } } { - local ((0 *) = $es $argv) - $fn-%run-file + if {~ $cmd () && !$stdin && !~ $#argv 0} { + local ((0 *) = $argv) + $fn-%run-file $0 + } {!~ $cmd ()} { + local ((0 *) = $es $argv) + $&runstring {$fn-%dispatch <=%parse} $cmd + } { + local ((0 *) = $es $argv) + $fn-%run-file + } } } } diff --git a/signal.c b/signal.c index 11709bfc..6e48fcfc 100644 --- a/signal.c +++ b/signal.c @@ -211,12 +211,6 @@ extern void setsigdefaults(void) { * utility functions */ -extern Boolean issilentsignal(List *e) { - return (termeq(e->term, "signal")) - && e->next != NULL - && termeq(e->next->term, "sigint"); -} - extern List *mksiglist(void) { int sig = NSIG; Sigeffect effects[NSIG]; From ad847aa10435f02342590bf5cb48b3d80d498a78 Mon Sep 17 00:00:00 2001 From: jpco Date: Sun, 14 Jan 2024 15:01:54 -0800 Subject: [PATCH 13/28] A bit more concision --- es.h | 3 +-- input.c | 45 ++++++++++++++++++--------------------------- main.c | 8 ++------ prim-etc.c | 4 +--- var.c | 5 ++++- 5 files changed, 26 insertions(+), 39 deletions(-) diff --git a/es.h b/es.h index 2123ad41..2005b3bd 100644 --- a/es.h +++ b/es.h @@ -187,7 +187,7 @@ extern List *extractmatches(List *subjects, List *patterns, StrList *quotes); /* var.c */ extern void initvars(void); -extern void importenv(char **envp, Boolean funcs); +extern void importenv(Boolean funcs); extern void hidevariables(void); extern void validatevar(const char *var); extern List *varlookup(const char *name, Binding *binding); @@ -283,7 +283,6 @@ 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); diff --git a/input.c b/input.c index 7ec2a192..e92bd480 100644 --- a/input.c +++ b/input.c @@ -523,13 +523,8 @@ static int stringfill(Input *in) { return EOF; } -/* runstring -- run commands from a string */ -extern List *runstring(const char *str, List *cmd) { +static Input stringinput(const char *str, unsigned char **buf) { Input in; - List *result; - unsigned char *buf; - - assert(str != NULL); memzero(&in, sizeof (Input)); in.fd = -1; @@ -537,12 +532,25 @@ extern List *runstring(const char *str, List *cmd) { 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; + *buf = ealloc(in.buflen + 1); + memcpy(*buf, str, in.buflen); + in.bufbegin = in.buf = *buf; in.bufend = in.buf + in.buflen; in.cleanup = stringcleanup; + return in; +} + +/* runstring -- run commands from a string */ +extern List *runstring(const char *str, List *cmd) { + Input in; + List *result; + unsigned char *buf; + + assert(str != NULL); + + in = stringinput(str, &buf); + RefAdd(in.name); result = runinput(&in, cmd); RefRemove(in.name); @@ -581,19 +589,7 @@ extern Tree *parsestring(const char *str) { 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 = stringinput(str, &buf); RefAdd(in.name); result = parseinput(&in); @@ -601,11 +597,6 @@ extern Tree *parsestring(const char *str) { return result; } -/* isinteractive -- is the innermost input source interactive? */ -extern Boolean isinteractive(void) { - return input == NULL ? FALSE : ((input->runflags & run_interactive) != 0); -} - /* * readline integration. diff --git a/main.c b/main.c index d6c5967d..abe46aa6 100644 --- a/main.c +++ b/main.c @@ -2,8 +2,6 @@ #include "es.h" -extern char **environ; - /* initpid -- set $pid for this shell */ static void initpid(void) { vardef("pid", NULL, mklist(mkstr(str("%d", getpid())), NULL)); @@ -23,10 +21,10 @@ int main(int argc, char **argv) { initpid(); initsignals(); hidevariables(); - importenv(environ, FALSE); + importenv(FALSE); ExceptionHandler - roothandler = &_localhandler; /* unhygeinic */ + roothandler = &_localhandler; /* unhygienic */ Ref(List *, args, listify(argc, argv)); @@ -43,8 +41,6 @@ int main(int argc, char **argv) { CatchException (e) - if (termeq(e->term, "exit")) - return exitstatus(e->next); return 1; EndExceptionHandler diff --git a/prim-etc.c b/prim-etc.c index 50de8c8f..058bda96 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -268,10 +268,8 @@ PRIM(setmaxevaldepth) { RefReturn(lp); } -extern char **environ; - PRIM(importenvfuncs) { - importenv(environ, TRUE); + importenv(TRUE); return true; } diff --git a/var.c b/var.c index 1b993102..cabde69a 100644 --- a/var.c +++ b/var.c @@ -425,11 +425,14 @@ static void importvar(char *name0, char *value) { } +extern char **environ; + /* importenv -- load variables from the environment */ -extern void importenv(char **envp, Boolean funcs) { +extern void importenv(Boolean funcs) { char *envstr; size_t bufsize = 1024; char *buf = ealloc(bufsize); + char **envp = environ; for (; (envstr = *envp) != NULL; envp++) { size_t nlen; From 365bbedf4a16a903455a8ab0a98c530b2ad8cd13 Mon Sep 17 00:00:00 2001 From: jpco Date: Sun, 14 Jan 2024 15:54:32 -0800 Subject: [PATCH 14/28] Update comment --- runtime.es | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/runtime.es b/runtime.es index 563422a3..fa40b486 100644 --- a/runtime.es +++ b/runtime.es @@ -4,19 +4,15 @@ # Read-eval-print loops # -# In es, the main read-eval-print loop (REPL) can lie outside the -# shell itself. Es can be run in one of two modes, interactive or -# batch, and there is a hook function for each form. It is the -# responsibility of the REPL to call the parser for reading commands, -# hand those commands to an appropriate dispatch function, and handle -# any exceptions that may be raised. The 'interactive' runflag can be -# used to determine whether the most closely binding REPL is interactive -# or batch. -# -# The REPLs are invoked by the shell's es:main function or the . or -# eval builtins. If the -i flag is used or the shell determines that -# it's input is interactive, %interactive-loop is invoked; otherwise -# %batch-loop is used. +# es contains two read-eval-print loops (REPLs) which are central to the +# operation of the shell. It is the responsibility of the REPL +# to call the parser for reading commands, hand those commands to an +# appropriate dispatch function, and handle any exceptions that may be +# raised. The REPLs are invoked by the es:main or . functions. +# %interactive-loop is invoked if the -i flag is used or if the shell +# determines that its input is interactive; otherwise, %batch-loop is +# used. The 'interactive' runflag can be used to determine whether the +# most closely binding REPL is interactive or batch. # # The function %parse can be used to call the parser, which returns # an es command. %parse takes two arguments, which are used as the @@ -26,18 +22,15 @@ # 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. -# Whatever routine invokes the REPL has the responsibility of setting up -# fn %dispatch appropriately; it is used for implementing the -e, -n, and -# -x options. Typically, fn %dispatch is locally bound. -# # 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. # Other than eof, %interactive-loop does not exit on exceptions, # where %batch-loop does. # +# By convention, the REPL must pass commands to the fn %dispatch, +# which has the actual responsibility for executing the command. +# # The looping construct forever is used rather than while, because # while catches the break exception, which would make it difficult # to print ``break outside of loop'' errors. From 1a94d49a147666fa9f7c24da77efe2e321740319 Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 15 Jan 2024 13:09:25 -0800 Subject: [PATCH 15/28] Runflags updates. Namely, don't allow the -GLI flags if support isn't compiled in. --- input.c | 31 +++++++------------------------ prim-dump.c | 18 ++++++++++++++++++ prim-etc.c | 21 ++++----------------- runtime.es | 48 ++++++++++++++++++++++++++++++------------------ 4 files changed, 59 insertions(+), 59 deletions(-) diff --git a/input.c b/input.c index e92bd480..31f25f1e 100644 --- a/input.c +++ b/input.c @@ -79,45 +79,28 @@ static void warn(char *s) { * runflags */ -#define NRUNFLAGS 4 +# define NRUNFLAGS 3 static struct{ int mask; char *name; } flagarr[NRUNFLAGS] = { - {eval_exitonfalse, "exitonfalse"}, {run_interactive, "interactive"}, {run_echoinput, "echoinput"}, {run_lisptrees, "lisptrees"} }; -extern List *runflags_from_int(int flags) { - int len = 0; - char *flagstrs[NRUNFLAGS]; - - int i; - for (i = 0; i < NRUNFLAGS; i++) { - if (flags & flagarr[i].mask) { - flagstrs[len++] = flagarr[i].name; - } - } - - return listify(len, flagstrs); -} - extern int runflags_to_int(List *list) { char *s; int flags = 0; + int i = 0; Ref(List *, lp, list); for (; lp != NULL; lp = lp->next) { s = getstr(lp->term); - if (streq(s, "interactive")) - flags |= run_interactive; - else if (streq(s, "echoinput")) - flags |= run_echoinput; -#if LISPTREES - else if (streq(s, "lisptrees")) - flags |= run_lisptrees; -#endif + for (i = 0; i < NRUNFLAGS; i++) + if (streq(s, flagarr[i].name)) { + flags |= flagarr[i].mask; + break; + } } RefEnd(lp); return flags; diff --git a/prim-dump.c b/prim-dump.c index 3aea6297..e48eaa96 100644 --- a/prim-dump.c +++ b/prim-dump.c @@ -63,6 +63,23 @@ PRIM(usage) { return mklist(mkstr(usage), NULL); } +PRIM(conditionalflags) { + Ref(List *, list, NULL); +#if GCINFO + list = mklist(mkstr("gcinfo"), list); + list = mklist(mkstr("I"), list); +#endif +#if GCVERBOSE + list = mklist(mkstr("gcverbose"), list); + list = mklist(mkstr("G"), list); +#endif +#if LISPTREES + list = mklist(mkstr("lisptrees"), list); + list = mklist(mkstr("L"), list); +#endif + RefReturn(list); +} + /* * initialization @@ -72,5 +89,6 @@ extern Dict *initprims_dump(Dict *primdict) { X(batchloop); X(defaultpath); X(usage); + X(conditionalflags); return primdict; } diff --git a/prim-etc.c b/prim-etc.c index 058bda96..4645d303 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -40,33 +40,20 @@ PRIM(exec) { return eval(list, NULL, evalflags | eval_inchild); } -#if GCVERBOSE +/* If not -DGCVERBOSE or -DGCINFO, these variables are harmlessly useless */ Boolean gcverbose = FALSE; -#endif -#if GCINFO Boolean gcinfo = FALSE; -#endif PRIM(setrunflags) { -#if GCVERBOSE || GCINFO - Boolean gcv = FALSE; - Boolean gci = FALSE; Ref(List *, lp, list); - for (lp = list; lp != NULL; lp = lp->next) { + for (lp = list; lp != NULL; lp = lp->next) { if (termeq(lp->term, "gcverbose")) - gcv = TRUE; + gcverbose = TRUE; else if (termeq(lp->term, "gcinfo")) - gci = TRUE; + gcinfo = TRUE; } RefEnd(lp); -#if GCVERBOSE - gcverbose = gcv; -#endif -#if GCINFO - gcinfo = gci; -#endif -#endif setrunflags(runflags_to_int(list)); return list; } diff --git a/runtime.es b/runtime.es index fa40b486..fb949db7 100644 --- a/runtime.es +++ b/runtime.es @@ -211,6 +211,19 @@ fn-. = %dot let ( usage = <=$&usage + runflag-flagpairs = ( + (e exitonfalse) + (i interactive) + (v echoinput) + (x printcmds) + (n noexec) + (l login) + # (L lisptrees) (G gcverbose) (I gcinfo) + <=$&conditionalflags + ) +) +let ( + runflag-args = <={let (accum = ()) for ((a _) = $runflag-flagpairs) accum = $accum $a} ) es:main = @ argv { let ( @@ -243,24 +256,23 @@ es:main = @ argv { if {~ $a --} { break } - for (f = <={%fsplit '' <={~~ $a -*}}) { - match $f ( - c {runcmd = true; (cmd argv) = $argv} - e {flags = $flags exitonfalse} - i {flags = $flags interactive} - v {flags = $flags echoinput} - L {flags = $flags lisptrees} - x {flags = $flags printcmds} - n {flags = $flags noexec} - l {flags = $flags login} - G {flags = $flags gcverbose} - I {flags = $flags gcinfo} - o {keepclosed = true} - d {allowdumps = true} - p {protected = true} - s {stdin = true; break} - * {usage} - ) + let (rfa = ()) { + for (f = <={%fsplit '' <={~~ $a -*}}) { + match $f ( + c {runcmd = true; (cmd argv) = $argv} + o {keepclosed = true} + d {allowdumps = true} + p {protected = true} + s {stdin = true; break} + $runflag-args {rfa = $rfa $f} + * {usage} + ) + } + for ((arg flag) = $runflag-flagpairs) { + if {~ $arg $rfa} { + flags = $flags $flag + } + } } } From 3b5a43a86abaa81713fdc235258374ee39ed0cba Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 15 Jan 2024 16:12:07 -0800 Subject: [PATCH 16/28] Reorganize runtime.es to read top-to-bottom instead of C-style bottom-to-top. --- runtime.es | 430 ++++++++++++++++++++++++++++------------------------- 1 file changed, 224 insertions(+), 206 deletions(-) diff --git a/runtime.es b/runtime.es index fb949db7..a73fbd78 100644 --- a/runtime.es +++ b/runtime.es @@ -1,214 +1,22 @@ # runtime.es -- set up initial interpreter runtime ($Revision: 1.1.1.1 $) -# -# Read-eval-print loops -# - -# es contains two read-eval-print loops (REPLs) which are central to the -# operation of the shell. It is the responsibility of the REPL -# to call the parser for reading commands, hand those commands to an -# appropriate dispatch function, and handle any exceptions that may be -# raised. The REPLs are invoked by the es:main or . functions. -# %interactive-loop is invoked if the -i flag is used or if the shell -# determines that its input is interactive; otherwise, %batch-loop is -# used. The 'interactive' runflag can be used to determine whether the -# most closely binding REPL is interactive or batch. -# -# 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. -# -# 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. -# Other than eof, %interactive-loop does not exit on exceptions, -# where %batch-loop does. -# -# By convention, the REPL must pass commands to the fn %dispatch, -# which has the actual responsibility for executing the command. -# -# The looping construct forever is used rather than while, because -# while catches the break exception, which would make it difficult -# to print ``break outside of loop'' errors. -# -# 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 { - let (result = <=true) { - catch @ e rest { - if {~ $e eof} { - return $result - } { - throw $e $rest - } - } { - forever { - let (cmd = <=%parse) { - if {!~ $#cmd 0} { - result = <={$fn-%dispatch $cmd(1)} - } - } - } - } - } -} - -fn %interactive-loop { - let (result = <=true) { - catch @ e type msg { - if {~ $e eof} { - return $result - } {~ $e exit} { - throw $e $type $msg - } {~ $e error} { - echo >[1=2] $msg - $fn-%dispatch false - } {~ $e signal} { - if {!~ $type sigint sigterm sigquit} { - echo >[1=2] caught unexpected signal: $type - } - } { - echo >[1=2] uncaught exception: $e $type $msg - } - throw retry # restart forever loop - } { - forever { - if {!~ $#fn-%prompt 0} { - %prompt - } - let (code = <={%parse $prompt}) { - if {!~ $#code 0} { - result = <={$fn-%dispatch $code} - } - } - } - } - } -} - # # Es entry points # -# Es execution is configured with the $runflags variable, which is -# similar to `$-` in POSIX shells. Unlike `$-`, the value of -# $runflags may be modified, and will (typically) modify the shell's -# behavior when changed. # -# $runflags may contain arbitrary words. The only words that have an -# effect by default are -# - exitonfalse -# - interactive -# - noexec -# - echoinput -# - printcmds -# and, if support is compiled in, -# - lisptrees -# - gcinfo -# - gcverbose +# es:main is the entry point to the shell. It parses the binary's argv, +# initializes runflags (described in detail below), runs .esrc (if login), +# and hands off execution to `%run-file` or `%run-string`. # # In several of the following functions, we call '$fn-' instead # of the usual ''. This is to prevent these infrastructural # functions from mangling the expected value of '$0'. - -set-runflags = @ new { - let (nf = ()) { - for (flag = $new) { - if {!~ $nf $flag} {nf = $nf $flag} - } - let ( - dp-p = <={if {~ $nf printcmds} {result 'print'} {result 'noprint'}} - dp-e = <={if {~ $nf noexec} {result 'noeval'} {result 'eval'}} - dp-f = <={if {~ $nf exitonfalse} {result '%exit-on-false'} {result ()}} - ) fn-%dispatch = $dp-f $(fn-%^$(dp-e)^-^$(dp-p)) - $&setrunflags $nf - } -} - - -# 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.) - -fn %eval-noprint # -fn %eval-print { echo $* >[1=2]; $* } # -x -fn %noeval-noprint { } # -n -fn %noeval-print { echo $* >[1=2] } # -n -x -fn-%exit-on-false = $&exitonfalse # -e - -noexport = $noexport fn-%dispatch runflags - -# %run-file wraps the '$&runfile' primitive with a default command -# (which calls one of the REPL functions defined above). When called -# on its own, the function is a worse (but technically passable) -# version of %dot. - -fn %run-file file { - $&runfile { - if {~ $runflags interactive} { - $fn-%interactive-loop - } { - $fn-%batch-loop - } - } $file -} - - -# %dot is the engine for running outside scripts in the current shell. -# It manages runflags based on args passed to it, sets $0 and $*, and -# calls `%run-file`. - -fn %dot args { - let ( - fn usage { - throw error %dot 'usage: . [-einvx] file [arg ...]' - } - flags = () - ) { - for (a = $args) { - if {!~ $a -*} { - break - } - args = $args(2 ...) - if {~ $a --} { - break - } - for (f = <={%fsplit '' <={~~ $a -*}}) { - match $f ( - e {flags = $flags exitonfalse} - i {flags = $flags interactive} - n {flags = $flags noexec} - v {flags = $flags echoinput} - x {flags = $flags printcmds} - * {usage} - ) - } - } - if {~ $#args 0} {usage} - local ( - 0 = $args(1) - * = $args(2 ...) - runflags = $flags - ) $fn-%run-file $args(1) - } -} - -fn-. = %dot - - -# -# es:main is the entry point to the shell. It parses the binary's argv, -# initializes runflags, runs .esrc (if appropriate), and starts the correct -# run loop. # +# A little tricky, but these primitives are only available at dump time, so +# lexically binding their output here lets us cache the results of conditional +# compilation for use at shell startup. let ( usage = <=$&usage runflag-flagpairs = ( @@ -226,6 +34,8 @@ let ( runflag-args = <={let (accum = ()) for ((a _) = $runflag-flagpairs) accum = $accum $a} ) es:main = @ argv { + es:main = () + let ( es = es flags = () @@ -256,7 +66,7 @@ es:main = @ argv { if {~ $a --} { break } - let (rfa = ()) { + let (rfarg = ()) { for (f = <={%fsplit '' <={~~ $a -*}}) { match $f ( c {runcmd = true; (cmd argv) = $argv} @@ -264,12 +74,12 @@ es:main = @ argv { d {allowdumps = true} p {protected = true} s {stdin = true; break} - $runflag-args {rfa = $rfa $f} + $runflag-args {rfarg = $rfarg $f} * {usage} ) } for ((arg flag) = $runflag-flagpairs) { - if {~ $arg $rfa} { + if {~ $arg $rfarg} { flags = $flags $flag } } @@ -321,6 +131,8 @@ es:main = @ argv { throw $e $type $msg } {~ $e error} { echo >[1=2] $msg + } {~ $e signal && ~ $type sigint} { + # sigint: the silent signal } { echo >[1=2] uncaught exception: $e $type $msg } @@ -331,24 +143,22 @@ es:main = @ argv { catch @ e type msg { if {~ $e exit} { - result $type + return $type } {~ $e error} { echo >[1=2] $msg - result 1 } {~ $e signal && ~ $type sigint} { # sigint: the silent signal - result 1 } { echo >[1=2] uncaught exception: $e $type $msg - result 1 } + result 1 } { if {~ $cmd () && !$stdin && !~ $#argv 0} { local ((0 *) = $argv) $fn-%run-file $0 } {!~ $cmd ()} { local ((0 *) = $es $argv) - $&runstring {$fn-%dispatch <=%parse} $cmd + $fn-%run-string $cmd } { local ((0 *) = $es $argv) $fn-%run-file @@ -356,3 +166,211 @@ es:main = @ argv { } } } + + +# %dot is the engine for running outside scripts in the current shell. +# It manages runflags based on args passed to it, sets $0 and $*, and +# calls `%run-file`. + +fn %dot args { + let ( + fn usage { + throw error %dot 'usage: . [-einvx] file [arg ...]' + } + flags = () + ) { + for (a = $args) { + if {!~ $a -*} { + break + } + args = $args(2 ...) + if {~ $a --} { + break + } + for (f = <={%fsplit '' <={~~ $a -*}}) { + match $f ( + e {flags = $flags exitonfalse} + i {flags = $flags interactive} + n {flags = $flags noexec} + v {flags = $flags echoinput} + x {flags = $flags printcmds} + * {usage} + ) + } + } + if {~ $#args 0} {usage} + local ( + 0 = $args(1) + * = $args(2 ...) + runflags = $flags + ) $fn-%run-file $args(1) + } +} + +fn-. = %dot + + +# %run-file wraps the '$&runfile' primitive with a default command +# (which calls one of the REPL functions defined below). When called +# on its own, the function is a worse (but technically passable) +# version of %dot. + +fn %run-file file { + $&runfile { + if {~ $runflags interactive} { + $fn-%interactive-loop + } { + $fn-%batch-loop + } + } $file +} + +# %run-string is the same as %run-file, but instead of taking a file +# as an argument to read, it takes a string to parse and run. + +fn-%run-string = $&runstring {$fn-%dispatch <=%parse} + + +# +# runflags +# + +# Es execution is configured with the $runflags variable, which is +# similar to `$-` in POSIX shells. Unlike `$-`, the value of +# $runflags may be modified, and will (typically) modify the shell's +# behavior when changed. +# +# $runflags may contain arbitrary words. The only words that have any +# effect by default are +# - exitonfalse +# - interactive +# - noexec +# - echoinput +# - printcmds +# and, if support is compiled in, +# - lisptrees +# - gcinfo +# - gcverbose + +set-runflags = @ new { + let (nf = ()) { + for (flag = $new) { + if {!~ $nf $flag} {nf = $nf $flag} + } + let ( + dp-p = <={if {~ $nf printcmds} {result print} {result noprint}} + dp-e = <={if {~ $nf noexec} {result noeval} {result eval}} + dp-f = <={if {~ $nf exitonfalse} {result %exit-on-false} {result ()}} + ) fn-%dispatch = $dp-f $(fn-%^$(dp-e)^-^$(dp-p)) + $&setrunflags $nf + } +} + + +# These functions are potentially passed to a REPL in the %dispatch function, +# depending on the value of $runflags. (For %eval-noprint, note that an empty +# list prepended to a command just causes the command to be executed.) + +fn %eval-noprint # +fn %eval-print { echo $* >[1=2]; $* } # -x +fn %noeval-noprint { } # -n +fn %noeval-print { echo $* >[1=2] } # -n -x +fn-%exit-on-false = $&exitonfalse # -e + +noexport = $noexport fn-%dispatch runflags + + +# +# Read-eval-print loops +# + +# es contains two read-eval-print loops (REPLs) which are central to the +# operation of the shell. It is the responsibility of the REPL +# to call the parser for reading commands, hand those commands to an +# appropriate dispatch function, and handle any exceptions that may be +# raised. The REPLs are invoked by the %run-file function, which is +# itself typically called by either es:main or %dot. +# +# %interactive-loop is invoked if the -i flag is used or if the shell +# determines that its input is interactive; otherwise, %batch-loop is +# used. The 'interactive' runflag can be used to determine whether the +# most closely binding REPL is interactive or batch. +# +# 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. +# +# 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. +# Other than eof, %interactive-loop does not exit on exceptions, +# where %batch-loop does. +# +# By convention, the REPL must pass commands to the fn %dispatch, +# which has the actual responsibility for executing the command. +# +# The looping construct forever is used rather than while, because +# while catches the break exception, which would make it difficult +# to print ``break outside of loop'' errors. +# +# 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 { + let (result = <=true) { + catch @ e rest { + if {~ $e eof} { + return $result + } { + throw $e $rest + } + } { + forever { + let (cmd = <=%parse) { + if {!~ $#cmd 0} { + result = <={$fn-%dispatch $cmd} + } + } + } + } + } +} + +fn %interactive-loop { + let (result = <=true) { + catch @ e type msg { + if {~ $e eof} { + return $result + } {~ $e exit} { + throw $e $type $msg + } {~ $e error} { + echo >[1=2] $msg + $fn-%dispatch false + } {~ $e signal} { + if {!~ $type sigint sigterm sigquit} { + echo >[1=2] caught unexpected signal: $type + } + } { + echo >[1=2] uncaught exception: $e $type $msg + } + throw retry # restart forever loop + } { + forever { + if {!~ $#fn-%prompt 0} { + %prompt + } + let (code = <={%parse $prompt}) { + if {!~ $#code 0} { + result = <={$fn-%dispatch $code} + } + } + } + } + } +} From a5b81903ccbe1777802907a9b050239585da3209 Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 15 Jan 2024 17:32:01 -0800 Subject: [PATCH 17/28] Fix up man page to reflect the last few commits correctly. --- doc/es.1 | 38 +++++++++++++++++++++++++++++--------- prim-etc.c | 11 ++++++++--- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index eacbc8a8..4fd63309 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -1745,23 +1745,39 @@ These words are .TP .Cr exitonfalse Exit if any command returns a non-zero status. -Corresponds with the -e option. +Corresponds with the +.Cr \-e +option. .TP .Cr interactive Run as an interactive shell. -Corresponds with the -i option. +Corresponds with the +.Cr \-i +option. .TP .Cr noexec Turn off execution of commands. -Corresponds with the -n option. +Corresponds with the +.Cr \-n +option. .TP .Cr echoinput Echo input to standard error. -Corresponds with the -v option. +Corresponds with the +.Cr \-v +option. .TP .Cr printcmds Print commands to standard error before executing them. -Corresponds with the -x option. +Corresponds with the +.Cr \-x +option. +.TP +.Cr login +Run as a login shell. +Mainly this controls whether +.Cr .esrc +is run at startup. .IE .PP The values of @@ -2684,8 +2700,7 @@ close home run count newfd seq dup openfile split flatten parse var -fsplit pipe -whatis isatty +fsplit pipe whatis .ft R .De .PP @@ -2696,7 +2711,7 @@ prefixes and internal hyphens: .ta 1.75i 3.5i .Ds .ft \*(Cf -exitonfalse runinput +exitonfalse runfile runstring .ft R .De .PP @@ -2770,7 +2785,12 @@ there should be no reason for a user to issue this command. Imports functions and settor functions from the environment. This is used during .I es -startup and should really never be used after that. +startup. +It is hardcoded to have no effect (and return false) if it has +already been called. +.TP +.Cr "$&isatty" +Tests whether the given fd is a TTY or not. .TP .Cr "$&noreturn \fIlambda args ...\fP" Call the diff --git a/prim-etc.c b/prim-etc.c index 4645d303..e51b4d05 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -85,12 +85,11 @@ PRIM(runstring) { fail("$&runstring", "usage: $&runstring command string"); Ref(List *, result, NULL); Ref(List *, cmd, mklist(list->term, NULL)); - char *string = mprint(getstr(list->next->term)); + Ref(char *, string, getstr(list->next->term)); result = runstring(string, cmd); - efree(string); - RefEnd(cmd); + RefEnd2(string, cmd); RefReturn(result); } @@ -255,8 +254,14 @@ PRIM(setmaxevaldepth) { RefReturn(lp); } +static Boolean didonce = FALSE; PRIM(importenvfuncs) { + if (didonce) + return false; + importenv(TRUE); + didonce = TRUE; + return true; } From 7d161b18f07e822128423ac6041a00e43476f488 Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 15 Jan 2024 18:02:54 -0800 Subject: [PATCH 18/28] Dump improvements: - skip empty lines in $&batchloop - catch exceptions that happen during dumping --- dump.c | 20 +++++++++++++++++++- main.c | 20 ++++++++++---------- prim-dump.c | 3 ++- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/dump.c b/dump.c index ad1096e3..c10a815f 100644 --- a/dump.c +++ b/dump.c @@ -291,7 +291,25 @@ static void printheader(List *title) { extern void runinitial(void) { initdumpprims(); - List *title = runfd(0, "initial.es", mklist(mkstr("$&batchloop"), NULL)); + List *title = NULL; + + ExceptionHandler + + title = runfd(0, "initial.es", mklist(mkstr("$&batchloop"), NULL)); + + CatchException (e) + + if (termeq(e->term, "exit")) + exit(exitstatus(e->next)); + else if (termeq(e->term, "error")) + eprint("%L\n", + e->next == NULL ? NULL : e->next->next, + " "); + else + eprint("uncaught exception %L\n", e, " "); + exit(1); + + EndExceptionHandler gcdisable(); diff --git a/main.c b/main.c index abe46aa6..1e739303 100644 --- a/main.c +++ b/main.c @@ -12,19 +12,19 @@ int main(int argc, char **argv) { initgc(); initconv(); - initinput(); - initprims(); - initvars(); + ExceptionHandler + roothandler = &_localhandler; /* unhygienic */ - runinitial(); + initinput(); + initprims(); + initvars(); - initpid(); - initsignals(); - hidevariables(); - importenv(FALSE); + runinitial(); - ExceptionHandler - roothandler = &_localhandler; /* unhygienic */ + initpid(); + initsignals(); + hidevariables(); + importenv(FALSE); Ref(List *, args, listify(argc, argv)); diff --git a/prim-dump.c b/prim-dump.c index e48eaa96..61021b9a 100644 --- a/prim-dump.c +++ b/prim-dump.c @@ -10,7 +10,8 @@ PRIM(batchloop) { for (;;) { List *cmd = prim("parse", NULL, NULL, 0); - result = eval(cmd, NULL, evalflags); + if (cmd != NULL) + result = eval(cmd, NULL, evalflags); SIGCHK(); } From 5a3957137a95dd1ece69a52b327ec63000d37bfa Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 15 Jan 2024 22:33:23 -0800 Subject: [PATCH 19/28] Shore up the "root exception handler" story. --- es.h | 2 +- main.c | 39 +++++++++++++++++++++------------------ prim-ctl.c | 13 +++++++++++++ prim-io.c | 2 +- runtime.es | 7 +++++-- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/es.h b/es.h index 2005b3bd..e3337c68 100644 --- a/es.h +++ b/es.h @@ -498,7 +498,7 @@ extern List *raised(List *e); _localhandler.up = tophandler; \ tophandler = &_localhandler; \ if (!setjmp(_localhandler.label)) { - + #define CatchException(e) \ pophandler(&_localhandler); \ } else { \ diff --git a/main.c b/main.c index 1e739303..31ef2ba4 100644 --- a/main.c +++ b/main.c @@ -12,36 +12,39 @@ int main(int argc, char **argv) { initgc(); initconv(); - ExceptionHandler - roothandler = &_localhandler; /* unhygienic */ + initinput(); + initprims(); + initvars(); - initinput(); - initprims(); - initvars(); + runinitial(); - runinitial(); + initpid(); + initsignals(); + hidevariables(); + importenv(FALSE); - initpid(); - initsignals(); - hidevariables(); - importenv(FALSE); + Ref(List *, args, listify(argc, argv)); - Ref(List *, args, listify(argc, argv)); + Ref(List *, esmain, varlookup("es:main", NULL)); + if (esmain == NULL) { + eprint("es:main not set\n"); + return 1; + } - Ref(List *, esmain, varlookup("es:main", NULL)); - if (esmain == NULL) { - eprint("es:main not set\n"); - return 1; - } + ExceptionHandler esmain = append(esmain, args); return exitstatus(eval(esmain, NULL, 0)); - RefEnd2(esmain, args); - CatchException (e) + /* This is the sub-root handler for the shell. + * The real root handler is in runtime.es. */ + if (termeq(e->term, "exit")) + return exitstatus(e->next); return 1; EndExceptionHandler + + RefEnd2(esmain, args); } diff --git a/prim-ctl.c b/prim-ctl.c index ef48fbff..acfeebaa 100644 --- a/prim-ctl.c +++ b/prim-ctl.c @@ -48,6 +48,7 @@ PRIM(throw) { PRIM(catch) { Atomic retry; + volatile Boolean root = FALSE; if (list == NULL) fail("$&catch", "usage: catch catcher body"); @@ -60,6 +61,11 @@ PRIM(catch) { ExceptionHandler + if (roothandler == NULL) { + root = TRUE; + roothandler = &_localhandler; + } + result = eval(lp->next, NULL, evalflags); CatchException (frombody) @@ -79,12 +85,19 @@ PRIM(catch) { unblocksignals(); } else { unblocksignals(); + if (root) + roothandler = NULL; throw(fromcatcher); } + EndExceptionHandler EndExceptionHandler } while (retry); + + if (root) + roothandler = NULL; + RefEnd(lp); RefReturn(result); } diff --git a/prim-io.c b/prim-io.c index 4eadb3bd..178371b9 100644 --- a/prim-io.c +++ b/prim-io.c @@ -348,7 +348,7 @@ static List *bqinput(const char *sep, int fd) { PRIM(backquote) { int pid, p[2], status; - + caller = "$&backquote"; if (list == NULL) fail(caller, "usage: backquote separator command [args ...]"); diff --git a/runtime.es b/runtime.es index a73fbd78..df4dce25 100644 --- a/runtime.es +++ b/runtime.es @@ -9,7 +9,7 @@ # initializes runflags (described in detail below), runs .esrc (if login), # and hands off execution to `%run-file` or `%run-string`. # -# In several of the following functions, we call '$fn-' instead +# In several of the functions in this file, we call '$fn-' instead # of the usual ''. This is to prevent these infrastructural # functions from mangling the expected value of '$0'. # @@ -141,9 +141,10 @@ es:main = @ argv { } } + # This is the main "root" exception handler for the shell. catch @ e type msg { if {~ $e exit} { - return $type + throw $e $type $msg } {~ $e error} { echo >[1=2] $msg } {~ $e signal && ~ $type sigint} { @@ -252,6 +253,8 @@ fn-%run-string = $&runstring {$fn-%dispatch <=%parse} # - gcinfo # - gcverbose +fn-%is-interactive = {~ $runflags interactive} + set-runflags = @ new { let (nf = ()) { for (flag = $new) { From d460f9665579e6b8151f702b4bcfed651e80d834 Mon Sep 17 00:00:00 2001 From: jpco Date: Mon, 15 Jan 2024 22:41:47 -0800 Subject: [PATCH 20/28] Fix incorrect (old) references to %run-input/$&runinput. --- doc/es.1 | 15 ++++++++++++--- prim-etc.c | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/doc/es.1 b/doc/es.1 index 4fd63309..b9d551fc 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2612,16 +2612,25 @@ This builtin can be used to set (by convention, the name of the program) to something other than file name. .TP -.Cr "%run-input \fI[file]\fP" +.Cr "%run-file \fI[file]\fP" Runs either .Cr %interactive-loop or .Cr %batch-loop -using +reading the .I file -as input. +for input. stdin is used as input if no file is given. .TP +.Cr "%run-string \fIstring\fP" +Runs either +.Cr %interactive-loop +or +.Cr %batch-loop +using the +.I string +directly as input. +.TP .Cr "%split \fIseparator \fR[\fPargs ...\fR]" Splits its arguments into separate strings at every occurrence of any of the characters in the string diff --git a/prim-etc.c b/prim-etc.c index e51b4d05..a315b39a 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -60,7 +60,7 @@ PRIM(setrunflags) { PRIM(runfile) { if (list == NULL || (list->next != NULL && list->next->next != NULL)) - fail("$&runinput", "usage: $&runinput command [file]"); + fail("$&runfile", "usage: $&runfile command [file]"); Ref(List *, result, NULL); Ref(List *, cmd, mklist(list->term, NULL)); Ref(char *, file, "stdin"); From ac08c759cf8119863651ee828f7d28a1cc3dec12 Mon Sep 17 00:00:00 2001 From: jpco Date: Tue, 16 Jan 2024 21:10:21 -0800 Subject: [PATCH 21/28] Fix memory-unsafe $&conditionalflags. --- prim-dump.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/prim-dump.c b/prim-dump.c index 61021b9a..1d3ce75f 100644 --- a/prim-dump.c +++ b/prim-dump.c @@ -66,18 +66,20 @@ PRIM(usage) { PRIM(conditionalflags) { Ref(List *, list, NULL); + Ref(Term *, t, NULL); #if GCINFO - list = mklist(mkstr("gcinfo"), list); - list = mklist(mkstr("I"), list); + t = mkstr("gcinfo"); list = mklist(t, list); + t = mkstr("I"); list = mklist(t, list); #endif #if GCVERBOSE - list = mklist(mkstr("gcverbose"), list); - list = mklist(mkstr("G"), list); + t = mkstr("gcverbose"); list = mklist(t, list); + t = mkstr("G"); list = mklist(t, list); #endif #if LISPTREES - list = mklist(mkstr("lisptrees"), list); - list = mklist(mkstr("L"), list); + t = mkstr("lisptrees"); list = mklist(t, list); + t = mkstr("L"); list = mklist(t, list); #endif + RefEnd(t); RefReturn(list); } From 98a113928e11c5c83581110268c07beaf3e3d86e Mon Sep 17 00:00:00 2001 From: jpco Date: Wed, 17 Jan 2024 08:13:45 -0800 Subject: [PATCH 22/28] Nitpicks: - Add a comment warning about lexical bindings in initial scripts - Rename "es:main" to "fn-%main" --- main.c | 2 +- runtime.es | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/main.c b/main.c index 31ef2ba4..b43ed4d2 100644 --- a/main.c +++ b/main.c @@ -25,7 +25,7 @@ int main(int argc, char **argv) { Ref(List *, args, listify(argc, argv)); - Ref(List *, esmain, varlookup("es:main", NULL)); + Ref(List *, esmain, varlookup("fn-%main", NULL)); if (esmain == NULL) { eprint("es:main not set\n"); return 1; diff --git a/runtime.es b/runtime.es index df4dce25..768b9506 100644 --- a/runtime.es +++ b/runtime.es @@ -16,7 +16,8 @@ # A little tricky, but these primitives are only available at dump time, so # lexically binding their output here lets us cache the results of conditional -# compilation for use at shell startup. +# compilation for use at shell startup. Note that build-time lexical bindings +# like these MUST NOT be reassigned to GC'd values! Treat them as read-only! let ( usage = <=$&usage runflag-flagpairs = ( @@ -33,8 +34,8 @@ let ( let ( runflag-args = <={let (accum = ()) for ((a _) = $runflag-flagpairs) accum = $accum $a} ) -es:main = @ argv { - es:main = () +fn %main argv { + fn-%main = () let ( es = es From e15ed3f9f7f2fbb3cfb2f8549548a8751d3a0c22 Mon Sep 17 00:00:00 2001 From: jpco Date: Fri, 19 Jan 2024 10:03:26 -0800 Subject: [PATCH 23/28] Complete migration of option-parsing into pure es. - Simplify $&access so that it doesn't need complex option parsing - Rewrite fn-access in initial.es to reproduce the same behavior as before - Add a %getopt function and migrate all the option parsing in initial.es/runtime.es to use it --- Makefile.in | 5 +- access.c | 138 +++++++++++++++----------------------------- doc/es.1 | 25 ++++++-- es.h | 8 --- initial.es | 161 ++++++++++++++++++++++++++++++++++++++++++---------- runtime.es | 70 +++++++++-------------- 6 files changed, 228 insertions(+), 179 deletions(-) diff --git a/Makefile.in b/Makefile.in index 6c2f0f99..abc641c9 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 list.c main.c match.c open.c \ prim-ctl.c prim-etc.c prim-io.c prim-sys.c prim-dump.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 list.o main.o match.o open.o \ prim-ctl.o prim-etc.o prim-io.o prim-sys.o prim-dump.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 @@ -130,7 +130,6 @@ 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 open.o : open.c es.h config.h stdenv.h -opt.o : opt.c es.h config.h stdenv.h 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 diff --git a/access.c b/access.c index b5d60675..b1634436 100644 --- a/access.c +++ b/access.c @@ -69,108 +69,64 @@ static int testfile(char *path, int perm, unsigned int type) { return testperm(&st, perm); } -static char *pathcat(char *prefix, char *suffix) { - char *s; - size_t plen, slen, len; - static char *pathbuf = NULL; - static size_t pathlen = 0; - - if (*prefix == '\0') - return suffix; - if (*suffix == '\0') - return prefix; - - plen = strlen(prefix); - slen = strlen(suffix); - len = plen + slen + 2; /* one for '/', one for '\0' */ - if (pathlen < len) { - pathlen = len; - pathbuf = erealloc(pathbuf, pathlen); +static int *permtab[256] = { NULL }; +static int *typetab[256] = { NULL }; + +PRIM(access) { + int c, *p, perm = 0, type = 0, estatus; + + if (length(list) != 3) + fail("$&access", "usage: access access-type file-type name"); + + Ref(List *, result, NULL); + Ref(char *, atype, getstr(list->term)); + Ref(char *, ftype, getstr(list->next->term)); + Ref(char *, name, getstr(list->next->next->term)); + + for (c = 0; atype[c] != '\0'; c++) { + if ((p = permtab[(unsigned char)atype[c]]) == NULL) + fail("$&access", "bad access type '%c'", c); + perm |= *p; } - memcpy(pathbuf, prefix, plen); - s = pathbuf + plen; - if (s[-1] != '/') - *s++ = '/'; - memcpy(s, suffix, slen + 1); - return pathbuf; + if (ftype[0] == '\0' || ftype[1] != '\0') + fail("$&access", "file type argument must be one character"); + if ((p = typetab[(unsigned char)ftype[0]]) == NULL) + fail("$&access", "bad file type argument '%c'", ftype[0]); + type = *p; + + estatus = testfile(name, perm, type); + result = mklist(mkstr(estatus == 0 ? "0" : esstrerror(estatus)), NULL); + + RefEnd3(name, ftype, atype); + RefReturn(result); } -PRIM(access) { - int c, perm = 0, type = 0, estatus = ENOENT; - Boolean first = FALSE, exception = FALSE; - char *suffix = NULL; - List *lp; - const char * const usage = "access [-n name] [-1e] [-rwx] [-fdcblsp] path ..."; - - gcdisable(); - esoptbegin(list, "$&access", usage, TRUE); - while ((c = esopt("bcdefln:prswx1")) != EOF) - switch (c) { - case 'n': suffix = getstr(esoptarg()); break; - case '1': first = TRUE; break; - case 'e': exception = TRUE; break; - case 'r': perm |= READ; break; - case 'w': perm |= WRITE; break; - case 'x': perm |= EXEC; break; - case 'f': type = S_IFREG; break; - case 'd': type = S_IFDIR; break; - case 'c': type = S_IFCHR; break; - case 'b': type = S_IFBLK; break; +#define FLAGTAB(type, c, def) \ + STMT(static int CONCAT(type,c) = def; \ + CONCAT(type,tab)[(unsigned char)(STRING(c)[0])] = &CONCAT(type,c)) + +extern Dict *initprims_access(Dict *primdict) { + FLAGTAB(perm, e, 0); + FLAGTAB(perm, r, READ); + FLAGTAB(perm, w, WRITE); + FLAGTAB(perm, x, EXEC); + + FLAGTAB(type, a, 0); + FLAGTAB(type, f, S_IFREG); + FLAGTAB(type, d, S_IFDIR); + FLAGTAB(type, c, S_IFCHR); + FLAGTAB(type, b, S_IFBLK); #ifdef S_IFLNK - case 'l': type = S_IFLNK; break; + FLAGTAB(type, l, S_IFLNK); #endif #ifdef S_IFSOCK - case 's': type = S_IFSOCK; break; + FLAGTAB(type, s, S_IFSOCK); #endif #ifdef S_IFIFO - case 'p': type = S_IFIFO; break; + FLAGTAB(type, p, S_IFIFO); #endif - default: - esoptend(); - fail("$&access", "access -%c is not supported on this system", c); - } - list = esoptend(); - - for (lp = NULL; list != NULL; list = list->next) { - int error; - char *name; - - name = getstr(list->term); - if (suffix != NULL) - name = pathcat(name, suffix); - error = testfile(name, perm, type); - - if (first) { - if (error == 0) { - Ref(List *, result, - mklist(mkstr(suffix == NULL - ? name - : gcdup(name)), - NULL)); - gcenable(); - RefReturn(result); - } else if (error != ENOENT) - estatus = error; - } else - lp = mklist(mkstr(error == 0 ? "0" : esstrerror(error)), - lp); - } - - if (first && exception) { - gcenable(); - if (suffix) - fail("$&access", "%s: %s", suffix, esstrerror(estatus)); - else - fail("$&access", "%s", esstrerror(estatus)); - } - Ref(List *, result, reverse(lp)); - gcenable(); - RefReturn(result); -} - -extern Dict *initprims_access(Dict *primdict) { X(access); return primdict; } diff --git a/doc/es.1 b/doc/es.1 index b9d551fc..6637e61d 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -2394,6 +2394,17 @@ into one string, separated by the string .IR separator . .TP +.Cr "%getopt \fIoptfunc args ...\fP" +This performs common +.Rc getopt +behavior, iterating through +.I args +and passing the args and the individual options they contain to +.IR optfunc . +All built-in +.I es +option parsing works via this function. +.TP .Cr "%here \fIfd word ... cmd\fP" Runs the command with the .IR word s @@ -2675,11 +2686,10 @@ builtin functions with the same names: .ta 1.75i 3.5i .Ds .ft \*(Cf -access forever throw -catch fork umask -echo if wait -exec newpgrp -exit result +forever throw catch +fork umask echo +if wait exec +newpgrp exit result .ft R .De .PP @@ -2696,6 +2706,11 @@ and primitives are used by the implementation of the .Cr vars builtin. +The +.Cr access +primitive is used by the implementation of the +.Cr access +builtin. .PP The following primitives implement the hook functions of the same names, with diff --git a/es.h b/es.h index e3337c68..a7b8d601 100644 --- a/es.h +++ b/es.h @@ -303,14 +303,6 @@ extern Boolean resetterminal; #endif -/* opt.c */ - -extern void esoptbegin(List *list, const char *caller, const char *usage, Boolean throws); -extern int esopt(const char *options); -extern Term *esoptarg(void); -extern List *esoptend(void); - - /* prim.c */ extern List *prim(char *s, List *list, Binding *binding, int evalflags); diff --git a/initial.es b/initial.es index 516b8c7c..0c2817be 100644 --- a/initial.es +++ b/initial.es @@ -63,7 +63,6 @@ # These builtin functions are straightforward calls to primitives. # See the manual page for details on what they do. -fn-access = $&access fn-break = $&break fn-catch = $&catch fn-echo = $&echo @@ -218,10 +217,64 @@ fn cd dir { } } +# The %getopt function is likely a mistake to include in the shell, +# but multiple built-ins in es require some option-parsing behavior +# and having a "librarified" version ensures some degree of consistency. +# +# This version of %getopt is designed to take advantage of the features of +# es. Its first argument, the "optfunc", is a lambda expression which will +# be repeatedly called to do the user's specific work. +# +# The remainder of the arguments to %getopt are the arguments to be parsed. +# %getopt will iterate through these until it finds one which doesn't match +# -*, or until a break-getopt exception is thrown. It will call optfunc +# once for each option, passing the full option argument and the specific +# option. The full option argument is useful for things like testing for +# `--`. When %getopt finishes, either due to an exception or a non-option +# argument, it will return the remainder of the arguments it was given. +# +# %getopt dynamically binds the function `optarg`, which when called, will +# remove the next argument from its iteration and return it to the caller. +# +# %getopt is $&noreturn and it calls its optfunc as $&noreturn, in order to +# allow the calling context to call return without any dynamic-behavior +# surprises. + +fn-%getopt = $&noreturn @ optfunc args { + catch @ e rest { + if {~ $e break-getopt} { + result $args + } { + throw $e $rest + } + } { + local (fn-optarg = { + let (result = $args(1)) { + args = $args(2 ...) + result $result + } + }) + while {~ $args(1) -*} { + catch @ e rest { + if {!~ $e continue-getopt} { + throw $e $rest + } + } { + let (arg = $args(1)) { + args = $args(2 ...) + for (c = <={%fsplit '' <={~~ $arg -*}}) { + $&noreturn $optfunc $arg $c + } + } + } + } + result $args + } +} + + # The vars function is provided for cultural compatibility with -# rc's whatis when used without arguments. The option parsing -# is very primitive; perhaps es should provide a getopt-like -# builtin. +# rc's whatis when used without arguments. # # The options to vars can be partitioned into two categories: # those which pick variables based on their source (-e for @@ -236,19 +289,8 @@ fn cd dir { # unless it is on the noexport list. fn vars { - # choose default options - if {~ $* -a} { - * = -v -f -s -e -p -i - } { - if {!~ $* -[vfs]} { * = $* -v } - if {!~ $* -[epi]} { * = $* -e } - } - # check args - for (i = $*) - if {!~ $i -[vfsepi]} { - throw error vars illegal option: $i -- usage: vars '-[vfsepia]' - } let ( + all = false vars = false fns = false sets = false @@ -256,31 +298,42 @@ fn vars { priv = false intern = false ) { - for (i = $*) if ( - {~ $i -v} {vars = true} - {~ $i -f} {fns = true} - {~ $i -s} {sets = true} - {~ $i -e} {export = true} - {~ $i -p} {priv = true} - {~ $i -i} {intern = true} - {throw error vars vars: bad option: $i} - ) + if {!~ <={%getopt @ arg c { + match $c ( + a {all = true} + v {vars = true} + f {fns = true} + s {sets = true} + e {export = true} + p {priv = true} + i {intern = true} + * {throw error vars vars: bad option: -$c -- usage: vars '-[vfsepia]'} + ) + } $*} ()} { + throw error vars vars: takes no positional arguments + } + if {!$vars && !$fns && !$sets} { + vars = true + } + if {!$export && !$priv && !$intern} { + export = true + } let ( dovar = @ var { # print functions and/or settor vars - if {if {~ $var fn-*} $fns {~ $var set-*} $sets $vars} { + if {$all || if {~ $var fn-*} $fns {~ $var set-*} $sets $vars} { echo <={%var $var} } } ) { - if {$export || $priv} { + if {$all || $export || $priv} { for (var = <= $&vars) # if not exported but in priv - if {if {~ $var $noexport} $priv $export} { + if {$all || if {~ $var $noexport} $priv $export} { $dovar $var } } - if {$intern} { + if {$all || $intern} { for (var = <= $&internals) $dovar $var } @@ -289,6 +342,56 @@ fn vars { } +# The access function provides a more capable and convenient wrapper +# around the $&access primitive: it can test multiple files at once, +# perform path-like searching (in fact, it is used for path searching), +# and react more thoroughly to successes and failures than $&access can. + +fn access { + let ( + perm = '' + type = a + + suffix = () + first = false + exception = false + + result = () + ) { + * = <={%getopt @ arg c { + match $c ( + n {suffix = <=optarg} + 1 {first = true} + e {exception = true} + (r w x) {perm = $perm^$c} + (f d c b l s p) {type = $c} + * {usage} + ) + } $*} + + let (r = ()) { + for (file = $*) { + if {!~ $suffix ()} { + if {~ $file */} { + file = $file$suffix + } { + file = $file/$suffix + } + } + if {r = <={$&access $perm $type $file} && $first} { + return $file + } + result = $result $r + } + if {$first && $exception} { + throw error access <={%flatten ' ' $suffix^':' $r} + } + } + result $result + } +} + + # # Syntactic sugar # diff --git a/runtime.es b/runtime.es index 768b9506..42dde6a6 100644 --- a/runtime.es +++ b/runtime.es @@ -59,30 +59,22 @@ fn %main argv { flags = $flags login } - for (a = $argv) { - if {!~ $a -*} { - break - } - argv = $argv(2 ...) - if {~ $a --} { - break - } - let (rfarg = ()) { - for (f = <={%fsplit '' <={~~ $a -*}}) { - match $f ( - c {runcmd = true; (cmd argv) = $argv} - o {keepclosed = true} - d {allowdumps = true} - p {protected = true} - s {stdin = true; break} - $runflag-args {rfarg = $rfarg $f} - * {usage} - ) - } - for ((arg flag) = $runflag-flagpairs) { - if {~ $arg $rfarg} { - flags = $flags $flag - } + let (rfarg = ()) { + argv = <={%getopt @ arg c { + if {~ $arg --} {throw break-getopt} + match $c ( + c {runcmd = true; cmd = <=optarg} + o {keepclosed = true} + d {allowdumps = true} + p {protected = true} + s {stdin = true; throw break-getopt} + $runflag-args {rfarg = $rfarg $f} + * {usage} + ) + } $argv} + for ((arg flag) = $runflag-flagpairs) { + if {~ $arg $rfarg} { + flags = $flags $flag } } } @@ -181,25 +173,17 @@ fn %dot args { } flags = () ) { - for (a = $args) { - if {!~ $a -*} { - break - } - args = $args(2 ...) - if {~ $a --} { - break - } - for (f = <={%fsplit '' <={~~ $a -*}}) { - match $f ( - e {flags = $flags exitonfalse} - i {flags = $flags interactive} - n {flags = $flags noexec} - v {flags = $flags echoinput} - x {flags = $flags printcmds} - * {usage} - ) - } - } + args = <={%getopt @ arg c { + if {~ $arg --} {throw break-getopt} + match $c ( + e {flags = $flags exitonfalse} + i {flasg = $flags interactive} + n {flags = $flags noexec} + v {flags = $flags echoinput} + x {flags = $flags printcmds} + * {usage} + ) + } $args} if {~ $#args 0} {usage} local ( 0 = $args(1) From 5de1c463e88fb6b4d4e365db86141d26d12283e4 Mon Sep 17 00:00:00 2001 From: jpco Date: Fri, 19 Jan 2024 11:38:12 -0800 Subject: [PATCH 24/28] Missed a couple old references to "es:main" --- main.c | 2 +- runtime.es | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main.c b/main.c index b43ed4d2..a05e0f43 100644 --- a/main.c +++ b/main.c @@ -27,7 +27,7 @@ int main(int argc, char **argv) { Ref(List *, esmain, varlookup("fn-%main", NULL)); if (esmain == NULL) { - eprint("es:main not set\n"); + eprint("%main not set\n"); return 1; } diff --git a/runtime.es b/runtime.es index 42dde6a6..f8cdbc8f 100644 --- a/runtime.es +++ b/runtime.es @@ -5,7 +5,7 @@ # # -# es:main is the entry point to the shell. It parses the binary's argv, +# %main is the entry point to the shell. It parses the binary's argv, # initializes runflags (described in detail below), runs .esrc (if login), # and hands off execution to `%run-file` or `%run-string`. # @@ -277,7 +277,7 @@ noexport = $noexport fn-%dispatch runflags # to call the parser for reading commands, hand those commands to an # appropriate dispatch function, and handle any exceptions that may be # raised. The REPLs are invoked by the %run-file function, which is -# itself typically called by either es:main or %dot. +# itself typically called by either %main or %dot. # # %interactive-loop is invoked if the -i flag is used or if the shell # determines that its input is interactive; otherwise, %batch-loop is From aa7e1e9602814308ed7a27df4a7e9be619272e8b Mon Sep 17 00:00:00 2001 From: jpco Date: Fri, 19 Jan 2024 13:33:56 -0800 Subject: [PATCH 25/28] Actually remove opt.c --- opt.c | 99 ----------------------------------------------------------- 1 file changed, 99 deletions(-) delete mode 100644 opt.c diff --git a/opt.c b/opt.c deleted file mode 100644 index 63e7b82a..00000000 --- a/opt.c +++ /dev/null @@ -1,99 +0,0 @@ -/* opt.c -- option parsing ($Revision: 1.1.1.1 $) */ - -#include "es.h" - -static const char *usage, *invoker; -static List *args; -static Term *termarg; -static int nextchar; -static Boolean throwonerr; - -extern void esoptbegin(List *list, const char *caller, const char *usagemsg, Boolean throws) { - static Boolean initialized = FALSE; - if (!initialized) { - initialized = TRUE; - globalroot(&usage); - globalroot(&invoker); - globalroot(&args); - globalroot(&termarg); - } - assert(usage == NULL); - usage = usagemsg; - invoker = caller; - args = list; - termarg = NULL; - nextchar = 0; - throwonerr = throws; -} - -extern int esopt(const char *options) { - int c; - const char *arg, *opt; - - assert(!throwonerr || usage != NULL); - assert(termarg == NULL); - if (nextchar == 0) { - if (args == NULL) - return EOF; - assert(args->term != NULL); - arg = getstr(args->term); - if (*arg != '-') - return EOF; - if (arg[1] == '-' && arg[2] == '\0') { - args = args->next; - return EOF; - } - nextchar = 1; - } else { - assert(args != NULL && args->term != NULL); - arg = getstr(args->term); - } - - c = arg[nextchar++]; - opt = strchr(options, c); - if (opt == NULL) { - const char *msg = usage; - usage = NULL; - args = NULL; - nextchar = 0; - if (throwonerr) - fail(invoker, "illegal option: -%c -- usage: %s", c, msg); - else return '?'; - } - - if (arg[nextchar] == '\0') { - nextchar = 0; - args = args->next; - } - - if (opt[1] == ':') { - if (args == NULL) { - const char *msg = usage; - if (throwonerr) - fail(invoker, - "option -%c expects an argument -- usage: %s", - c, msg); - else return ':'; - } - termarg = (nextchar == 0) - ? args->term - : mkstr(gcdup(arg + nextchar)); - nextchar = 0; - args = args->next; - } - return c; -} - -extern Term *esoptarg(void) { - Term *t = termarg; - assert(t != NULL); - termarg = NULL; - return t; -} - -extern List *esoptend(void) { - List *result = args; - args = NULL; - usage = NULL; - return result; -} From bc15306f1b06bb3cb22884c61505613cd61a1417 Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 20 Jan 2024 12:49:13 -0800 Subject: [PATCH 26/28] Fix breaking typos -_- --- runtime.es | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime.es b/runtime.es index f8cdbc8f..46f5d540 100644 --- a/runtime.es +++ b/runtime.es @@ -68,10 +68,11 @@ fn %main argv { d {allowdumps = true} p {protected = true} s {stdin = true; throw break-getopt} - $runflag-args {rfarg = $rfarg $f} + $runflag-args {rfarg = $rfarg $c} * {usage} ) } $argv} + for ((arg flag) = $runflag-flagpairs) { if {~ $arg $rfarg} { flags = $flags $flag @@ -177,7 +178,7 @@ fn %dot args { if {~ $arg --} {throw break-getopt} match $c ( e {flags = $flags exitonfalse} - i {flasg = $flags interactive} + i {flags = $flags interactive} n {flags = $flags noexec} v {flags = $flags echoinput} x {flags = $flags printcmds} From 5dd39fec3bbc18a5b177560df19d39c76a71de91 Mon Sep 17 00:00:00 2001 From: jpco Date: Sat, 20 Jan 2024 12:57:33 -0800 Subject: [PATCH 27/28] Only try to run .esrc on login if it exists --- runtime.es | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime.es b/runtime.es index 46f5d540..5d3b82ce 100644 --- a/runtime.es +++ b/runtime.es @@ -119,7 +119,7 @@ fn %main argv { $&importenvfuncs } - if {~ $runflags login} { + if {~ $runflags login && access -r ~/.esrc} { catch @ e type msg { if {~ $e exit} { throw $e $type $msg From 2a11f1f5bad9be8cc0482e29faaced72e8095711 Mon Sep 17 00:00:00 2001 From: jpco Date: Sun, 24 Nov 2024 08:42:39 -0800 Subject: [PATCH 28/28] Add $&getpid primitive and use that to initialize $pid at startup. This is actually an unrelated mailing list suggestion (see http://wryun.github.io/es-shell/mail-archive/msg00977.html) which just happens to work very well in the context of a %main hook. --- main.c | 6 ------ prim-etc.c | 5 +++++ runtime.es | 1 + 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/main.c b/main.c index a05e0f43..d51ab735 100644 --- a/main.c +++ b/main.c @@ -2,11 +2,6 @@ #include "es.h" -/* initpid -- set $pid for this shell */ -static void initpid(void) { - vardef("pid", NULL, mklist(mkstr(str("%d", getpid())), NULL)); -} - /* main -- initialize, parse command arguments, and start running */ int main(int argc, char **argv) { initgc(); @@ -18,7 +13,6 @@ int main(int argc, char **argv) { runinitial(); - initpid(); initsignals(); hidevariables(); importenv(FALSE); diff --git a/prim-etc.c b/prim-etc.c index a315b39a..50052c7d 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -265,6 +265,10 @@ PRIM(importenvfuncs) { return true; } +PRIM(getpid) { + return mklist(mkstr(str("%d", getpid())), NULL); +} + #if READLINE PRIM(resetterminal) { resetterminal = TRUE; @@ -302,6 +306,7 @@ extern Dict *initprims_etc(Dict *primdict) { X(noreturn); X(setmaxevaldepth); X(importenvfuncs); + X(getpid); #if READLINE X(resetterminal); #endif diff --git a/runtime.es b/runtime.es index 5d3b82ce..14f000c8 100644 --- a/runtime.es +++ b/runtime.es @@ -36,6 +36,7 @@ let ( ) fn %main argv { fn-%main = () + pid = <=$&getpid let ( es = es