diff --git a/Makefile.in b/Makefile.in index 14b23c53..abc641c9 100644 --- a/Makefile.in +++ b/Makefile.in @@ -52,13 +52,13 @@ 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 \ - prim-ctl.c prim-etc.c prim-io.c prim-sys.c prim.c print.c proc.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 \ - prim-ctl.o prim-etc.o prim-io.o prim-sys.o prim.o print.o proc.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 OTHER = Makefile parse.y mksignal @@ -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) @@ -130,12 +130,12 @@ 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 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/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 65f39e62..6637e61d 100644 --- a/doc/es.1 +++ b/doc/es.1 @@ -1729,6 +1729,56 @@ 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 +.Cr \-e +option. +.TP +.Cr interactive +Run as an interactive shell. +Corresponds with the +.Cr \-i +option. +.TP +.Cr noexec +Turn off execution of commands. +Corresponds with the +.Cr \-n +option. +.TP +.Cr echoinput +Echo input to standard error. +Corresponds with the +.Cr \-v +option. +.TP +.Cr printcmds +Print commands to standard error before executing them. +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 .Cr path @@ -2238,8 +2288,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 @@ -2343,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 @@ -2549,16 +2611,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 +2623,25 @@ This builtin can be used to set (by convention, the name of the program) to something other than file name. .TP +.Cr "%run-file \fI[file]\fP" +Runs either +.Cr %interactive-loop +or +.Cr %batch-loop +reading the +.I file +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 @@ -2615,20 +2686,13 @@ 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 -In addition, the primitive -.Cr dot -implements the -.Rc `` . '' -builtin function. -.PP The .Cr cd primitive is used in the implementation of the @@ -2642,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 @@ -2666,7 +2735,7 @@ prefixes and internal hyphens: .ta 1.75i 3.5i .Ds .ft \*(Cf -batchloop exitonfalse isinteractive +exitonfalse runfile runstring .ft R .De .PP @@ -2690,7 +2759,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 +2805,17 @@ 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. +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 .IR lambda , diff --git a/dump.c b/dump.c index 92d20f67..c10a815f 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 @@ -288,8 +289,28 @@ static void printheader(List *title) { } extern void runinitial(void) { - List *title = runfd(0, "initial.es", 0); - + initdumpprims(); + + 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(); cvars = mkdict(); diff --git a/es.h b/es.h index a8f6f733..a7b8d601 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(Boolean funcs); extern void hidevariables(void); extern void validatevar(const char *var); extern List *varlookup(const char *name, Binding *binding); @@ -283,37 +283,31 @@ 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); -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 *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] = '-' */ -#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; #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); extern void initprims(void); +extern void initdumpprims(void); extern List *primswithprefix(char *prefix); @@ -339,12 +333,11 @@ 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; extern void sigchk(void); -extern Boolean issilentsignal(List *e); extern void setsigdefaults(void); extern void blocksignals(void); extern void unblocksignals(void); @@ -497,7 +490,7 @@ extern List *raised(List *e); _localhandler.up = tophandler; \ tophandler = &_localhandler; \ if (!setjmp(_localhandler.label)) { - + #define CatchException(e) \ pophandler(&_localhandler); \ } else { \ diff --git a/initial.es b/initial.es index af775b55..0c2817be 100644 --- a/initial.es +++ b/initial.es @@ -63,8 +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 fn-echo = $&echo @@ -219,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 @@ -237,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 @@ -257,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 } @@ -290,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 # @@ -385,7 +487,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 @@ -580,100 +682,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 # @@ -732,6 +740,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 @@ -744,8 +753,12 @@ 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-. = $&runfile $&batchloop +# source the runtime init script. +. ./runtime.es # # Title diff --git a/input.c b/input.c index 31d525ae..31f25f1e 100644 --- a/input.c +++ b/input.c @@ -75,6 +75,38 @@ static void warn(char *s) { } +/* + * runflags + */ + +# define NRUNFLAGS 3 +static struct{ + int mask; + char *name; +} flagarr[NRUNFLAGS] = { + {run_interactive, "interactive"}, + {run_echoinput, "echoinput"}, + {run_lisptrees, "lisptrees"} +}; + +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); + for (i = 0; i < NRUNFLAGS; i++) + if (streq(s, flagarr[i].name)) { + flags |= flagarr[i].mask; + break; + } + } + RefEnd(lp); + return flags; +} + + /* * history */ @@ -341,6 +373,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 @@ -390,43 +429,22 @@ extern void resetparser(void) { } /* runinput -- run from an input source */ -extern List *runinput(Input *in, int runflags) { - volatile int flags = runflags; +extern List *runinput(Input *in, List *cmd) { 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; + + 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 - 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); + result = eval(cmd, NULL, in->runflags); CatchException (e) @@ -455,7 +473,7 @@ static void fdcleanup(Input *in) { } /* runfd -- run commands from a file descriptor */ -extern List *runfd(int fd, const char *name, int flags) { +extern List *runfd(int fd, const char *name, List *cmd) { Input in; List *result; @@ -471,7 +489,7 @@ extern List *runfd(int fd, const char *name, int flags) { in.name = (name == NULL) ? str("fd %d", fd) : name; RefAdd(in.name); - result = runinput(&in, flags); + result = runinput(&in, cmd); RefRemove(in.name); return result; @@ -488,28 +506,36 @@ static int stringfill(Input *in) { return EOF; } -/* runstring -- run commands from a string */ -extern List *runstring(const char *str, const char *name, int flags) { +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; in.lineno = 1; - in.name = (name == NULL) ? str : name; + 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, flags); + result = runinput(&in, cmd); RefRemove(in.name); return result; } @@ -546,19 +572,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); @@ -566,11 +580,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 3007c1af..d51ab735 100644 --- a/main.c +++ b/main.c @@ -2,223 +2,43 @@ #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 */ - -/* extern int isatty(int fd); */ -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]); - 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)); -} - -/* 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( - "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; - - 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 */ - initgc(); initconv(); - if (argc == 0) { - argc = 1; - argv = ealloc(2 * sizeof (char *)); - argv[0] = "es"; - argv[1] = NULL; - } - if (*argv[0] == '-') - loginshell = TRUE; + initinput(); + initprims(); + initvars(); - Ref(List *, args, listify(argc, argv)); - esoptbegin(args->next, NULL, NULL, FALSE); - while ((c = esopt("eilxvnpodsc:?GIL")) != EOF) - switch (c) { - case 'c': cmd = getstr(esoptarg()); 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 - default: - usage(); - } + runinitial(); -getopt_done: - Ref(List *, argp, esoptend()); + initsignals(); + hidevariables(); + importenv(FALSE); - if (cmd_stdin && cmd != NULL) { - eprint("es: -s and -c are incompatible\n"); - exit(1); - } + Ref(List *, args, listify(argc, argv)); - if (!keepclosed) { - checkfd(0, oOpen); - checkfd(1, oCreate); - checkfd(2, oCreate); + Ref(List *, esmain, varlookup("fn-%main", NULL)); + if (esmain == NULL) { + eprint("%main not set\n"); + return 1; } - if ( - cmd == NULL - && (argp == NULL || cmd_stdin) - && (runflags & run_interactive) == 0 - && isatty(0) - ) - runflags |= run_interactive; - ExceptionHandler - roothandler = &_localhandler; /* unhygeinic */ - initinput(); - initprims(); - initvars(); - - runinitial(); - - initpath(); - initpid(); - initsignals(runflags & run_interactive, allowquit); - hidevariables(); - initenv(environ, protected); - - if (loginshell) - runesrc(); - - if (cmd == NULL && !cmd_stdin && argp != NULL) { - int fd; - char *file = getstr(argp->term); - argp = argp->next; - if ((fd = eopen(file, oOpen)) == -1) { - eprint("%s: %s\n", file, esstrerror(errno)); - return 1; - } - vardef("*", NULL, argp); - vardef("0", NULL, mklist(mkstr(file), NULL)); - return exitstatus(runfd(fd, file, runflags)); - } - - vardef("*", NULL, argp); - vardef("0", NULL, mklist(mkstr(argv[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)); 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); - 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 - RefEnd2(argp, args); + + RefEnd2(esmain, args); } 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; -} 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-dump.c b/prim-dump.c new file mode 100644 index 00000000..1d3ce75f --- /dev/null +++ b/prim-dump.c @@ -0,0 +1,97 @@ +/* prim-dump.c -- primitives for the esdump step ($Revision: 1.0 $) */ + +#include "es.h" +#include "prim.h" + +PRIM(batchloop) { + Ref(List *, result, true); + + ExceptionHandler + + for (;;) { + List *cmd = prim("parse", NULL, NULL, 0); + if (cmd != NULL) + result = eval(cmd, NULL, evalflags); + SIGCHK(); + } + + CatchException (e) + + if (!termeq(e->term, "eof")) + throw(e); + + EndExceptionHandler + + 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); +} + +PRIM(conditionalflags) { + Ref(List *, list, NULL); + Ref(Term *, t, NULL); +#if GCINFO + t = mkstr("gcinfo"); list = mklist(t, list); + t = mkstr("I"); list = mklist(t, list); +#endif +#if GCVERBOSE + t = mkstr("gcverbose"); list = mklist(t, list); + t = mkstr("G"); list = mklist(t, list); +#endif +#if LISPTREES + t = mkstr("lisptrees"); list = mklist(t, list); + t = mkstr("L"); list = mklist(t, list); +#endif + RefEnd(t); + RefReturn(list); +} + + +/* + * initialization + */ + +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 73638d62..50052c7d 100644 --- a/prim-etc.c +++ b/prim-etc.c @@ -40,41 +40,56 @@ 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, TRUE); - 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; - } +/* If not -DGCVERBOSE or -DGCINFO, these variables are harmlessly useless */ +Boolean gcverbose = FALSE; +Boolean gcinfo = FALSE; + +PRIM(setrunflags) { + Ref(List *, lp, list); + for (lp = list; lp != NULL; lp = lp->next) { + if (termeq(lp->term, "gcverbose")) + gcverbose = TRUE; + else if (termeq(lp->term, "gcinfo")) + gcinfo = TRUE; + } + RefEnd(lp); + + setrunflags(runflags_to_int(list)); + return list; +} +PRIM(runfile) { + if (list == NULL || (list->next != NULL && list->next->next != NULL)) + fail("$&runfile", "usage: $&runfile 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, "stdin"); - Ref(char *, file, getstr(lp->term)); - lp = lp->next; - fd = eopen(file, oOpen); - if (fd == -1) - fail("$&dot", "%s: %s", file, esstrerror(errno)); + int fd = 0; - varpush(&star, "*", lp); - varpush(&zero, "0", mklist(mkstr(file), NULL)); + 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)); + Ref(char *, string, getstr(list->next->term)); - result = runfd(fd, file, runflags); + result = runstring(string, cmd); - varpop(&zero); - varpop(&star); - RefEnd2(file, lp); + RefEnd2(string, cmd); RefReturn(result); } @@ -183,42 +198,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 +221,6 @@ PRIM(internals) { return listvars(TRUE); } -PRIM(isinteractive) { - return isinteractive() ? true : false; -} - PRIM(noreturn) { if (list == NULL) fail("$&noreturn", "usage: $&noreturn lambda args ..."); @@ -279,6 +254,21 @@ PRIM(setmaxevaldepth) { RefReturn(lp); } +static Boolean didonce = FALSE; +PRIM(importenvfuncs) { + if (didonce) + return false; + + importenv(TRUE); + didonce = TRUE; + + return true; +} + +PRIM(getpid) { + return mklist(mkstr(str("%d", getpid())), NULL); +} + #if READLINE PRIM(resetterminal) { resetterminal = TRUE; @@ -296,7 +286,9 @@ extern Dict *initprims_etc(Dict *primdict) { X(count); X(version); X(exec); - X(dot); + X(setrunflags); + X(runfile); + X(runstring); X(flatten); X(whatis); X(sethistory); @@ -304,17 +296,17 @@ 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); + X(importenvfuncs); + X(getpid); #if READLINE X(resetterminal); #endif diff --git a/prim-io.c b/prim-io.c index d2061d26..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 ...]"); @@ -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 13a66ae7..ff921934 100644 --- a/prim.c +++ b/prim.c @@ -37,6 +37,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..14f000c8 --- /dev/null +++ b/runtime.es @@ -0,0 +1,366 @@ +# runtime.es -- set up initial interpreter runtime ($Revision: 1.1.1.1 $) + +# +# Es entry points +# + +# +# %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 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'. +# + +# 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. 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 = ( + (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} +) +fn %main argv { + fn-%main = () + pid = <=$&getpid + + let ( + es = es + flags = () + cmd = () + runcmd = false + keepclosed = false + stdin = false + allowdumps = false + protected = false + + fn usage { + echo $usage + exit 1 + } + ) { + if {!~ $#argv 0} { + (es argv) = $argv + } + if {~ $es -*} { + flags = $flags login + } + + 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 $c} + * {usage} + ) + } $argv} + + for ((arg flag) = $runflag-flagpairs) { + if {~ $arg $rfarg} { + flags = $flags $flag + } + } + } + + if {$runcmd && ~ $cmd ()} { + usage >[1=2] + } + + 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 {!$protected} { + $&importenvfuncs + } + + if {~ $runflags login && access -r ~/.esrc} { + catch @ e type msg { + if {~ $e exit} { + 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 + } + } { + . ~/.esrc + } + } + + # This is the main "root" exception handler for the shell. + catch @ e type msg { + if {~ $e exit} { + 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 + } + result 1 + } { + if {~ $cmd () && !$stdin && !~ $#argv 0} { + local ((0 *) = $argv) + $fn-%run-file $0 + } {!~ $cmd ()} { + local ((0 *) = $es $argv) + $fn-%run-string $cmd + } { + local ((0 *) = $es $argv) + $fn-%run-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 = () + ) { + args = <={%getopt @ arg c { + if {~ $arg --} {throw break-getopt} + match $c ( + e {flags = $flags exitonfalse} + i {flags = $flags interactive} + n {flags = $flags noexec} + v {flags = $flags echoinput} + x {flags = $flags printcmds} + * {usage} + ) + } $args} + 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 + +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 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 %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} + } + } + } + } + } +} diff --git a/signal.c b/signal.c index 527dd93c..6e48fcfc 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()); @@ -220,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]; diff --git a/var.c b/var.c index 70869aea..cabde69a 100644 --- a/var.c +++ b/var.c @@ -425,11 +425,14 @@ static void importvar(char *name0, char *value) { } -/* initenv -- load variables from the environment */ -extern void initenv(char **envp, Boolean protected) { +extern char **environ; + +/* importenv -- load variables from the environment */ +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; @@ -451,8 +454,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); }